diff -Nru jgit-4.1.2/BUILD jgit-4.11.9/BUILD --- jgit-4.1.2/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +config_setting( + name = "jdk9", + values = { + "java_toolchain": "@bazel_tools//tools/jdk:toolchain_jdk9", + }, +) + +genrule( + name = "all", + testonly = 1, + srcs = [ + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.pgm:pgm", + "//org.eclipse.jgit.ui:ui", + "//org.eclipse.jgit.archive:jgit-archive", + "//org.eclipse.jgit.http.apache:http-apache", + "//org.eclipse.jgit.http.server:jgit-servlet", + "//org.eclipse.jgit.lfs:jgit-lfs", + "//org.eclipse.jgit.lfs.server:jgit-lfs-server", + "//org.eclipse.jgit.junit:junit", + ], + outs = ["all.zip"], + cmd = " && ".join([ + "p=$$PWD", + "t=$$(mktemp -d || mktemp -d -t bazel-tmp)", + "cp $(SRCS) $$t", + "cd $$t", + "zip -qr $$p/$@ .", + ]), +) diff -Nru jgit-4.1.2/debian/changelog jgit-4.11.9/debian/changelog --- jgit-4.1.2/debian/changelog 2021-01-31 21:58:33.000000000 +0000 +++ jgit-4.11.9/debian/changelog 2021-02-01 22:07:44.000000000 +0000 @@ -1,8 +1,21 @@ -jgit (4.1.2-1) unstable; urgency=medium +jgit (4.11.9-1) unstable; urgency=medium * Team upload. * New upstream release (Closes: #905547) - Refreshed the patches + - Build the new lfs modules + - New dependencies on libjetty9-java and libgoogle-gson-java + - Disabled error_prone + - Depend on libjavaewah-java (>= 0.8.6) + * Fixed the watch file to track the releases with 2-digit minor versions + + -- Emmanuel Bourg Mon, 01 Feb 2021 23:07:44 +0100 + +jgit (4.1.2-1) unstable; urgency=medium + + * Team upload. + * New upstream release + - Refreshed the patches - Ignore the new packaging module - No longer build the console and java7 modules (removed) - New dependency on libeclipse-jdt-annotation-java diff -Nru jgit-4.1.2/debian/control jgit-4.11.9/debian/control --- jgit-4.1.2/debian/control 2021-01-31 21:25:25.000000000 +0000 +++ jgit-4.11.9/debian/control 2021-02-01 12:54:32.000000000 +0000 @@ -13,9 +13,11 @@ libcommons-compress-java, libeclipse-jdt-annotation-java, libeclipse-osgi-java, + libgoogle-gson-java, libhamcrest-java, libhttpclient-java, - libjavaewah-java, + libjavaewah-java (>= 0.8.6), + libjetty9-java, libjsch-java, libmaven-antrun-plugin-java, libservlet-api-java, @@ -31,8 +33,10 @@ Architecture: all Depends: libeclipse-jdt-annotation-java, + libgoogle-gson-java, libhttpclient-java, - libjavaewah-java, + libjavaewah-java (>= 0.8.6), + libjetty9-java, libjsch-java, libslf4j-java, ${misc:Depends} diff -Nru jgit-4.1.2/debian/libjgit-java.poms jgit-4.11.9/debian/libjgit-java.poms --- jgit-4.1.2/debian/libjgit-java.poms 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/libjgit-java.poms 2021-02-01 11:32:56.000000000 +0000 @@ -30,11 +30,15 @@ org.eclipse.jgit.ant/pom.xml --package=libjgit-ant-java --java-lib org.eclipse.jgit.ant.test/pom.xml --ignore org.eclipse.jgit.archive/pom.xml --package=jgit-cli --java-lib -org.eclipse.jgit.http.apache/pom.xml --ignore +org.eclipse.jgit.http.apache/pom.xml org.eclipse.jgit.http.server/pom.xml org.eclipse.jgit.http.test/pom.xml --ignore org.eclipse.jgit.junit/pom.xml --ignore org.eclipse.jgit.junit.http/pom.xml --ignore +org.eclipse.jgit.lfs/pom.xml +org.eclipse.jgit.lfs.server/pom.xml +org.eclipse.jgit.lfs.server.test/pom.xml --ignore +org.eclipse.jgit.lfs.test/pom.xml --ignore org.eclipse.jgit.packaging/pom.xml --ignore org.eclipse.jgit.pgm/pom.xml --package=jgit-cli --java-lib org.eclipse.jgit.pgm.test/pom.xml --ignore diff -Nru jgit-4.1.2/debian/maven.ignoreRules jgit-4.11.9/debian/maven.ignoreRules --- jgit-4.1.2/debian/maven.ignoreRules 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/maven.ignoreRules 2021-02-01 22:02:11.000000000 +0000 @@ -1,7 +1,12 @@ +* japicmp-maven-plugin * * * * +* maven-enforcer-plugin * * * * * maven-javadoc-plugin * * * * * maven-shade-plugin * * * * * maven-source-plugin * * * * * jacoco-maven-plugin * * * * * clirr-maven-plugin * * * * +* spotbugs-maven-plugin * * * * +com.google.errorprone error_prone_core * * * * +org.codehaus.plexus plexus-compiler* * * * * org.eclipse.jgit org.eclipse.jgit.junit * * * * diff -Nru jgit-4.1.2/debian/maven.properties jgit-4.11.9/debian/maven.properties --- jgit-4.1.2/debian/maven.properties 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/maven.properties 2021-02-01 11:32:56.000000000 +0000 @@ -1 +1,2 @@ maven.test.skip=true +maven.compiler.release=8 diff -Nru jgit-4.1.2/debian/maven.rules jgit-4.11.9/debian/maven.rules --- jgit-4.1.2/debian/maven.rules 2021-01-31 21:20:56.000000000 +0000 +++ jgit-4.11.9/debian/maven.rules 2021-02-01 11:32:56.000000000 +0000 @@ -22,3 +22,4 @@ s/ant/org.apache.ant/ * * s/.*/debian/ * * javax.servlet s/servlet-api/javax.servlet-api/ * s/.*/debian/ * * s/org.osgi/org.eclipse.osgi/ s/org.osgi.core/org.eclipse.osgi/ jar s/.*/debian/ * * +org.eclipse.jetty jetty-servlet * s/.*/9.x/ * * diff -Nru jgit-4.1.2/debian/patches/args4j-2.32-compatibility.patch jgit-4.11.9/debian/patches/args4j-2.32-compatibility.patch --- jgit-4.1.2/debian/patches/args4j-2.32-compatibility.patch 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/patches/args4j-2.32-compatibility.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -Description: Fixes the compatibility with args4j 2.32 -Origin: backport, https://github.com/eclipse/jgit/commit/a0558b70 ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java -@@ -76,22 +76,22 @@ - int timeout = -1; - - @Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories") -- final List enable = new ArrayList(); -+ List enable = new ArrayList(); - - @Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories") -- final List disable = new ArrayList(); -+ List disable = new ArrayList(); - - @Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename") -- final List canOverride = new ArrayList(); -+ List canOverride = new ArrayList(); - - @Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename") -- final List forbidOverride = new ArrayList(); -+ List forbidOverride = new ArrayList(); - - @Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk") - boolean exportAll; - - @Argument(required = true, metaVar = "metaVar_directory", usage = "usage_directoriesToExport") -- final List directory = new ArrayList(); -+ List directory = new ArrayList(); - - @Override - protected boolean requiresRepository() { ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java -@@ -67,7 +67,7 @@ - } - - @Argument(index = 1, metaVar = "metaVar_treeish", required = true) -- private final List trees = new ArrayList(); -+ private List trees = new ArrayList(); - - @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class) - private TreeFilter pathFilter = TreeFilter.ALL; ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java -@@ -63,7 +63,7 @@ - } - - @Argument(index = 1, metaVar = "metaVar_commitish", required = true) -- private final List commits = new ArrayList(); -+ private List commits = new ArrayList(); - - @Override - protected void run() throws Exception { ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java -@@ -77,7 +77,7 @@ - private String remote = Constants.DEFAULT_REMOTE_NAME; - - @Argument(index = 1, metaVar = "metaVar_refspec") -- private final List refSpecs = new ArrayList(); -+ private List refSpecs = new ArrayList(); - - @Option(name = "--all") - private boolean all; diff -Nru jgit-4.1.2/debian/patches/disable-errorprone.patch jgit-4.11.9/debian/patches/disable-errorprone.patch --- jgit-4.1.2/debian/patches/disable-errorprone.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/debian/patches/disable-errorprone.patch 2021-02-01 11:34:08.000000000 +0000 @@ -0,0 +1,40 @@ +Description: Disable error prone (missing dependency) +Author: Emmanuel Bourg +Forwarded: not-needed +--- a/pom.xml ++++ b/pom.xml +@@ -274,34 +274,6 @@ + 1.8 + 1.8 + +- +- +- default-compile +- compile +- +- compile +- +- +- +- org/eclipse/jgit/transport/InsecureCipherFactory.java +- +- +- +- +- compile-with-errorprone +- compile +- +- compile +- +- +- javac-with-errorprone +- true +- +- org/eclipse/jgit/transport/InsecureCipherFactory.java +- +- +- +- + + + org.codehaus.plexus diff -Nru jgit-4.1.2/debian/patches/fix-jgit-pgm-test-dependencies.patch jgit-4.11.9/debian/patches/fix-jgit-pgm-test-dependencies.patch --- jgit-4.1.2/debian/patches/fix-jgit-pgm-test-dependencies.patch 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/patches/fix-jgit-pgm-test-dependencies.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -From: Jakub Adam -Date: Wed, 26 Mar 2014 20:03:03 +0100 -Subject: fix-jgit-pgm-test-dependencies - ---- - org.eclipse.jgit.pgm.test/pom.xml | 6 ++++++ - 1 file changed, 6 insertions(+) - ---- a/org.eclipse.jgit.pgm.test/pom.xml -+++ b/org.eclipse.jgit.pgm.test/pom.xml -@@ -84,6 +84,12 @@ - ${project.version} - - -+ -+ org.tukaani -+ xz -+ debian -+ -+ - - - diff -Nru jgit-4.1.2/debian/patches/ftbfs-args4j-2.0.30-fix.patch jgit-4.11.9/debian/patches/ftbfs-args4j-2.0.30-fix.patch --- jgit-4.1.2/debian/patches/ftbfs-args4j-2.0.30-fix.patch 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/patches/ftbfs-args4j-2.0.30-fix.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,180 +0,0 @@ -From: Jakub Adam -Date: Tue, 3 Dec 2013 16:00:31 +0100 -Subject: ftbfs-args4j-2.0.30-fix - -Setting multiValued was removed from @Option in args4j 2.0.23 -as it had no effect. - -Added 'hidden' argument to OptionDef constructor. ---- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java | 2 +- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java | 10 +++++----- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java | 2 +- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java | 2 +- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java | 2 +- - .../src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java | 2 +- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java | 2 +- - org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java | 2 +- - .../src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java | 4 ++-- - .../src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java | 6 +++--- - .../src/org/eclipse/jgit/pgm/opt/CmdLineParser.java | 2 +- - 11 files changed, 18 insertions(+), 18 deletions(-) - ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java -@@ -78,7 +78,7 @@ - private String name; - - @Argument(index = 1) -- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) -+ @Option(name = "--", metaVar = "metaVar_paths", handler = StopOptionHandler.class) - private List paths = new ArrayList(); - - @Override ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java -@@ -75,16 +75,16 @@ - @Option(name = "--timeout", metaVar = "metaVar_seconds", usage = "usage_abortConnectionIfNoActivity") - int timeout = -1; - -- @Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories", multiValued = true) -+ @Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories") - final List enable = new ArrayList(); - -- @Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories", multiValued = true) -+ @Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories") - final List disable = new ArrayList(); - -- @Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true) -+ @Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename") - final List canOverride = new ArrayList(); - -- @Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true) -+ @Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename") - final List forbidOverride = new ArrayList(); - - @Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk") -@@ -159,4 +159,4 @@ - throw die(MessageFormat.format(CLIText.get().serviceNotSupported, n)); - return svc; - } --} -\ No newline at end of file -+} ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java -@@ -89,7 +89,7 @@ - @Option(name = "--cached", usage = "usage_cached") - private boolean cached; - -- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = PathTreeFilterHandler.class) -+ @Option(name = "--", metaVar = "metaVar_paths", handler = PathTreeFilterHandler.class) - private TreeFilter pathFilter = TreeFilter.ALL; - - // BEGIN -- Options shared with Log ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java -@@ -69,7 +69,7 @@ - @Argument(index = 1, metaVar = "metaVar_treeish", required = true) - private final List trees = new ArrayList(); - -- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class) -+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class) - private TreeFilter pathFilter = TreeFilter.ALL; - - @Override ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java -@@ -67,7 +67,7 @@ - private AbstractTreeIterator tree; - - @Argument(index = 1) -- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) -+ @Option(name = "--", metaVar = "metaVar_paths", handler = StopOptionHandler.class) - private List paths = new ArrayList(); - - @Override ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java -@@ -126,7 +126,7 @@ - @Argument(index = 0, metaVar = "metaVar_commitish") - private final List commits = new ArrayList(); - -- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class) -+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class) - protected TreeFilter pathFilter = TreeFilter.ALL; - - private final List revLimiter = new ArrayList(); ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java -@@ -87,7 +87,7 @@ - @Argument(index = 0, metaVar = "metaVar_object") - private String objectName; - -- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class) -+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class) - protected TreeFilter pathFilter = TreeFilter.ALL; - - // BEGIN -- Options shared with Diff ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java -@@ -83,7 +83,7 @@ - @Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class) - protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$ - -- @Option(name = "--", metaVar = "metaVar_path", multiValued = true) -+ @Option(name = "--", metaVar = "metaVar_path") - protected List filterPaths; - - @Override ---- 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 -@@ -111,13 +111,13 @@ - // - // - -- @Option(name = "--algorithm", multiValued = true, metaVar = "NAME", usage = "Enable algorithm(s)") -+ @Option(name = "--algorithm", metaVar = "NAME", usage = "Enable algorithm(s)") - List algorithms = new ArrayList(); - - @Option(name = "--text-limit", metaVar = "LIMIT", usage = "Maximum size in KiB to scan per file revision") - int textLimit = 15 * 1024; // 15 MiB as later we do * 1024. - -- @Option(name = "--repository", aliases = { "-r" }, multiValued = true, metaVar = "GIT_DIR", usage = "Repository to scan") -+ @Option(name = "--repository", aliases = { "-r" }, metaVar = "GIT_DIR", usage = "Repository to scan") - List gitDirs = new ArrayList(); - - @Option(name = "--count", metaVar = "LIMIT", usage = "Number of file revisions to be compared") ---- 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 -@@ -250,16 +250,16 @@ - // - // - -- @Option(name = "--hash", multiValued = true, metaVar = "NAME", usage = "Enable hash function(s)") -+ @Option(name = "--hash", metaVar = "NAME", usage = "Enable hash function(s)") - List hashFunctions = new ArrayList(); - -- @Option(name = "--fold", multiValued = true, metaVar = "NAME", usage = "Enable fold function(s)") -+ @Option(name = "--fold", metaVar = "NAME", usage = "Enable fold function(s)") - List foldFunctions = new ArrayList(); - - @Option(name = "--text-limit", metaVar = "LIMIT", usage = "Maximum size in KiB to scan") - int textLimit = 15 * 1024; // 15 MiB as later we do * 1024. - -- @Option(name = "--repository", aliases = { "-r" }, multiValued = true, metaVar = "GIT_DIR", usage = "Repository to scan") -+ @Option(name = "--repository", aliases = { "-r" }, metaVar = "GIT_DIR", usage = "Repository to scan") - List gitDirs = new ArrayList(); - - @Override ---- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java -+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java -@@ -184,7 +184,7 @@ - static class MyOptionDef extends OptionDef { - - public MyOptionDef(OptionDef o) { -- super(o.usage(), o.metaVar(), o.required(), o.handler(), o -+ super(o.usage(), o.metaVar(), o.required(), o.help(), o.hidden(), o.handler(), o - .isMultiValued()); - } - diff -Nru jgit-4.1.2/debian/patches/series jgit-4.11.9/debian/patches/series --- jgit-4.1.2/debian/patches/series 2021-01-31 18:51:39.000000000 +0000 +++ jgit-4.11.9/debian/patches/series 2021-02-01 11:34:03.000000000 +0000 @@ -1,5 +1,3 @@ debian-custom-build.patch -ftbfs-args4j-2.0.30-fix.patch unversioned-orbit-dependencies.patch -fix-jgit-pgm-test-dependencies.patch -args4j-2.32-compatibility.patch +disable-errorprone.patch diff -Nru jgit-4.1.2/debian/patches/unversioned-orbit-dependencies.patch jgit-4.11.9/debian/patches/unversioned-orbit-dependencies.patch --- jgit-4.1.2/debian/patches/unversioned-orbit-dependencies.patch 2021-01-31 18:53:46.000000000 +0000 +++ jgit-4.11.9/debian/patches/unversioned-orbit-dependencies.patch 2021-02-01 11:33:26.000000000 +0000 @@ -8,12 +8,12 @@ --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF -@@ -156,7 +156,7 @@ - Bundle-RequiredExecutionEnvironment: JavaSE-1.7 - 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)", +@@ -147,7 +147,7 @@ + org.eclipse.jgit.util.sha1;version="4.11.9", + org.eclipse.jgit.util.time;version="4.11.9" + Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +-Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", +Import-Package: com.googlecode.javaewah, + com.jcraft.jsch;version="[0.1.37,0.2.0)", javax.crypto, javax.net.ssl, - org.slf4j;version="[1.7.0,2.0.0)", diff -Nru jgit-4.1.2/debian/watch jgit-4.11.9/debian/watch --- jgit-4.1.2/debian/watch 2021-01-31 21:25:44.000000000 +0000 +++ jgit-4.11.9/debian/watch 2021-02-01 11:32:56.000000000 +0000 @@ -1,2 +1,2 @@ version=4 -https://git.eclipse.org/c/jgit/jgit.git/refs/tags .*/snapshot/jgit-(\d.\d.\d).\d+-r.tar.xz +https://git.eclipse.org/c/jgit/jgit.git/refs/tags .*/snapshot/jgit-([\d\.]+).\d{12}-r.tar.xz diff -Nru jgit-4.1.2/Documentation/technical/reftable.md jgit-4.11.9/Documentation/technical/reftable.md --- jgit-4.1.2/Documentation/technical/reftable.md 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/Documentation/technical/reftable.md 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,955 @@ +# reftable + +[TOC] + +## Overview + +### Problem statement + +Some repositories contain a lot of references (e.g. android at 866k, +rails at 31k). The existing packed-refs format takes up a lot of +space (e.g. 62M), and does not scale with additional references. +Lookup of a single reference requires linearly scanning the file. + +Atomic pushes modifying multiple references require copying the +entire packed-refs file, which can be a considerable amount of data +moved (e.g. 62M in, 62M out) for even small transactions (2 refs +modified). + +Repositories with many loose references occupy a large number of disk +blocks from the local file system, as each reference is its own file +storing 41 bytes (and another file for the corresponding reflog). +This negatively affects the number of inodes available when a large +number of repositories are stored on the same filesystem. Readers can +be penalized due to the larger number of syscalls required to traverse +and read the `$GIT_DIR/refs` directory. + +### Objectives + +- Near constant time lookup for any single reference, even when the + repository is cold and not in process or kernel cache. +- Near constant time verification if a SHA-1 is referred to by at + least one reference (for allow-tip-sha1-in-want). +- Efficient lookup of an entire namespace, such as `refs/tags/`. +- Support atomic push with `O(size_of_update)` operations. +- Combine reflog storage with ref storage for small transactions. +- Separate reflog storage for base refs and historical logs. + +### Description + +A reftable file is a portable binary file format customized for +reference storage. References are sorted, enabling linear scans, +binary search lookup, and range scans. + +Storage in the file is organized into variable sized blocks. Prefix +compression is used within a single block to reduce disk space. Block +size and alignment is tunable by the writer. + +### Performance + +Space used, packed-refs vs. reftable: + +repository | packed-refs | reftable | % original | avg ref | avg obj +-----------|------------:|---------:|-----------:|---------:|--------: +android | 62.2 M | 36.1 M | 58.0% | 33 bytes | 5 bytes +rails | 1.8 M | 1.1 M | 57.7% | 29 bytes | 4 bytes +git | 78.7 K | 48.1 K | 61.0% | 50 bytes | 4 bytes +git (heads)| 332 b | 269 b | 81.0% | 33 bytes | 0 bytes + +Scan (read 866k refs), by reference name lookup (single ref from 866k +refs), and by SHA-1 lookup (refs with that SHA-1, from 866k refs): + +format | cache | scan | by name | by SHA-1 +------------|------:|--------:|---------------:|---------------: +packed-refs | cold | 402 ms | 409,660.1 usec | 412,535.8 usec +packed-refs | hot | | 6,844.6 usec | 20,110.1 usec +reftable | cold | 112 ms | 33.9 usec | 323.2 usec +reftable | hot | | 20.2 usec | 320.8 usec + +Space used for 149,932 log entries for 43,061 refs, +reflog vs. reftable: + +format | size | avg entry +--------------|------:|-----------: +$GIT_DIR/logs | 173 M | 1209 bytes +reftable | 5 M | 37 bytes + +## Details + +### Peeling + +References stored in a reftable are peeled, a record for an annotated +(or signed) tag records both the tag object, and the object it refers +to. + +### Reference name encoding + +Reference names are an uninterpreted sequence of bytes that must pass +[git-check-ref-format][ref-fmt] as a valid reference name. + +[ref-fmt]: https://git-scm.com/docs/git-check-ref-format + +### Network byte order + +All multi-byte, fixed width fields are in network byte order. + +### Ordering + +Blocks are lexicographically ordered by their first reference. + +### Directory/file conflicts + +The reftable format accepts both `refs/heads/foo` and +`refs/heads/foo/bar` as distinct references. + +This property is useful for retaining log records in reftable, but may +confuse versions of Git using `$GIT_DIR/refs` directory tree to +maintain references. Users of reftable may choose to continue to +reject `foo` and `foo/bar` type conflicts to prevent problems for +peers. + +## File format + +### Structure + +A reftable file has the following high-level structure: + + first_block { + header + first_ref_block + } + ref_block* + ref_index* + obj_block* + obj_index* + log_block* + log_index* + footer + +A log-only file omits the `ref_block`, `ref_index`, `obj_block` and +`obj_index` sections, containing only the file header and log block: + + first_block { + header + } + log_block* + log_index* + footer + +in a log-only file the first log block immediately follows the file +header, without padding to block alignment. + +### Block size + +The file's block size is arbitrarily determined by the writer, and +does not have to be a power of 2. The block size must be larger than +the longest reference name or log entry used in the repository, as +references cannot span blocks. + +Powers of two that are friendly to the virtual memory system or +filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can +yield better compression, with a possible increased cost incurred by +readers during access. + +The largest block size is `16777215` bytes (15.99 MiB). + +### Block alignment + +Writers may choose to align blocks at multiples of the block size by +including `padding` filled with NUL bytes at the end of a block to +round out to the chosen alignment. When alignment is used, writers +must specify the alignment with the file header's `block_size` field. + +Block alignment is not required by the file format. Unaligned files +must set `block_size = 0` in the file header, and omit `padding`. +Unaligned files with more than one ref block must include the +[ref index](#Ref-index) to support fast lookup. Readers must be +able to read both aligned and non-aligned files. + +Very small files (e.g. 1 only ref block) may omit `padding` and the +ref index to reduce total file size. + +### Header + +A 24-byte header appears at the beginning of the file: + + 'REFT' + uint8( version_number = 1 ) + uint24( block_size ) + uint64( min_update_index ) + uint64( max_update_index ) + +Aligned files must specify `block_size` to configure readers with the +expected block alignment. Unaligned files must set `block_size = 0`. + +The `min_update_index` and `max_update_index` describe bounds for the +`update_index` field of all log records in this file. When reftables +are used in a stack for [transactions](#Update-transactions), these +fields can order the files such that the prior file's +`max_update_index + 1` is the next file's `min_update_index`. + +### First ref block + +The first ref block shares the same block as the file header, and is +24 bytes smaller than all other blocks in the file. The first block +immediately begins after the file header, at position 24. + +If the first block is a log block (a log-only file), its block header +begins immediately at position 24. + +### Ref block format + +A ref block is written as: + + 'r' + uint24( block_len ) + ref_record+ + uint24( restart_offset )+ + uint16( restart_count ) + + padding? + +Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which +encodes the number of bytes in the block up to, but not including the +optional `padding`. This is always less than or equal to the file's +block size. In the first ref block, `block_len` includes 24 bytes +for the file header. + +The 2-byte `restart_count` stores the number of entries in the +`restart_offset` list, which must not be empty. Readers can use +`restart_count` to binary search between restarts before starting a +linear scan. + +Exactly `restart_count` 3-byte `restart_offset` values precedes the +`restart_count`. Offsets are relative to the start of the block and +refer to the first byte of any `ref_record` whose name has not been +prefix compressed. Entries in the `restart_offset` list must be +sorted, ascending. Readers can start linear scans from any of these +records. + +A variable number of `ref_record` fill the middle of the block, +describing reference names and values. The format is described below. + +As the first ref block shares the first file block with the file +header, all `restart_offset` in the first block are relative to the +start of the file (position 0), and include the file header. This +forces the first `restart_offset` to be `28`. + +#### ref record + +A `ref_record` describes a single reference, storing both the name and +its value(s). Records are formatted as: + + varint( prefix_length ) + varint( (suffix_length << 3) | value_type ) + suffix + varint( update_index_delta ) + value? + +The `prefix_length` field specifies how many leading bytes of the +prior reference record's name should be copied to obtain this +reference's name. This must be 0 for the first reference in any +block, and also must be 0 for any `ref_record` whose offset is listed +in the `restart_offset` table at the end of the block. + +Recovering a reference name from any `ref_record` is a simple concat: + + this_name = prior_name[0..prefix_length] + suffix + +The `suffix_length` value provides the number of bytes available in +`suffix` to copy from `suffix` to complete the reference name. + +The `update_index` that last modified the reference can be obtained by +adding `update_index_delta` to the `min_update_index` from the file +header: `min_update_index + update_index_delta`. + +The `value` follows. Its format is determined by `value_type`, one of +the following: + +- `0x0`: deletion; no value data (see transactions, below) +- `0x1`: one 20-byte object id; value of the ref +- `0x2`: two 20-byte object ids; value of the ref, peeled target +- `0x3`: symbolic reference: `varint( target_len ) target` + +Symbolic references use `0x3`, followed by the complete name of the +reference target. No compression is applied to the target name. + +Types `0x4..0x7` are reserved for future use. + +### Ref index + +The ref index stores the name of the last reference from every ref +block in the file, enabling reduced disk seeks for lookups. Any +reference can be found by searching the index, identifying the +containing block, and searching within that block. + +The index may be organized into a multi-level index, where the 1st +level index block points to additional ref index blocks (2nd level), +which may in turn point to either additional index blocks (e.g. 3rd +level) or ref blocks (leaf level). Disk reads required to access a +ref go up with higher index levels. Multi-level indexes may be +required to ensure no single index block exceeds the file format's max +block size of `16777215` bytes (15.99 MiB). To acheive constant O(1) +disk seeks for lookups the index must be a single level, which is +permitted to exceed the file's configured block size, but not the +format's max block size of 15.99 MiB. + +If present, the ref index block(s) appears after the last ref block. + +If there are at least 4 ref blocks, a ref index block should be +written to improve lookup times. Cold reads using the index require +2 disk reads (read index, read block), and binary searching < 4 blocks +also requires <= 2 reads. Omitting the index block from smaller files +saves space. + +If the file is unaligned and contains more than one ref block, the ref +index must be written. + +Index block format: + + 'i' + uint24( block_len ) + index_record+ + uint24( restart_offset )+ + uint16( restart_count ) + + padding? + +The index blocks begin with `block_type = 'i'` and a 3-byte +`block_len` which encodes the number of bytes in the block, +up to but not including the optional `padding`. + +The `restart_offset` and `restart_count` fields are identical in +format, meaning and usage as in ref blocks. + +To reduce the number of reads required for random access in very large +files the index block may be larger than other blocks. However, +readers must hold the entire index in memory to benefit from this, so +it's a time-space tradeoff in both file size and reader memory. + +Increasing the file's block size decreases the index size. +Alternatively a multi-level index may be used, keeping index blocks +within the file's block size, but increasing the number of blocks +that need to be accessed. + +#### index record + +An index record describes the last entry in another block. +Index records are written as: + + varint( prefix_length ) + varint( (suffix_length << 3) | 0 ) + suffix + varint( block_position ) + +Index records use prefix compression exactly like `ref_record`. + +Index records store `block_position` after the suffix, specifying the +absolute position in bytes (from the start of the file) of the block +that ends with this reference. Readers can seek to `block_position` to +begin reading the block header. + +Readers must examine the block header at `block_position` to determine +if the next block is another level index block, or the leaf-level ref +block. + +#### Reading the index + +Readers loading the ref index must first read the footer (below) to +obtain `ref_index_position`. If not present, the position will be 0. +The `ref_index_position` is for the 1st level root of the ref index. + +### Obj block format + +Object blocks are optional. Writers may choose to omit object blocks, +especially if readers will not use the SHA-1 to ref mapping. + +Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping +to ref blocks containing references pointing to that object directly, +or as the peeled value of an annotated tag. Like ref blocks, object +blocks use the file's standard block size. The abbrevation length is +available in the footer as `obj_id_len`. + +To save space in small files, object blocks may be omitted if the ref +index is not present, as brute force search will only need to read a +few ref blocks. When missing, readers should brute force a linear +search of all references to lookup by SHA-1. + +An object block is written as: + + 'o' + uint24( block_len ) + obj_record+ + uint24( restart_offset )+ + uint16( restart_count ) + + padding? + +Fields are identical to ref block. Binary search using the restart +table works the same as in reference blocks. + +Because object identifiers are abbreviated by writers to the shortest +unique abbreviation within the reftable, obj key lengths are variable +between 2 and 20 bytes. Readers must compare only for common prefix +match within an obj block or obj index. + +#### obj record + +An `obj_record` describes a single object abbreviation, and the blocks +containing references using that unique abbreviation: + + varint( prefix_length ) + varint( (suffix_length << 3) | cnt_3 ) + suffix + varint( cnt_large )? + varint( position_delta )* + +Like in reference blocks, abbreviations are prefix compressed within +an obj block. On large reftables with many unique objects, higher +block sizes (64k), and higher restart interval (128), a +`prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in +obj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits). + +Each record contains `position_count` number of positions for matching +ref blocks. For 1-7 positions the count is stored in `cnt_3`. When +`cnt_3 = 0` the actual count follows in a varint, `cnt_large`. + +The use of `cnt_3` bets most objects are pointed to by only a single +reference, some may be pointed to by a couple of references, and very +few (if any) are pointed to by more than 7 references. + +A special case exists when `cnt_3 = 0` and `cnt_large = 0`: there +are no `position_delta`, but at least one reference starts with this +abbreviation. A reader that needs exact reference names must scan all +references to find which specific references have the desired object. +Writers should use this format when the `position_delta` list would have +overflowed the file's block size due to a high number of references +pointing to the same object. + +The first `position_delta` is the position from the start of the file. +Additional `position_delta` entries are sorted ascending and relative +to the prior entry, e.g. a reader would perform: + + pos = position_delta[0] + prior = pos + for (j = 1; j < position_count; j++) { + pos = prior + position_delta[j] + prior = pos + } + +With a position in hand, a reader must linearly scan the ref block, +starting from the first `ref_record`, testing each reference's SHA-1s +(for `value_type = 0x1` or `0x2`) for full equality. Faster searching +by SHA-1 within a single ref block is not supported by the reftable +format. Smaller block sizes reduce the number of candidates this step +must consider. + +### Obj index + +The obj index stores the abbreviation from the last entry for every +obj block in the file, enabling reduced disk seeks for all lookups. +It is formatted exactly the same as the ref index, but refers to obj +blocks. + +The obj index should be present if obj blocks are present, as +obj blocks should only be written in larger files. + +Readers loading the obj index must first read the footer (below) to +obtain `obj_index_position`. If not present, the position will be 0. + +### Log block format + +Unlike ref and obj blocks, log blocks are always unaligned. + +Log blocks are variable in size, and do not match the `block_size` +specified in the file header or footer. Writers should choose an +appropriate buffer size to prepare a log block for deflation, such as +`2 * block_size`. + +A log block is written as: + + 'g' + uint24( block_len ) + zlib_deflate { + log_record+ + uint24( restart_offset )+ + uint16( restart_count ) + } + +Log blocks look similar to ref blocks, except `block_type = 'g'`. + +The 4-byte block header is followed by the deflated block contents +using zlib deflate. The `block_len` in the header is the inflated +size (including 4-byte block header), and should be used by readers to +preallocate the inflation output buffer. A log block's `block_len` +may exceed the file's block size. + +Offsets within the log block (e.g. `restart_offset`) still include +the 4-byte header. Readers may prefer prefixing the inflation output +buffer with the 4-byte header. + +Within the deflate container, a variable number of `log_record` +describe reference changes. The log record format is described +below. See ref block format (above) for a description of +`restart_offset` and `restart_count`. + +Because log blocks have no alignment or padding between blocks, +readers must keep track of the bytes consumed by the inflater to +know where the next log block begins. + +#### log record + +Log record keys are structured as: + + ref_name '\0' reverse_int64( update_index ) + +where `update_index` is the unique transaction identifier. The +`update_index` field must be unique within the scope of a `ref_name`. +See the update transactions section below for further details. + +The `reverse_int64` function inverses the value so lexographical +ordering the network byte order encoding sorts the more recent records +with higher `update_index` values first: + + reverse_int64(int64 t) { + return 0xffffffffffffffff - t; + } + +Log records have a similar starting structure to ref and index +records, utilizing the same prefix compression scheme applied to the +log record key described above. + +``` + varint( prefix_length ) + varint( (suffix_length << 3) | log_type ) + suffix + log_data { + old_id + new_id + varint( name_length ) name + varint( email_length ) email + varint( time_seconds ) + sint16( tz_offset ) + varint( message_length ) message + }? +``` + +Log record entries use `log_type` to indicate what follows: + +- `0x0`: deletion; no log data. +- `0x1`: standard git reflog data using `log_data` above. + +The `log_type = 0x0` is mostly useful for `git stash drop`, removing +an entry from the reflog of `refs/stash` in a transaction file +(below), without needing to rewrite larger files. Readers reading a +stack of reflogs must treat this as a deletion. + +For `log_type = 0x1`, the `log_data` section follows +[git update-ref][update-ref] logging, and includes: + +- two 20-byte SHA-1s (old id, new id) +- varint string of committer's name +- varint string of committer's email +- varint time in seconds since epoch (Jan 1, 1970) +- 2-byte timezone offset in minutes (signed) +- varint string of message + +`tz_offset` is the absolute number of minutes from GMT the committer +was at the time of the update. For example `GMT-0800` is encoded in +reftable as `sint16(-480)` and `GMT+0230` is `sint16(150)`. + +The committer email does not contain `<` or `>`, it's the value +normally found between the `<>` in a git commit object header. + +The `message_length` may be 0, in which case there was no message +supplied for the update. + +[update-ref]: https://git-scm.com/docs/git-update-ref#_logging_updates + +#### Reading the log + +Readers accessing the log must first read the footer (below) to +determine the `log_position`. The first block of the log begins at +`log_position` bytes since the start of the file. The `log_position` +is not block aligned. + +#### Importing logs + +When importing from `$GIT_DIR/logs` writers should globally order all +log records roughly by timestamp while preserving file order, and +assign unique, increasing `update_index` values for each log line. +Newer log records get higher `update_index` values. + +Although an import may write only a single reftable file, the reftable +file must span many unique `update_index`, as each log line requires +its own `update_index` to preserve semantics. + +### Log index + +The log index stores the log key (`refname \0 reverse_int64(update_index)`) +for the last log record of every log block in the file, supporting +bounded-time lookup. + +A log index block must be written if 2 or more log blocks are written +to the file. If present, the log index appears after the last log +block. There is no padding used to align the log index to block +alignment. + +Log index format is identical to ref index, except the keys are 9 +bytes longer to include `'\0'` and the 8-byte +`reverse_int64(update_index)`. Records use `block_position` to +refer to the start of a log block. + +#### Reading the index + +Readers loading the log index must first read the footer (below) to +obtain `log_index_position`. If not present, the position will be 0. + +### Footer + +After the last block of the file, a file footer is written. It begins +like the file header, but is extended with additional data. + +A 68-byte footer appears at the end: + +``` + 'REFT' + uint8( version_number = 1 ) + uint24( block_size ) + uint64( min_update_index ) + uint64( max_update_index ) + + uint64( ref_index_position ) + uint64( (obj_position << 5) | obj_id_len ) + uint64( obj_index_position ) + + uint64( log_position ) + uint64( log_index_position ) + + uint32( CRC-32 of above ) +``` + +If a section is missing (e.g. ref index) the corresponding position +field (e.g. `ref_index_position`) will be 0. + +- `obj_position`: byte position for the first obj block. +- `obj_id_len`: number of bytes used to abbreviate object identifiers + in obj blocks. +- `log_position`: byte position for the first log block. +- `ref_index_position`: byte position for the start of the ref index. +- `obj_index_position`: byte position for the start of the obj index. +- `log_index_position`: byte position for the start of the log index. + +#### Reading the footer + +Readers must seek to `file_length - 68` to access the footer. A +trusted external source (such as `stat(2)`) is necessary to obtain +`file_length`. When reading the footer, readers must verify: + +- 4-byte magic is correct +- 1-byte version number is recognized +- 4-byte CRC-32 matches the other 64 bytes (including magic, and version) + +Once verified, the other fields of the footer can be accessed. + +### Varint encoding + +Varint encoding is identical to the ofs-delta encoding method used +within pack files. + +Decoder works such as: + + val = buf[ptr] & 0x7f + while (buf[ptr] & 0x80) { + ptr++ + val = ((val + 1) << 7) | (buf[ptr] & 0x7f) + } + +### Binary search + +Binary search within a block is supported by the `restart_offset` +fields at the end of the block. Readers can binary search through the +restart table to locate between which two restart points the sought +reference or key should appear. + +Each record identified by a `restart_offset` stores the complete key +in the `suffix` field of the record, making the compare operation +during binary search straightforward. + +Once a restart point lexicographically before the sought reference has +been identified, readers can linearly scan through the following +record entries to locate the sought record, terminating if the current +record sorts after (and therefore the sought key is not present). + +#### Restart point selection + +Writers determine the restart points at file creation. The process is +arbitrary, but every 16 or 64 records is recommended. Every 16 may +be more suitable for smaller block sizes (4k or 8k), every 64 for +larger block sizes (64k). + +More frequent restart points reduces prefix compression and increases +space consumed by the restart table, both of which increase file size. + +Less frequent restart points makes prefix compression more effective, +decreasing overall file size, with increased penalities for readers +walking through more records after the binary search step. + +A maximum of `65535` restart points per block is supported. + +## Considerations + +### Lightweight refs dominate + +The reftable format assumes the vast majority of references are single +SHA-1 valued with common prefixes, such as Gerrit Code Review's +`refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many +lightweight tags in the `refs/tags/` namespace. + +Annotated tags storing the peeled object cost an additional 20 bytes +per reference. + +### Low overhead + +A reftable with very few references (e.g. git.git with 5 heads) +is 269 bytes for reftable, vs. 332 bytes for packed-refs. This +supports reftable scaling down for transaction logs (below). + +### Block size + +For a Gerrit Code Review type repository with many change refs, larger +block sizes (64 KiB) and less frequent restart points (every 64) yield +better compression due to more references within the block compressing +against the prior reference. + +Larger block sizes reduce the index size, as the reftable will +require fewer blocks to store the same number of references. + +### Minimal disk seeks + +Assuming the index block has been loaded into memory, binary searching +for any single reference requires exactly 1 disk seek to load the +containing block. + +### Scans and lookups dominate + +Scanning all references and lookup by name (or namespace such as +`refs/heads/`) are the most common activities performed on repositories. +SHA-1s are stored directly with references to optimize this use case. + +### Logs are infrequently read + +Logs are infrequently accessed, but can be large. Deflating log +blocks saves disk space, with some increased penalty at read time. + +Logs are stored in an isolated section from refs, reducing the burden +on reference readers that want to ignore logs. Further, historical +logs can be isolated into log-only files. + +### Logs are read backwards + +Logs are frequently accessed backwards (most recent N records for +master to answer `master@{4}`), so log records are grouped by +reference, and sorted descending by update index. + +## Repository format + +### Version 1 + +A repository must set its `$GIT_DIR/config` to configure reftable: + + [core] + repositoryformatversion = 1 + [extensions] + refStorage = reftable + +### Layout + +The `$GIT_DIR/refs` path is a file when reftable is configured, not a +directory. This prevents loose references from being stored. + +A collection of reftable files are stored in the `$GIT_DIR/reftable/` +directory: + + 00000001.log + 00000001.ref + 00000002.ref + +where reftable files are named by a unique name such as produced by +the function `${update_index}.ref`. + +Log-only files use the `.log` extension, while ref-only and mixed ref +and log files use `.ref`. extension. + +The stack ordering file is `$GIT_DIR/refs` and lists the current +files, one per line, in order, from oldest (base) to newest (most +recent): + + $ cat .git/refs + 00000001.log + 00000001.ref + 00000002.ref + +Readers must read `$GIT_DIR/refs` to determine which files are +relevant right now, and search through the stack in reverse order +(last reftable is examined first). + +Reftable files not listed in `refs` may be new (and about to be added +to the stack by the active writer), or ancient and ready to be pruned. + +### Readers + +Readers can obtain a consistent snapshot of the reference space by +following: + +1. Open and read the `refs` file. +2. Open each of the reftable files that it mentions. +3. If any of the files is missing, goto 1. +4. Read from the now-open files as long as necessary. + +### Update transactions + +Although reftables are immutable, mutations are supported by writing a +new reftable and atomically appending it to the stack: + +1. Acquire `refs.lock`. +2. Read `refs` to determine current reftables. +3. Select `update_index` to be most recent file's `max_update_index + 1`. +4. Prepare temp reftable `${update_index}_XXXXXX`, including log entries. +5. Rename `${update_index}_XXXXXX` to `${update_index}.ref`. +6. Copy `refs` to `refs.lock`, appending file from (5). +7. Rename `refs.lock` to `refs`. + +During step 4 the new file's `min_update_index` and `max_update_index` +are both set to the `update_index` selected by step 3. All log +records for the transaction use the same `update_index` in their keys. +This enables later correlation of which references were updated by the +same transaction. + +Because a single `refs.lock` file is used to manage locking, the +repository is single-threaded for writers. Writers may have to +busy-spin (with backoff) around creating `refs.lock`, for up to an +acceptable wait period, aborting if the repository is too busy to +mutate. Application servers wrapped around repositories (e.g. Gerrit +Code Review) can layer their own lock/wait queue to improve fairness +to writers. + +### Reference deletions + +Deletion of any reference can be explicitly stored by setting the +`type` to `0x0` and omitting the `value` field of the `ref_record`. +This serves as a tombstone, overriding any assertions about the +existence of the reference from earlier files in the stack. + +### Compaction + +A partial stack of reftables can be compacted by merging references +using a straightforward merge join across reftables, selecting the +most recent value for output, and omitting deleted references that do +not appear in remaining, lower reftables. + +A compacted reftable should set its `min_update_index` to the smallest of +the input files' `min_update_index`, and its `max_update_index` +likewise to the largest input `max_update_index`. + +For sake of illustration, assume the stack currently consists of +reftable files (from oldest to newest): A, B, C, and D. The compactor +is going to compact B and C, leaving A and D alone. + +1. Obtain lock `refs.lock` and read the `refs` file. +2. Obtain locks `B.lock` and `C.lock`. + Ownership of these locks prevents other processes from trying + to compact these files. +3. Release `refs.lock`. +4. Compact `B` and `C` into a temp file `${min_update_index}_XXXXXX`. +5. Reacquire lock `refs.lock`. +6. Verify that `B` and `C` are still in the stack, in that order. This + should always be the case, assuming that other processes are adhering + to the locking protocol. +7. Rename `${min_update_index}_XXXXXX` to `${min_update_index}_2.ref`. +8. Write the new stack to `refs.lock`, replacing `B` and `C` with the + file from (4). +9. Rename `refs.lock` to `refs`. +10. Delete `B` and `C`, perhaps after a short sleep to avoid forcing + readers to backtrack. + +This strategy permits compactions to proceed independently of updates. + +## Alternatives considered + +### bzip packed-refs + +`bzip2` can significantly shrink a large packed-refs file (e.g. 62 +MiB compresses to 23 MiB, 37%). However the bzip format does not support +random access to a single reference. Readers must inflate and discard +while performing a linear scan. + +Breaking packed-refs into chunks (individually compressing each chunk) +would reduce the amount of data a reader must inflate, but still +leaves the problem of indexing chunks to support readers efficiently +locating the correct chunk. + +Given the compression achieved by reftable's encoding, it does not +seem necessary to add the complexity of bzip/gzip/zlib. + +### Michael Haggerty's alternate format + +Michael Haggerty proposed [an alternate][mh-alt] format to reftable on +the Git mailing list. This format uses smaller chunks, without the +restart table, and avoids block alignment with padding. Reflog entries +immediately follow each ref, and are thus interleaved between refs. + +Performance testing indicates reftable is faster for lookups (51% +faster, 11.2 usec vs. 5.4 usec), although reftable produces a +slightly larger file (+ ~3.2%, 28.3M vs 29.2M): + +format | size | seek cold | seek hot | +---------:|-------:|----------:|----------:| +mh-alt | 28.3 M | 23.4 usec | 11.2 usec | +reftable | 29.2 M | 19.9 usec | 5.4 usec | + +[mh-alt]: https://public-inbox.org/git/CAMy9T_HCnyc1g8XWOOWhe7nN0aEFyyBskV2aOMb_fe+wGvEJ7A@mail.gmail.com/ + +### JGit Ketch RefTree + +[JGit Ketch][ketch] proposed [RefTree][reftree], an encoding of +references inside Git tree objects stored as part of the repository's +object database. + +The RefTree format adds additional load on the object database storage +layer (more loose objects, more objects in packs), and relies heavily +on the packer's delta compression to save space. Namespaces which are +flat (e.g. thousands of tags in refs/tags) initially create very +large loose objects, and so RefTree does not address the problem of +copying many references to modify a handful. + +Flat namespaces are not efficiently searchable in RefTree, as tree +objects in canonical formatting cannot be binary searched. This fails +the need to handle a large number of references in a single namespace, +such as GitHub's `refs/pulls`, or a project with many tags. + +[ketch]: https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03073.html +[reftree]: https://public-inbox.org/git/CAJo=hJvnAPNAdDcAAwAvU9C4RVeQdoS3Ev9WTguHx4fD0V_nOg@mail.gmail.com/ + +### LMDB + +David Turner proposed [using LMDB][dt-lmdb], as LMDB is lightweight +(64k of runtime code) and GPL-compatible license. + +A downside of LMDB is its reliance on a single C implementation. This +makes embedding inside JGit (a popular reimplemenation of Git) +difficult, and hoisting onto virtual storage (for JGit DFS) virtually +impossible. + +A common format that can be supported by all major Git implementations +(git-core, JGit, libgit2) is strongly preferred. + +[dt-lmdb]: https://public-inbox.org/git/1455772670-21142-26-git-send-email-dturner@twopensource.com/ + +## Future + +### Longer hashes + +Version will bump (e.g. 2) to indicate `value` uses a different +object id length other than 20. The length could be stored in an +expanded file header, or hardcoded as part of the version. diff -Nru jgit-4.1.2/.gitignore jgit-4.11.9/.gitignore --- jgit-4.1.2/.gitignore 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -1 +1,6 @@ +/.project /target +.DS_Store +infer-out +bazel-* +*~ diff -Nru jgit-4.1.2/lib/BUILD jgit-4.11.9/lib/BUILD --- jgit-4.1.2/lib/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/lib/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,171 @@ +java_library( + name = "args4j", + visibility = [ + "//org.eclipse.jgit.pgm:__pkg__", + "//org.eclipse.jgit.pgm.test:__pkg__", + ], + exports = ["@args4j//jar"], +) + +java_library( + name = "commons-compress", + visibility = [ + "//org.eclipse.jgit.archive:__pkg__", + "//org.eclipse.jgit.pgm.test:__pkg__", + ], + exports = ["@commons-compress//jar"], +) + +java_library( + name = "commons-codec", + exports = ["@commons-codec//jar"], +) + +java_library( + name = "commons-logging", + testonly = 1, + visibility = ["//visibility:public"], + exports = ["@commons-logging//jar"], +) + +java_library( + name = "gson", + visibility = [ + "//org.eclipse.jgit.lfs:__pkg__", + "//org.eclipse.jgit.lfs.server:__pkg__", + ], + exports = ["@gson//jar"], +) + +java_library( + name = "httpclient", + visibility = [ + "//org.eclipse.jgit.http.apache:__pkg__", + "//org.eclipse.jgit.lfs.server.test:__pkg__", + "//org.eclipse.jgit.pgm:__pkg__", + ], + exports = ["@httpclient//jar"], +) + +java_library( + name = "httpcore", + visibility = [ + "//org.eclipse.jgit.http.apache:__pkg__", + "//org.eclipse.jgit.lfs.server:__pkg__", + "//org.eclipse.jgit.lfs.server.test:__pkg__", + "//org.eclipse.jgit.pgm:__pkg__", + ], + exports = ["@httpcore//jar"], +) + +java_library( + name = "javaewah", + visibility = ["//visibility:public"], + exports = ["@javaewah//jar"], +) + +java_library( + name = "jetty-http", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-http//jar"], + runtime_deps = [":commons-codec"], +) + +java_library( + name = "jetty-io", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-io//jar"], +) + +java_library( + name = "jetty-security", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-security//jar"], +) + +java_library( + name = "jetty-server", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-server//jar"], +) + +java_library( + name = "jetty-servlet", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-servlet//jar"], +) + +java_library( + name = "jetty-util", + # TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. + visibility = ["//visibility:public"], + exports = ["@jetty-util//jar"], +) + +java_library( + name = "jsch", + visibility = [ + "//org.eclipse.jgit:__pkg__", + "//org.eclipse.jgit.test:__pkg__", + ], + exports = ["@jsch//jar"], +) + +java_library( + name = "jzlib", + visibility = [ + "//org.eclipse.jgit:__pkg__", + "//org.eclipse.jgit.test:__pkg__", + ], + exports = ["@jzlib//jar"], +) + +java_library( + name = "junit", + testonly = 1, + visibility = ["//visibility:public"], + exports = [ + "@hamcrest-core//jar", + "@hamcrest-library//jar", + "@junit//jar", + ], +) + +java_library( + name = "servlet-api", + visibility = [ + "//org.eclipse.jgit.http.apache:__pkg__", + "//org.eclipse.jgit.http.server:__pkg__", + "//org.eclipse.jgit.http.test:__pkg__", + "//org.eclipse.jgit.junit.http:__pkg__", + "//org.eclipse.jgit.lfs.server:__pkg__", + "//org.eclipse.jgit.lfs.server.test:__pkg__", + "//org.eclipse.jgit.pgm:__pkg__", + ], + exports = ["@servlet-api-3_1//jar"], +) + +java_library( + name = "slf4j-api", + visibility = ["//visibility:public"], + exports = ["@log-api//jar"], +) + +java_library( + name = "slf4j-simple", + testonly = 1, + visibility = ["//visibility:public"], + exports = ["@slf4j-simple//jar"], +) + +java_library( + name = "xz", + testonly = 1, + visibility = ["//visibility:public"], + exports = ["@tukaani-xz//jar"], +) diff -Nru jgit-4.1.2/.mailmap jgit-4.11.9/.mailmap --- jgit-4.1.2/.mailmap 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/.mailmap 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,9 @@ -Shawn Pearce Shawn O. Pearce -Shawn Pearce Shawn Pearce -Shawn Pearce Shawn O. Pearce -Saša Živkov Sasa Zivkov -Saša Živkov Saša Živkov +Han-Wen Nienhuys Han-Wen NIenhuys +Mark Ingram markdingram +Roberto Tyley roberto +Saša Živkov Sasa Zivkov +Saša Živkov Saša Živkov +Saša Živkov Sasa Zivkov +Shawn Pearce Shawn O. Pearce +Shawn Pearce Shawn Pearce +Shawn Pearce Shawn O. Pearce diff -Nru jgit-4.1.2/.mvn/jvm.config jgit-4.11.9/.mvn/jvm.config --- jgit-4.1.2/.mvn/jvm.config 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/.mvn/jvm.config 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +-Xmx1024m \ No newline at end of file diff -Nru jgit-4.1.2/.mvn/maven.config jgit-4.11.9/.mvn/maven.config --- jgit-4.1.2/.mvn/maven.config 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/.mvn/maven.config 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +-T 1C diff -Nru jgit-4.1.2/org.eclipse.jgit/about.html jgit-4.11.9/org.eclipse.jgit/about.html --- jgit-4.1.2/org.eclipse.jgit/about.html 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/about.html 2019-09-03 12:37:49.000000000 +0000 @@ -21,6 +21,10 @@ margin-top: 0.05em; margin-bottom: 0.05em; } + .ubc-name { + margin-left: 0.5in; + white-space: pre; + } @@ -54,6 +58,39 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+
+

SHA-1 UbcCheck - MIT

+ +

Copyright (c) 2017:

+
+Marc Stevens +Cryptology Group +Centrum Wiskunde & Informatica +P.O. Box 94079, 1090 GB Amsterdam, Netherlands +marc@marc-stevens.nl +
+
+Dan Shumow +Microsoft Research +danshu@microsoft.com +
+

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +

+
  • The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software.
+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

+ diff -Nru jgit-4.1.2/org.eclipse.jgit/BUILD jgit-4.11.9/org.eclipse.jgit/BUILD --- jgit-4.1.2/org.eclipse.jgit/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +INSECURE_CIPHER_FACTORY = [ + "src/org/eclipse/jgit/transport/InsecureCipherFactory.java", +] + +SRCS = glob( + ["src/**/*.java"], + exclude = INSECURE_CIPHER_FACTORY, +) + +RESOURCES = glob(["resources/**"]) + +java_library( + name = "jgit", + srcs = SRCS, + javacopts = select({ + "//:jdk9": ["--add-modules=java.xml.bind"], + "//conditions:default": [], + }), + resource_strip_prefix = "org.eclipse.jgit/resources", + resources = RESOURCES, + deps = [ + ":insecure_cipher_factory", + "//lib:javaewah", + "//lib:jsch", + "//lib:jzlib", + "//lib:slf4j-api", + ], +) + +java_library( + name = "insecure_cipher_factory", + srcs = INSECURE_CIPHER_FACTORY, + javacopts = ["-Xep:InsecureCryptoUsage:OFF"], +) diff -Nru jgit-4.1.2/org.eclipse.jgit/.classpath jgit-4.11.9/org.eclipse.jgit/.classpath --- jgit-4.1.2/org.eclipse.jgit/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,12 +1,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit Bundle-SymbolicName: org.eclipse.jgit -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.api;version="4.1.2"; +Export-Package: org.eclipse.jgit.annotations;version="4.11.9", + org.eclipse.jgit.api;version="4.11.9"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -20,60 +22,65 @@ org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - 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"; + org.eclipse.jgit.api.errors;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", + org.eclipse.jgit.attributes;version="4.11.9", + org.eclipse.jgit.blame;version="4.11.9"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff", - org.eclipse.jgit.diff;version="4.1.2"; + org.eclipse.jgit.diff;version="4.11.9"; 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.1.2"; + org.eclipse.jgit.dircache;version="4.11.9"; 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.1.2"; + org.eclipse.jgit.errors;version="4.11.9"; 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.1.2"; - uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="4.1.2", - org.eclipse.jgit.gitrepo;version="4.1.2"; + org.eclipse.jgit.events;version="4.11.9";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.fnmatch;version="4.11.9", + org.eclipse.jgit.gitrepo;version="4.11.9"; uses:="org.eclipse.jgit.api, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.xml.sax.helpers, org.xml.sax", - org.eclipse.jgit.gitrepo.internal;version="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"; + org.eclipse.jgit.gitrepo.internal;version="4.11.9";x-internal:=true, + org.eclipse.jgit.hooks;version="4.11.9";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.ignore;version="4.11.9", + org.eclipse.jgit.ignore.internal;version="4.11.9";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal;version="4.11.9";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", + org.eclipse.jgit.internal.fsck;version="4.11.9";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal.ketch;version="4.11.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.dfs;version="4.11.9"; x-friends:="org.eclipse.jgit.test, - org.eclipse.jgit.http.server", - org.eclipse.jgit.internal.storage.file;version="4.1.2"; + org.eclipse.jgit.http.server, + org.eclipse.jgit.http.test, + org.eclipse.jgit.lfs.test", + org.eclipse.jgit.internal.storage.file;version="4.11.9"; 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.1.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.lib;version="4.1.2"; + org.eclipse.jgit.lfs, + org.eclipse.jgit.pgm, + org.eclipse.jgit.pgm.test", + org.eclipse.jgit.internal.storage.io;version="4.11.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.pack;version="4.11.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.reftable;version="4.11.9"; + x-friends:="org.eclipse.jgit.http.test,org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.reftree;version="4.11.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.lib;version="4.11.9"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, @@ -83,45 +90,33 @@ org.eclipse.jgit.treewalk, org.eclipse.jgit.transport, org.eclipse.jgit.submodule", - org.eclipse.jgit.merge;version="4.1.2"; + org.eclipse.jgit.lib.internal;version="4.11.9";x-internal:=true, + org.eclipse.jgit.merge;version="4.11.9"; 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.1.2", - org.eclipse.jgit.notes;version="4.1.2"; + org.eclipse.jgit.nls;version="4.11.9", + org.eclipse.jgit.notes;version="4.11.9"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - 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.1.2"; + org.eclipse.jgit.patch;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", + org.eclipse.jgit.revplot;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", + org.eclipse.jgit.revwalk;version="4.11.9"; 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.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.1.2"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.treewalk.filter, - org.eclipse.jgit.treewalk", - org.eclipse.jgit.transport;version="4.1.2"; + org.eclipse.jgit.revwalk.filter;version="4.11.9";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.file;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.pack;version="4.11.9";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.submodule;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk", + org.eclipse.jgit.transport;version="4.11.9"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, org.eclipse.jgit.internal.storage.pack, @@ -133,30 +128,27 @@ org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - org.eclipse.jgit.transport.http;version="4.1.2"; - uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="4.1.2"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.transport", - org.eclipse.jgit.treewalk;version="4.1.2"; + org.eclipse.jgit.transport.http;version="4.11.9";uses:="javax.net.ssl", + org.eclipse.jgit.transport.resolver;version="4.11.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", + org.eclipse.jgit.treewalk;version="4.11.9"; 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.1.2"; - uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="4.1.2"; + org.eclipse.jgit.treewalk.filter;version="4.11.9";uses:="org.eclipse.jgit.treewalk", + org.eclipse.jgit.util;version="4.11.9"; 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.1.2" -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -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)", + org.eclipse.jgit.util.io;version="4.11.9", + org.eclipse.jgit.util.sha1;version="4.11.9", + org.eclipse.jgit.util.time;version="4.11.9" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", + com.jcraft.jsch;version="[0.1.37,0.2.0)", javax.crypto, javax.net.ssl, org.slf4j;version="[1.7.0,2.0.0)", diff -Nru jgit-4.1.2/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF jgit-4.11.9/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -3,5 +3,5 @@ Bundle-Name: org.eclipse.jgit - Sources Bundle-SymbolicName: org.eclipse.jgit.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.1.2.201602141800-r -Eclipse-SourceBundle: org.eclipse.jgit;version="4.1.2.201602141800-r";roots="." +Bundle-Version: 4.11.9.201909030838-r +Eclipse-SourceBundle: org.eclipse.jgit;version="4.11.9.201909030838-r";roots="." diff -Nru jgit-4.1.2/org.eclipse.jgit/pom.xml jgit-4.11.9/org.eclipse.jgit/pom.xml --- jgit-4.1.2/org.eclipse.jgit/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit @@ -75,6 +75,11 @@ + com.jcraft + jzlib + + + com.googlecode.javaewah JavaEWAH @@ -89,11 +94,6 @@ slf4j-api - - org.eclipse.jdt - org.eclipse.jdt.annotation - 1.1.0 - @@ -166,16 +166,53 @@ - org.codehaus.mojo - clirr-maven-plugin + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + verify + + cmp + + + - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin findBugs/FindBugsExcludeFilter.xml @@ -187,13 +224,44 @@ - org.codehaus.mojo - clirr-maven-plugin - ${clirr-version} - - ${jgit-last-release-version} - info - + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + cmp-report + + + + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -3,6 +3,7 @@ errorIncludeNotImplemented=Error: tag not supported as no callback defined. errorNoDefault=Error: no default remote in manifest file. errorNoDefaultFilename=Error: no default remote in manifest file {0}. +errorNoFetch=Error: remote {0} is missing fetch attribute. errorParsingManifestFile=Error occurred during parsing manifest file. errorRemoteUnavailable=Error remote {0} is unavailable. invalidManifest=Invalid manifest. diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -20,6 +20,9 @@ atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. atLeastTwoFiltersNeeded=At least two filters needed. +atomicPushNotSupported=Atomic push not supported. +atomicRefUpdatesNotSupported=Atomic ref updates not supported +atomicSymRefNotSupported=Atomic symref not supported authenticationNotSupported=authentication not supported badBase64InputCharacterAt=Bad Base64 input character at {0} : {1} (decimal) badEntryDelimiter=Bad entry delimiter @@ -29,6 +32,7 @@ badObjectType=Bad object type: {0} badRef=Bad ref: {0}: {1} badSectionEntry=Bad section entry: {0} +badShallowLine=Bad shallow line: {0} bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor an index base64InputNotProperlyPadded=Base64 input not properly padded. baseLengthIncorrect=base length incorrect @@ -37,14 +41,20 @@ blameNotCommittedYet=Not Committed Yet blobNotFound=Blob not found: {0} blobNotFoundForPath=Blob not found: {0} for path: {1} +blockLimitNotMultipleOfBlockSize=blockLimit {0} must be a multiple of blockSize {1} +blockLimitNotPositive=blockLimit must be positive: {0} +blockSizeNotPowerOf2=blockSize must be a power of 2 +bothRefTargetsMustNotBeNull=both old and new ref targets must not be null. branchNameInvalid=Branch name {0} is not allowed buildingBitmaps=Building bitmaps cachedPacksPreventsIndexCreation=Using cached packs prevents index creation cachedPacksPreventsListingObjects=Using cached packs prevents listing objects +cannotAccessLastModifiedForSafeDeletion=Unable to access lastModifiedTime of file {0}, skip deletion since we cannot safely avoid race condition cannotBeCombined=Cannot be combined. cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included. cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}. cannotChangeToComment=Cannot change a non-comment line to a comment line. +cannotCheckoutFromUnbornBranch=Cannot checkout from unborn branch cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches. cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff. cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}. @@ -57,7 +67,7 @@ cannotCreateHEAD=cannot create HEAD 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 +cannotDeleteCheckedOutBranch=Branch {0} is checked out and cannot be deleted cannotDeleteFile=Cannot delete file: {0} cannotDeleteObjectsPath=Cannot delete {0}/{1}: {2} cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0} @@ -72,10 +82,10 @@ cannotListObjectsPath=Cannot ls {0}/{1}: {2} cannotListPackPath=Cannot ls {0}/pack: {1} cannotListRefs=cannot list refs -cannotLock=Cannot lock {0} +cannotLock=Cannot lock {0}. Ensure that no other process has an open file handle on the lock file {0}.lock, then you may delete the lock file and retry. cannotLockPackIn=Cannot lock pack in {0} cannotMatchOnEmptyString=Cannot match on empty string. -cannotMkdirObjectPath=Cannot mkdir {0}/{1}: {2} +cannotMkdirObjectPath=Cannot create directory {0}/{1}: {2} cannotMoveIndexTo=Cannot move index to {0} cannotMovePackTo=Cannot move pack to {0} cannotOpenService=cannot open {0} @@ -83,10 +93,12 @@ cannotParseGitURIish=Cannot parse Git URI-ish cannotPullOnARepoWithState=Cannot pull into a repository with state: {0} cannotRead=Cannot read {0} +cannotReadBackDelta=Cannot read delta type {0} cannotReadBlob=Cannot read blob {0} cannotReadCommit=Cannot read commit {0} cannotReadFile=Cannot read file {0} cannotReadHEAD=cannot read HEAD: {0} {1} +cannotReadIndex=The index file {0} exists but cannot be read cannotReadObject=Cannot read object cannotReadObjectsPath=Cannot read {0}/{1}: {2} cannotReadTree=Cannot read tree {0} @@ -96,6 +108,7 @@ cannotStoreObjects=cannot store objects cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID cannotUnloadAModifiedTree=Cannot unload a modified tree. +cannotUpdateUnbornBranch=Cannot update unborn branch cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index. 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. @@ -105,12 +118,16 @@ cantPassMeATree=Can't pass me a tree! channelMustBeInRange1_255=channel {0} must be in range [1, 255] characterClassIsNotSupported=The character class {0} is not supported. +checkingOutFiles=Checking out files checkoutConflictWithFile=Checkout conflict with file: {0} checkoutConflictWithFiles=Checkout conflict with files: {0} checkoutUnexpectedResult=Checkout returned unexpected result {0} classCastNotA=Not a {0} cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory +closed=closed +closeLockTokenFailed=Closing LockToken ''{0}'' failed collisionOn=Collision on {0} +commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds commandRejectedByHook=Rejected by "{0}" hook.\n{1} commandWasCalledInTheWrongState=Command {0} was called in the wrong state commitAlreadyExists=exists {0} @@ -118,18 +135,23 @@ commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported commitAmendOnInitialNotPossible=Amending is not possible on initial commit. compressingObjects=Compressing objects +configSubsectionContainsNewline=config subsection name contains newline +configSubsectionContainsNullByte=config subsection name contains byte 0x00 +configValueContainsNullByte=config value contains byte 0x00 +configHandleIsStale=config file handle is stale, {0}. retry connectionFailed=connection failed connectionTimeOut=Connection time out: {0} contextMustBeNonNegative=context must be >= 0 corruptionDetectedReReadingAt=Corruption detected re-reading at {0} +corruptObjectBadDate=bad date +corruptObjectBadEmail=bad email corruptObjectBadStream=bad stream corruptObjectBadStreamCorruptHeader=bad stream, corrupt header +corruptObjectBadTimezone=bad time zone corruptObjectDuplicateEntryNames=duplicate entry names corruptObjectGarbageAfterSize=garbage after size corruptObjectIncorrectLength=incorrect length corruptObjectIncorrectSorting=incorrectly sorted -corruptObjectInvalidAuthor=invalid author -corruptObjectInvalidCommitter=invalid committer corruptObjectInvalidEntryMode=invalid entry mode corruptObjectInvalidMode=invalid mode corruptObjectInvalidModeChar=invalid mode character @@ -148,11 +170,11 @@ corruptObjectInvalidNamePrn=invalid name 'PRN' corruptObjectInvalidObject=invalid object corruptObjectInvalidParent=invalid parent -corruptObjectInvalidTagger=invalid tagger corruptObjectInvalidTree=invalid tree corruptObjectInvalidType=invalid type corruptObjectInvalidType2=invalid type {0} corruptObjectMalformedHeader=malformed header: {0} +corruptObjectMissingEmail=missing email corruptObjectNameContainsByte=name contains byte 0x%x corruptObjectNameContainsChar=name contains '%c' corruptObjectNameContainsNullByte=name contains byte 0x00 @@ -178,6 +200,8 @@ corruptObjectTruncatedInMode=truncated in mode corruptObjectTruncatedInName=truncated in name corruptObjectTruncatedInObjectId=truncated in object id +corruptObjectZeroId=entry points to null SHA-1 +corruptUseCnt=close() called when useCnt is already zero for {0} couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen @@ -197,12 +221,14 @@ createBranchFailedUnknownReason=Create branch failed for unknown reason createBranchUnexpectedResult=Create branch returned unexpected result {0} createNewFileFailed=Could not create new file {0} +createRequiresZeroOldId=Create requires old ID to be zero credentialPassword=Password credentialUsername=Username daemonAlreadyRunning=Daemon already running daysAgo={0} days ago deleteBranchUnexpectedResult=Delete branch returned unexpected result {0} deleteFileFailed=Could not delete file {0} +deleteRequiresZeroNewId=Delete requires new ID to be zero deleteTagUnexpectedResult=Delete tag returned unexpected result {0} deletingNotSupported=Deleting {0} not supported. destinationIsNotAWildcard=Destination is not a wildcard. @@ -228,8 +254,10 @@ emptyPathNotPermitted=Empty path not permitted. emptyRef=Empty ref: {0} encryptionError=Encryption error: {0} +encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported. endOfFileInEscape=End of file in escape entryNotFoundByPath=Entry not found by path: {0} +enumValueNotSupported0=Invalid value: {0} enumValueNotSupported2=Invalid value: {0}.{1}={2} enumValueNotSupported3=Invalid value: {0}.{1}.{2}={3} enumValuesNotAvailable=Enumerated values of type {0} not available @@ -245,6 +273,7 @@ exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0} +exceptionCaughtDuringExecutionOfCommand=Exception caught during execution of command ''{0}'' in ''{1}'', return code ''{2}'', error message ''{3}'' exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command exceptionCaughtDuringExecutionOfFetchCommand=Exception caught during execution of fetch command exceptionCaughtDuringExecutionOfLsRemoteCommand=Exception caught during execution of ls-remote command @@ -255,21 +284,23 @@ exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command -exceptionCaughtDuringExcecutionOfCommand=Exception caught during execution of command {0} in {1} exceptionHookExecutionInterrupted=Execution of "{0}" hook interrupted. exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1} -exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt +exceptionWhileReadingPack=Exception caught while accessing pack file {0}, the pack file might be corrupt. Caught {1} consecutive errors while trying to read this pack. expectedACKNAKFoundEOF=Expected ACK/NAK, found EOF expectedACKNAKGot=Expected ACK/NAK, got: {0} expectedBooleanStringValue=Expected boolean string value expectedCharacterEncodingGuesses=Expected {0} character encoding guesses +expectedDirectoryNotSubmodule=Expected submodule ''{0}'' to be a directory expectedEOFReceived=expected EOF; received ''{0}'' instead expectedGot=expected ''{0}'', got ''{1}'' expectedLessThanGot=expected less than ''{0}'', got ''{1}'' expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}'' expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1} expectedReportForRefNotReceived={0}: expected report for ref {1} not received +failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}" +failedToDetermineFilterDefinition=An exception occured while determining filter definitions failedUpdatingRefs=failed updating refs failureDueToOneOfTheFollowing=Failure due to one of the following: failureUpdatingFETCH_HEAD=Failure updating FETCH_HEAD: {0} @@ -278,21 +309,28 @@ fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes). fileIsTooLarge=File is too large: {0} fileModeNotSetForPath=FileMode not set for path {0} +filterExecutionFailed=Execution of filter command ''{0}'' on file ''{1}'' failed +filterExecutionFailedRc=Execution of filter command ''{0}'' on file ''{1}'' failed with return code ''{2}'', message on stderr: ''{3}'' findingGarbage=Finding garbage flagIsDisposed={0} is disposed. flagNotFromThis={0} not from this. flagsAlreadyCreated={0} flags already created. funnyRefname=funny refname gcFailed=Garbage collection failed. +gcLogExists=A previous GC run reported an error: ''{0}''. Automatic gc will fail until ''{1}'' is removed. +gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire. gitmodulesNotFound=.gitmodules not found in tree. headRequiredToStash=HEAD required to stash local changes hoursAgo={0} hours ago +httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments +httpConfigInvalidURL=Cannot parse URL from subsection http.{0} in git config; ignored. hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet hunkBelongsToAnotherFile=Hunk belongs to another file hunkDisconnectedFromFile=Hunk disconnected from file hunkHeaderDoesNotMatchBodyLineCountOf=Hunk header {0} does not match body line count of {1} illegalArgumentNotA=Not {0} illegalCombinationOfArguments=The combination of arguments {0} and {1} is not allowed +illegalHookName=Illegal hook name {0} illegalPackingPhase=Illegal packing phase {0} illegalStateExists=exists {0} improperlyPaddedBase64Input=Improperly padded Base64 input. @@ -304,6 +342,8 @@ indexSignatureIsInvalid=Index signature is invalid: {0} indexWriteException=Modified index could not be written initFailedBareRepoDifferentDirs=When initializing a bare repo with directory {0} and separate git-dir {1} specified both folders must point to the same location +initFailedDirIsNoDirectory=Cannot set directory to ''{0}'' which is not a directory +initFailedGitDirIsNoDirectory=Cannot set git-dir to ''{0}'' which is not a directory initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location inMemoryBufferLimitExceeded=In-memory buffer limit exceeded inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing. @@ -319,8 +359,11 @@ invalidChannel=Invalid channel {0} invalidCharacterInBase64Data=Invalid character in Base64 data. invalidCommitParentNumber=Invalid commit parent number +invalidDepth=Invalid depth: {0} invalidEncryption=Invalid encryption +invalidExpandWildcard=ExpandFromSource on a refspec that can have mismatched wildcards does not make sense. invalidGitdirRef = Invalid .git reference in file ''{0}'' +invalidGitModules=Invalid .gitmodules file invalidGitType=invalid git type: {0} invalidId=Invalid id: {0} invalidId0=Invalid id @@ -330,8 +373,10 @@ invalidIntegerValue=Invalid integer value: {0}.{1}={2} invalidKey=Invalid key: {0} invalidLineInConfigFile=Invalid line in config file +invalidLineInConfigFileWithParam=Invalid line in config file: {0} invalidModeFor=Invalid mode {0} for {1} {2} in {3}. invalidModeForPath=Invalid mode {0} for path {1} +invalidNameContainsDotDot=Invalid name (contains ".."): {0} invalidObject=Invalid {0} {1}: {2} invalidOldIdSent=invalid old id sent invalidPacketLineHeader=Invalid packet line header: {0} @@ -340,13 +385,22 @@ invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} +invalidRedirectLocation=Invalid redirect location {0} -> {1} invalidReflogRevision=Invalid reflog revision: {0} invalidRefName=Invalid ref name: {0} +invalidReftableBlock=Invalid reftable block +invalidReftableCRC=Invalid reftable CRC-32 +invalidReftableFile=Invalid reftable file invalidRemote=Invalid remote: {0} +invalidRepositoryStateNoHead=Invalid repository --- cannot read HEAD invalidShallowObject=invalid shallow object {0}, expected commit invalidStageForPath=Invalid stage {0} for path {1} +invalidSystemProperty=Invalid system property ''{0}'': ''{1}''; using default value {2} invalidTagOption=Invalid tag option: {0} invalidTimeout=Invalid timeout: {0} +invalidTimeUnitValue2=Invalid time unit value: {0}.{1}={2} +invalidTimeUnitValue3=Invalid time unit value: {0}.{1}.{2}={3} +invalidTreeZeroLengthName=Cannot append a tree entry with zero-length name invalidURL=Invalid URL {0} invalidWildcards=Invalid wildcards {0} invalidRefSpec=Invalid refspec {0} @@ -359,6 +413,7 @@ largeObjectException={0} exceeds size limit largeObjectOutOfMemory=Out of memory loading {0} lengthExceedsMaximumArraySize=Length exceeds maximum array size +lfsHookConflict=LFS built-in hook conflicts with existing pre-push hook in repository {0}. Either remove the pre-push hook or disable built-in LFS support. listingAlternates=Listing alternates listingPacks=Listing packs localObjectsIncomplete=Local objects incomplete. @@ -380,8 +435,11 @@ mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}" messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger minutesAgo={0} minutes ago +mismatchOffset=mismatch offset for object {0} +mismatchCRC=mismatch CRC for object {0} missingAccesskey=Missing accesskey. missingConfigurationForKey=No value for key {0} found in configuration +missingCRC=missing CRC for object {0} missingDeltaBase=delta base missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch missingObject=Missing {0} {1} @@ -395,10 +453,12 @@ months=months monthsAgo={0} months ago multipleMergeBasesFor=Multiple merge bases for:\n {0}\n {1} found:\n {2}\n {3} +nameMustNotBeNullOrEmpty=Ref name must not be null or empty. need2Arguments=Need 2 arguments needPackOut=need packOut needsAtLeastOneEntry=Needs at least one entry needsWorkdir=Needs workdir +newIdMustNotBeNull=New ID must not be null newlineInQuotesNotAllowed=Newline in quotes not allowed noApplyInDelete=No apply in delete noClosingBracket=No closing {0} found for {1} at index {2}. @@ -407,7 +467,10 @@ noHMACsupport=No {0} support: {1} noMergeBase=No merge base could be determined. Reason={0}. {1} noMergeHeadSpecified=No merge head specified +nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos +noPathAttributesFound=No Attributes found for {0}. noSuchRef=no such ref +noSuchSubmodule=no such submodule {0} notABoolean=Not a boolean: {0} notABundle=not a bundle notADIRCFile=Not a DIRC file. @@ -425,11 +488,13 @@ objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree. objectIsCorrupt=Object {0} is corrupt: {1} +objectIsCorrupt3={0}: object {1}: {2} objectIsNotA=Object {0} is not a {1}. objectNotFound=Object {0} not found. objectNotFoundIn=Object {0} not found in {1}. obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack +oldIdMustNotBeNull=Expected old ID must not be null onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available onlyOneFetchSupported=Only one fetch supported onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported. @@ -437,18 +502,21 @@ openingConnection=Opening connection operationCanceled=Operation {0} was canceled outputHasAlreadyBeenStarted=Output has already been started. -packChecksumMismatch=Pack checksum mismatch detected for pack file {0} +overflowedReftableBlock=Overflowed reftable block +packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: .pack has {1} whilst .idx has {2} 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} +packedRefsCorruptionDetected=packed-refs corruption detected: {0} packfileCorruptionDetected=Packfile corruption detected: {0} packFileInvalid=Pack file invalid: {0} packfileIsTruncated=Packfile {0} is truncated. packfileIsTruncatedNoParam=Packfile is truncated. packHandleIsStale=Pack file {0} handle is stale, removing it from pack list packHasUnresolvedDeltas=pack has unresolved deltas +packInaccessible=Failed to access pack file {0}, caught {2} consecutive errors while trying to access this pack. packingCancelledDuringObjectsWriting=Packing cancelled during objects writing packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2} packRefs=Pack refs @@ -462,6 +530,7 @@ pathIsNotInWorkingDir=Path is not in working dir pathNotConfigured=Submodule path is not configured peeledLineBeforeRef=Peeled line before ref. +peeledRefIsRequired=Peeled ref is required. 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. @@ -480,7 +549,9 @@ pushCertificateInvalidSignature=Push certificate has invalid signature format pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport pushNotPermitted=push not permitted +pushOptionsNotSupported=Push options not supported; received {0} rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry +readerIsRequired=Reader is required readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} readTimedOut=Read timed out after {0} ms receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. @@ -488,10 +559,15 @@ receivePackInvalidLimit=Illegal limit parameter value {0} receivePackTooLarge=Pack exceeds the limit of {0} bytes, rejecting the pack receivingObjects=Receiving objects +redirectBlocked=Redirection blocked: redirect {0} -> {1} not allowed +redirectHttp=URI ''{0}'': following HTTP redirect #{1} {2} -> {3} +redirectLimitExceeded=Redirected more than {0} times; aborted at {1} -> {2} +redirectLocationMissing=Invalid redirect: no redirect location for {0} +redirectsOff=Cannot redirect because http.followRedirects is false (HTTP status {0}) refAlreadyExists=already exists refAlreadyExists1=Ref {0} already exists reflogEntryNotFound=Entry {0} not found in reflog for ''{1}'' -refNotResolved=Ref {0} can not be resolved +refNotResolved=Ref {0} cannot be resolved refUpdateReturnCodeWas=RefUpdate return code was: {0} remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. @@ -509,7 +585,7 @@ renamesRejoiningModifies=Rejoining modified file pairs repositoryAlreadyExists=Repository already exists: {0} repositoryConfigFileInvalid=Repository config file {0} invalid {1} -repositoryIsRequired=Repository is required. +repositoryIsRequired=repository is required repositoryNotFound=repository not found: {0} repositoryState_applyMailbox=Apply mailbox repositoryState_bare=Bare @@ -535,8 +611,10 @@ selectingCommits=Selecting commits sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm. serviceNotEnabledNoName=Service not enabled -serviceNotPermitted={0} not permitted +serviceNotPermitted={1} not permitted on ''{0}'' +sha1CollisionDetected1=SHA-1 collision detected on {0} shallowCommitsAlreadyInitialized=Shallow commits have already been initialized +shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk shortCompressedStreamAt=Short compressed stream at {0} shortReadOfBlock=Short read of block. shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section. @@ -551,6 +629,15 @@ sourceRefDoesntResolveToAnyObject=Source ref {0} doesn''t resolve to any object. sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0} squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD +sshUserNameError=Jsch error: failed to set SSH user name correctly to ''{0}''; using ''{1}'' picked up from SSH config file. +sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems +sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated. +sslFailureCause=SSL reported: {0} +sslFailureTrustExplanation=Do you want to skip SSL verification for this server? +sslTrustAlways=Always skip SSL verification for this server from now on +sslTrustForRepo=Skip SSL verification for git operations for repository {0} +sslTrustNow=Skip SSL verification for this single git operation +sslVerifyCannotSave=Could not save setting for http.sslVerify staleRevFlagsOn=Stale RevFlags on {0} startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported stashApplyConflict=Applying stashed changes resulted in a conflict @@ -562,6 +649,7 @@ stashDropDeleteRefFailed=Deleting stash reference failed with result: {0} stashDropFailed=Dropping stashed commit failed stashDropMissingReflog=Stash reflog does not contain entry ''{0}'' +stashDropNotSupported=Dropping stash not supported on this ref backend 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 @@ -569,16 +657,23 @@ storePushCertOneRef=Store push certificate for {0} storePushCertReflog=Store push certificate submoduleExists=Submodule ''{0}'' already exists in the index +submoduleNameInvalid=Invalid submodule name ''{0}'' submoduleParentRemoteUrlInvalid=Cannot remove segment from remote url ''{0}'' +submodulePathInvalid=Invalid submodule path ''{0}'' +submoduleUrlInvalid=Invalid submodule URL ''{0}'' submodulesNotSupported=Submodules are not supported supportOnlyPackIndexVersion2=Only support index version 2 symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java. -systemConfigFileInvalid=Systen wide config file {0} is invalid {1} +systemConfigFileInvalid=System wide config file {0} is invalid {1} tagAlreadyExists=tag ''{0}'' already exists tagNameInvalid=tag name {0} is invalid tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD currently not supported theFactoryMustNotBeNull=The factory must not be null +threadInterruptedWhileRunning="Current thread interrupted while running {0}" +timeIsUncertain=Time is uncertain timerAlreadyTerminated=Timer already terminated +tooManyCommands=Too many commands +tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? topologicalSortRequired=Topological sort required. transactionAborted=transaction aborted transportExceptionBadRef=Empty ref: {0}: {1} @@ -587,6 +682,7 @@ transportExceptionMissingAssumed=Missing assumed {0} transportExceptionReadRef=read {0} transportNeedsRepository=Transport needs repository +transportProvidedRefWithNoObjectId=Transport provided ref {0} with no object id transportProtoAmazonS3=Amazon S3 transportProtoBundleFile=Git Bundle File transportProtoFTP=FTP @@ -607,28 +703,35 @@ tSizeMustBeGreaterOrEqual1=tSize must be >= 1 unableToCheckConnectivity=Unable to check connectivity. unableToCreateNewObject=Unable to create new object: {0} +unableToReadPackfile=Unable to read packfile {0} +unableToRemovePath=Unable to remove path ''{0}'' unableToStore=Unable to store {0}. unableToWrite=Unable to write {0} unauthorized=Unauthorized +underflowedReftableBlock=Underflowed reftable block unencodeableFile=Unencodable file: {0} unexpectedCompareResult=Unexpected metadata comparison result: {0} unexpectedEndOfConfigFile=Unexpected end of config file unexpectedEndOfInput=Unexpected end of input +unexpectedEofInPack=Unexpected EOF in partially created pack unexpectedHunkTrailer=Unexpected hunk trailer unexpectedOddResult=odd: {0} + {1} - {2} unexpectedRefReport={0}: unexpected ref report: {1} unexpectedReportLine=unexpected report line: {0} unexpectedReportLine2={0} unexpected report line: {1} +unexpectedSubmoduleStatus=Unexpected submodule status: ''{0}'' unknownOrUnsupportedCommand=Unknown or unsupported command "{0}", only "{1}" is allowed. unknownDIRCVersion=Unknown DIRC version {0} unknownHost=unknown host unknownIndexVersionOrCorruptIndex=Unknown index version (or corrupt index): {0} unknownObject=unknown object +unknownObjectInIndex=unknown object {0} found in index but not in pack file unknownObjectType=Unknown object type {0}. unknownObjectType2=unknown unknownRepositoryFormat=Unknown repository format unknownRepositoryFormat2=Unknown repository format "{0}"; expected "0". unknownZlibError=Unknown zlib error. +unlockLockFileFailed=Unlocking LockFile ''{0}'' failed unmergedPath=Unmerged path: {0} unmergedPaths=Repository contains unmerged paths unpackException=Exception while parsing pack stream @@ -645,6 +748,9 @@ unsupportedOperationNotAddAtEnd=Not add-at-end: {0} unsupportedPackIndexVersion=Unsupported pack index version {0} unsupportedPackVersion=Unsupported pack version {0}. +unsupportedReftableVersion=Unsupported reftable version {0}. +unsupportedRepositoryDescription=Repository description not supported +updateRequiresOldIdAndNewId=Update requires both old ID and new ID to be nonzero updatingHeadFailed=Updating HEAD failed updatingReferences=Updating references updatingRefFailed=Updating the ref {0} to {1} failed. ReturnCode from RefUpdate.update() was {2} diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,13 @@ +accepted=accepted. +cannotFetchFromLocalReplica=cannot fetch from LocalReplica +failed=failed! +invalidFollowerUri=invalid follower URI +leaderFailedToStore=leader failed to store +localReplicaRequired=LocalReplica instance is required +mismatchedTxnNamespace=mismatched txnNamespace; expected {0} found {1} +outsideTxnNamespace=ref {0} is outside of txnNamespace {1} +proposingUpdates=Proposing updates +queuedProposalFailedToApply=queued proposal failed to apply +starting=starting! +unsupportedVoterCount=unsupported voter count {0}, expected one of {1} +waitingForQueue=Waiting for queue diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,4 @@ cannotReadIndex=Cannot read index {0} -cannotReadBackDelta=Cannot read delta type {0} shortReadOfBlock=Short read of block at {0} in pack {1}; expected {2} bytes, received only {3} shortReadOfIndex=Short read of index {0} -unexpectedEofInPack=Unexpected EOF in partially created pack willNotStoreEmptyPack=Cannot store empty pack diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,92 @@ +/* Template for compress method; run through cpp. */ + +#define ROUND1_STEP(a, b, c, d, e, T) e += s1(a,b,c,d,w[T]); b = rotateLeft(b, 30) +#define ROUND2_STEP(a, b, c, d, e, T) e += s2(a,b,c,d,w[T]); b = rotateLeft(b, 30) +#define ROUND3_STEP(a, b, c, d, e, T) e += s3(a,b,c,d,w[T]); b = rotateLeft(b, 30) +#define ROUND4_STEP(a, b, c, d, e, T) e += s4(a,b,c,d,w[T]); b = rotateLeft(b, 30) + + ROUND1_STEP(a, b, c, d, e, 0); + ROUND1_STEP(e, a, b, c, d, 1); + ROUND1_STEP(d, e, a, b, c, 2); + ROUND1_STEP(c, d, e, a, b, 3); + ROUND1_STEP(b, c, d, e, a, 4); + ROUND1_STEP(a, b, c, d, e, 5); + ROUND1_STEP(e, a, b, c, d, 6); + ROUND1_STEP(d, e, a, b, c, 7); + ROUND1_STEP(c, d, e, a, b, 8); + ROUND1_STEP(b, c, d, e, a, 9); + ROUND1_STEP(a, b, c, d, e, 10); + ROUND1_STEP(e, a, b, c, d, 11); + ROUND1_STEP(d, e, a, b, c, 12); + ROUND1_STEP(c, d, e, a, b, 13); + ROUND1_STEP(b, c, d, e, a, 14); + ROUND1_STEP(a, b, c, d, e, 15); + ROUND1_STEP(e, a, b, c, d, 16); + ROUND1_STEP(d, e, a, b, c, 17); + ROUND1_STEP(c, d, e, a, b, 18); + ROUND1_STEP(b, c, d, e, a, 19); + + ROUND2_STEP(a, b, c, d, e, 20); + ROUND2_STEP(e, a, b, c, d, 21); + ROUND2_STEP(d, e, a, b, c, 22); + ROUND2_STEP(c, d, e, a, b, 23); + ROUND2_STEP(b, c, d, e, a, 24); + ROUND2_STEP(a, b, c, d, e, 25); + ROUND2_STEP(e, a, b, c, d, 26); + ROUND2_STEP(d, e, a, b, c, 27); + ROUND2_STEP(c, d, e, a, b, 28); + ROUND2_STEP(b, c, d, e, a, 29); + ROUND2_STEP(a, b, c, d, e, 30); + ROUND2_STEP(e, a, b, c, d, 31); + ROUND2_STEP(d, e, a, b, c, 32); + ROUND2_STEP(c, d, e, a, b, 33); + ROUND2_STEP(b, c, d, e, a, 34); + ROUND2_STEP(a, b, c, d, e, 35); + ROUND2_STEP(e, a, b, c, d, 36); + ROUND2_STEP(d, e, a, b, c, 37); + ROUND2_STEP(c, d, e, a, b, 38); + ROUND2_STEP(b, c, d, e, a, 39); + + ROUND3_STEP(a, b, c, d, e, 40); + ROUND3_STEP(e, a, b, c, d, 41); + ROUND3_STEP(d, e, a, b, c, 42); + ROUND3_STEP(c, d, e, a, b, 43); + ROUND3_STEP(b, c, d, e, a, 44); + ROUND3_STEP(a, b, c, d, e, 45); + ROUND3_STEP(e, a, b, c, d, 46); + ROUND3_STEP(d, e, a, b, c, 47); + ROUND3_STEP(c, d, e, a, b, 48); + ROUND3_STEP(b, c, d, e, a, 49); + ROUND3_STEP(a, b, c, d, e, 50); + ROUND3_STEP(e, a, b, c, d, 51); + ROUND3_STEP(d, e, a, b, c, 52); + ROUND3_STEP(c, d, e, a, b, 53); + ROUND3_STEP(b, c, d, e, a, 54); + ROUND3_STEP(a, b, c, d, e, 55); + ROUND3_STEP(e, a, b, c, d, 56); + ROUND3_STEP(d, e, a, b, c, 57); + state58.save(a, b, c, d, e); + ROUND3_STEP(c, d, e, a, b, 58); + ROUND3_STEP(b, c, d, e, a, 59); + + ROUND4_STEP(a, b, c, d, e, 60); + ROUND4_STEP(e, a, b, c, d, 61); + ROUND4_STEP(d, e, a, b, c, 62); + ROUND4_STEP(c, d, e, a, b, 63); + ROUND4_STEP(b, c, d, e, a, 64); + state65.save(a, b, c, d, e); + ROUND4_STEP(a, b, c, d, e, 65); + ROUND4_STEP(e, a, b, c, d, 66); + ROUND4_STEP(d, e, a, b, c, 67); + ROUND4_STEP(c, d, e, a, b, 68); + ROUND4_STEP(b, c, d, e, a, 69); + ROUND4_STEP(a, b, c, d, e, 70); + ROUND4_STEP(e, a, b, c, d, 71); + ROUND4_STEP(d, e, a, b, c, 72); + ROUND4_STEP(c, d, e, a, b, 73); + ROUND4_STEP(b, c, d, e, a, 74); + ROUND4_STEP(a, b, c, d, e, 75); + ROUND4_STEP(e, a, b, c, d, 76); + ROUND4_STEP(d, e, a, b, c, 77); + ROUND4_STEP(c, d, e, a, b, 78); + ROUND4_STEP(b, c, d, e, a, 79); diff -Nru jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress --- jgit-4.1.2/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,192 @@ +/* Template for recompress method; run through cpp. */ + +#define ROUND1_STEP(a, b, c, d, e, T) {e += s1(a,b,c,d,w2[T]); b = rotateLeft(b, 30);} +#define ROUND2_STEP(a, b, c, d, e, T) {e += s2(a,b,c,d,w2[T]); b = rotateLeft(b, 30);} +#define ROUND3_STEP(a, b, c, d, e, T) {e += s3(a,b,c,d,w2[T]); b = rotateLeft(b, 30);} +#define ROUND4_STEP(a, b, c, d, e, T) {e += s4(a,b,c,d,w2[T]); b = rotateLeft(b, 30);} + +#define ROUND1_STEP_BW(a, b, c, d, e, T) {b = rotateRight(b, 30); e -= s1(a,b,c,d,w2[T]);} +#define ROUND2_STEP_BW(a, b, c, d, e, T) {b = rotateRight(b, 30); e -= s2(a,b,c,d,w2[T]);} +#define ROUND3_STEP_BW(a, b, c, d, e, T) {b = rotateRight(b, 30); e -= s3(a,b,c,d,w2[T]);} +#define ROUND4_STEP_BW(a, b, c, d, e, T) {b = rotateRight(b, 30); e -= s4(a,b,c,d,w2[T]);} + + /* Condition to go backwards: if (t > step) */ + /* t=80-66 have no identified DV; skip. + ROUND4_STEP_BW(b, c, d, e, a, 79) + ROUND4_STEP_BW(c, d, e, a, b, 78) + ROUND4_STEP_BW(d, e, a, b, c, 77) + ROUND4_STEP_BW(e, a, b, c, d, 76) + ROUND4_STEP_BW(a, b, c, d, e, 75) + ROUND4_STEP_BW(b, c, d, e, a, 74) + ROUND4_STEP_BW(c, d, e, a, b, 73) + ROUND4_STEP_BW(d, e, a, b, c, 72) + ROUND4_STEP_BW(e, a, b, c, d, 71) + ROUND4_STEP_BW(a, b, c, d, e, 70) + ROUND4_STEP_BW(b, c, d, e, a, 69) + ROUND4_STEP_BW(c, d, e, a, b, 68) + ROUND4_STEP_BW(d, e, a, b, c, 67) + ROUND4_STEP_BW(e, a, b, c, d, 66) + ROUND4_STEP_BW(a, b, c, d, e, 65) + */ + if (t == 65) { + ROUND4_STEP_BW(b, c, d, e, a, 64) + ROUND4_STEP_BW(c, d, e, a, b, 63) + ROUND4_STEP_BW(d, e, a, b, c, 62) + ROUND4_STEP_BW(e, a, b, c, d, 61) + ROUND4_STEP_BW(a, b, c, d, e, 60) + + ROUND3_STEP_BW(b, c, d, e, a, 59) + ROUND3_STEP_BW(c, d, e, a, b, 58) + } + ROUND3_STEP_BW(d, e, a, b, c, 57) + ROUND3_STEP_BW(e, a, b, c, d, 56) + ROUND3_STEP_BW(a, b, c, d, e, 55) + ROUND3_STEP_BW(b, c, d, e, a, 54) + ROUND3_STEP_BW(c, d, e, a, b, 53) + ROUND3_STEP_BW(d, e, a, b, c, 52) + ROUND3_STEP_BW(e, a, b, c, d, 51) + ROUND3_STEP_BW(a, b, c, d, e, 50) + ROUND3_STEP_BW(b, c, d, e, a, 49) + ROUND3_STEP_BW(c, d, e, a, b, 48) + ROUND3_STEP_BW(d, e, a, b, c, 47) + ROUND3_STEP_BW(e, a, b, c, d, 46) + ROUND3_STEP_BW(a, b, c, d, e, 45) + ROUND3_STEP_BW(b, c, d, e, a, 44) + ROUND3_STEP_BW(c, d, e, a, b, 43) + ROUND3_STEP_BW(d, e, a, b, c, 42) + ROUND3_STEP_BW(e, a, b, c, d, 41) + ROUND3_STEP_BW(a, b, c, d, e, 40) + + ROUND2_STEP_BW(b, c, d, e, a, 39) + ROUND2_STEP_BW(c, d, e, a, b, 38) + ROUND2_STEP_BW(d, e, a, b, c, 37) + ROUND2_STEP_BW(e, a, b, c, d, 36) + ROUND2_STEP_BW(a, b, c, d, e, 35) + ROUND2_STEP_BW(b, c, d, e, a, 34) + ROUND2_STEP_BW(c, d, e, a, b, 33) + ROUND2_STEP_BW(d, e, a, b, c, 32) + ROUND2_STEP_BW(e, a, b, c, d, 31) + ROUND2_STEP_BW(a, b, c, d, e, 30) + ROUND2_STEP_BW(b, c, d, e, a, 29) + ROUND2_STEP_BW(c, d, e, a, b, 28) + ROUND2_STEP_BW(d, e, a, b, c, 27) + ROUND2_STEP_BW(e, a, b, c, d, 26) + ROUND2_STEP_BW(a, b, c, d, e, 25) + ROUND2_STEP_BW(b, c, d, e, a, 24) + ROUND2_STEP_BW(c, d, e, a, b, 23) + ROUND2_STEP_BW(d, e, a, b, c, 22) + ROUND2_STEP_BW(e, a, b, c, d, 21) + ROUND2_STEP_BW(a, b, c, d, e, 20) + + ROUND1_STEP_BW(b, c, d, e, a, 19) + ROUND1_STEP_BW(c, d, e, a, b, 18) + ROUND1_STEP_BW(d, e, a, b, c, 17) + ROUND1_STEP_BW(e, a, b, c, d, 16) + ROUND1_STEP_BW(a, b, c, d, e, 15) + ROUND1_STEP_BW(b, c, d, e, a, 14) + ROUND1_STEP_BW(c, d, e, a, b, 13) + ROUND1_STEP_BW(d, e, a, b, c, 12) + ROUND1_STEP_BW(e, a, b, c, d, 11) + ROUND1_STEP_BW(a, b, c, d, e, 10) + ROUND1_STEP_BW(b, c, d, e, a, 9) + ROUND1_STEP_BW(c, d, e, a, b, 8) + ROUND1_STEP_BW(d, e, a, b, c, 7) + ROUND1_STEP_BW(e, a, b, c, d, 6) + ROUND1_STEP_BW(a, b, c, d, e, 5) + ROUND1_STEP_BW(b, c, d, e, a, 4) + ROUND1_STEP_BW(c, d, e, a, b, 3) + ROUND1_STEP_BW(d, e, a, b, c, 2) + ROUND1_STEP_BW(e, a, b, c, d, 1) + ROUND1_STEP_BW(a, b, c, d, e, 0) + + hIn.save(a, b, c, d, e); + a = s.a; b = s.b; c = s.c; d = s.d; e = s.e; + + /* Condition to go fowards: if (t <= step) */ + /* Earliest restart is T=58; skip. + ROUND1_STEP(a, b, c, d, e, 0) + ROUND1_STEP(e, a, b, c, d, 1) + ROUND1_STEP(d, e, a, b, c, 2) + ROUND1_STEP(c, d, e, a, b, 3) + ROUND1_STEP(b, c, d, e, a, 4) + ROUND1_STEP(a, b, c, d, e, 5) + ROUND1_STEP(e, a, b, c, d, 6) + ROUND1_STEP(d, e, a, b, c, 7) + ROUND1_STEP(c, d, e, a, b, 8) + ROUND1_STEP(b, c, d, e, a, 9) + ROUND1_STEP(a, b, c, d, e, 10) + ROUND1_STEP(e, a, b, c, d, 11) + ROUND1_STEP(d, e, a, b, c, 12) + ROUND1_STEP(c, d, e, a, b, 13) + ROUND1_STEP(b, c, d, e, a, 14) + ROUND1_STEP(a, b, c, d, e, 15) + ROUND1_STEP(e, a, b, c, d, 16) + ROUND1_STEP(d, e, a, b, c, 17) + ROUND1_STEP(c, d, e, a, b, 18) + ROUND1_STEP(b, c, d, e, a, 19) + + ROUND2_STEP(a, b, c, d, e, 20) + ROUND2_STEP(e, a, b, c, d, 21) + ROUND2_STEP(d, e, a, b, c, 22) + ROUND2_STEP(c, d, e, a, b, 23) + ROUND2_STEP(b, c, d, e, a, 24) + ROUND2_STEP(a, b, c, d, e, 25) + ROUND2_STEP(e, a, b, c, d, 26) + ROUND2_STEP(d, e, a, b, c, 27) + ROUND2_STEP(c, d, e, a, b, 28) + ROUND2_STEP(b, c, d, e, a, 29) + ROUND2_STEP(a, b, c, d, e, 30) + ROUND2_STEP(e, a, b, c, d, 31) + ROUND2_STEP(d, e, a, b, c, 32) + ROUND2_STEP(c, d, e, a, b, 33) + ROUND2_STEP(b, c, d, e, a, 34) + ROUND2_STEP(a, b, c, d, e, 35) + ROUND2_STEP(e, a, b, c, d, 36) + ROUND2_STEP(d, e, a, b, c, 37) + ROUND2_STEP(c, d, e, a, b, 38) + ROUND2_STEP(b, c, d, e, a, 39) + + ROUND3_STEP(a, b, c, d, e, 40) + ROUND3_STEP(e, a, b, c, d, 41) + ROUND3_STEP(d, e, a, b, c, 42) + ROUND3_STEP(c, d, e, a, b, 43) + ROUND3_STEP(b, c, d, e, a, 44) + ROUND3_STEP(a, b, c, d, e, 45) + ROUND3_STEP(e, a, b, c, d, 46) + ROUND3_STEP(d, e, a, b, c, 47) + ROUND3_STEP(c, d, e, a, b, 48) + ROUND3_STEP(b, c, d, e, a, 49) + ROUND3_STEP(a, b, c, d, e, 50) + ROUND3_STEP(e, a, b, c, d, 51) + ROUND3_STEP(d, e, a, b, c, 52) + ROUND3_STEP(c, d, e, a, b, 53) + ROUND3_STEP(b, c, d, e, a, 54) + ROUND3_STEP(a, b, c, d, e, 55) + ROUND3_STEP(e, a, b, c, d, 56) + ROUND3_STEP(d, e, a, b, c, 57) + */ + if (t == 58) { + ROUND3_STEP(c, d, e, a, b, 58) + ROUND3_STEP(b, c, d, e, a, 59) + + ROUND4_STEP(a, b, c, d, e, 60) + ROUND4_STEP(e, a, b, c, d, 61) + ROUND4_STEP(d, e, a, b, c, 62) + ROUND4_STEP(c, d, e, a, b, 63) + ROUND4_STEP(b, c, d, e, a, 64) + } + ROUND4_STEP(a, b, c, d, e, 65) + ROUND4_STEP(e, a, b, c, d, 66) + ROUND4_STEP(d, e, a, b, c, 67) + ROUND4_STEP(c, d, e, a, b, 68) + ROUND4_STEP(b, c, d, e, a, 69) + ROUND4_STEP(a, b, c, d, e, 70) + ROUND4_STEP(e, a, b, c, d, 71) + ROUND4_STEP(d, e, a, b, c, 72) + ROUND4_STEP(c, d, e, a, b, 73) + ROUND4_STEP(b, c, d, e, a, 74) + ROUND4_STEP(a, b, c, d, e, 75) + ROUND4_STEP(e, a, b, c, d, 76) + ROUND4_STEP(d, e, a, b, c, 77) + ROUND4_STEP(c, d, e, a, b, 78) + ROUND4_STEP(b, c, d, e, a, 79) diff -Nru jgit-4.1.2/org.eclipse.jgit/.settings/.api_filters jgit-4.11.9/org.eclipse.jgit/.settings/.api_filters --- jgit-4.1.2/org.eclipse.jgit/.settings/.api_filters 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/.settings/.api_filters 2019-09-03 12:37:49.000000000 +0000 @@ -1,23 +1,121 @@ - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - + + + diff -Nru jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,15 +1,15 @@ eclipse.preferences.version=1 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.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015, Andrey Loskutov + * 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.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JGit's replacement for the {@code javax.annotation.Nonnull}. + *

+ * Denotes that a local variable, parameter, field, method return value is expected + * to be non {@code null}. + * + * @since 4.2 + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) +public @interface NonNull { + // marker annotation with no members +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,99 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks types that can hold the value {@code null} at run time. + *

+ * Unlike {@code org.eclipse.jdt.annotation.Nullable}, this has run-time + * retention, allowing the annotation to be recognized by + * Guice. Unlike + * {@code javax.annotation.Nullable}, this does not involve importing new classes + * to a standard (Java EE) package, so it can be deployed in an OSGi container + * without running into + * split-package + * problems. + *

+ * You can use this annotation to qualify a type in a method signature or local + * variable declaration. The entity whose type has this annotation is allowed to + * hold the value {@code null} at run time. This allows annotation based null + * analysis to infer that + *

    + *
  • Binding a {@code null} value to the entity is legal. + *
  • Dereferencing the entity is unsafe and can trigger a + * {@code NullPointerException}. + *
+ *

+ * To avoid a dependency on Java 8, this annotation does not use + * {@link Target @Target} {@code TYPE_USE}. That may change when JGit starts + * requiring Java 8. + *

+ * Warning: Please do not use this annotation on arrays. Different + * annotation processors treat {@code @Nullable Object[]} differently: some + * treat it as an array of nullable objects, for consistency with versions of + * {@code Nullable} defined with {@code @Target} {@code TYPE_USE}, while others + * treat it as a nullable array of objects. JGit therefore avoids using this + * annotation on arrays altogether. + * + * @see + * The checker-framework manual + * @since 4.2 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) +public @interface Nullable { + // marker annotation with no members +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,17 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.FileMode.GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.LinkedList; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; @@ -57,12 +63,13 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; -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.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.NameConflictTreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; @@ -84,12 +91,14 @@ private boolean update = false; /** + * Constructor for AddCommand * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public AddCommand(Repository repo) { super(repo); - filepatterns = new LinkedList(); + filepatterns = new LinkedList<>(); } /** @@ -112,7 +121,10 @@ /** * Allow clients to provide their own implementation of a FileTreeIterator + * * @param f + * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} + * object. * @return {@code this} */ public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) { @@ -121,93 +133,127 @@ } /** + * {@inheritDoc} + *

* Executes the {@code Add} command. Each instance of this class should only * be used for one invocation of the command. Don't call this method twice * on an instance. - * - * @return the DirCache after Add */ + @Override public DirCache call() throws GitAPIException, NoFilepatternException { if (filepatterns.isEmpty()) throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); checkCallable(); DirCache dc = null; - boolean addAll = false; - if (filepatterns.contains(".")) //$NON-NLS-1$ - addAll = true; + boolean addAll = filepatterns.contains("."); //$NON-NLS-1$ try (ObjectInserter inserter = repo.newObjectInserter(); - final TreeWalk tw = new TreeWalk(repo)) { + NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) { + tw.setOperationType(OperationType.CHECKIN_OP); dc = repo.lockDirCache(); - DirCacheIterator c; DirCacheBuilder builder = dc.builder(); tw.addTree(new DirCacheBuildIterator(builder)); if (workingTreeIterator == null) workingTreeIterator = new FileTreeIterator(repo); + workingTreeIterator.setDirCacheIterator(tw, 0); tw.addTree(workingTreeIterator); - tw.setRecursive(true); if (!addAll) tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); - String lastAddedFile = null; + byte[] lastAdded = null; while (tw.next()) { - String path = tw.getPathString(); - + DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class); - if (tw.getTree(0, DirCacheIterator.class) == null && - f != null && f.isEntryIgnored()) { + if (c == null && f != null && f.isEntryIgnored()) { // file is not in index but is ignored, do nothing + continue; + } else if (c == null && update) { + // Only update of existing entries was requested. + continue; + } + + DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null; + if (entry != null && entry.getStage() > 0 + && lastAdded != null + && lastAdded.length == tw.getPathLength() + && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) { + // In case of an existing merge conflict the + // DirCacheBuildIterator iterates over all stages of + // this path, we however want to add only one + // new DirCacheEntry per path. + continue; + } + + if (tw.isSubtree() && !tw.isDirectoryFileConflict()) { + tw.enterSubtree(); + continue; + } + + if (f == null) { // working tree file does not exist + if (entry != null + && (!update || GITLINK == entry.getFileMode())) { + builder.add(entry); + } + continue; + } + + if (entry != null && entry.isAssumeValid()) { + // Index entry is marked assume valid. Even though + // the user specified the file to be added JGit does + // not consider the file for addition. + builder.add(entry); + continue; } - // In case of an existing merge conflict the - // DirCacheBuildIterator iterates over all stages of - // this path, we however want to add only one - // new DirCacheEntry per path. - else if (!(path.equals(lastAddedFile))) { - if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) { - c = tw.getTree(0, DirCacheIterator.class); - if (f != null) { // the file exists - long sz = f.getEntryLength(); - DirCacheEntry entry = new DirCacheEntry(path); - if (c == null || c.getDirCacheEntry() == null - || !c.getDirCacheEntry().isAssumeValid()) { - FileMode mode = f.getIndexFileMode(c); - entry.setFileMode(mode); - - if (FileMode.GITLINK != mode) { - entry.setLength(sz); - entry.setLastModified(f - .getEntryLastModified()); - long contentSize = f - .getEntryContentLength(); - InputStream in = f.openEntryStream(); - try { - entry.setObjectId(inserter.insert( - Constants.OBJ_BLOB, contentSize, in)); - } finally { - in.close(); - } - } else - entry.setObjectId(f.getEntryObjectId()); - builder.add(entry); - lastAddedFile = path; - } else { - builder.add(c.getDirCacheEntry()); - } - - } else if (c != null - && (!update || FileMode.GITLINK == c - .getEntryFileMode())) - builder.add(c.getDirCacheEntry()); + + if ((f.getEntryRawMode() == TYPE_TREE + && f.getIndexFileMode(c) != FileMode.GITLINK) || + (f.getEntryRawMode() == TYPE_GITLINK + && f.getIndexFileMode(c) == FileMode.TREE)) { + // Index entry exists and is symlink, gitlink or file, + // otherwise the tree would have been entered above. + // Replace the index entry by diving into tree of files. + tw.enterSubtree(); + continue; + } + + byte[] path = tw.getRawPath(); + if (entry == null || entry.getStage() > 0) { + entry = new DirCacheEntry(path); + } + FileMode mode = f.getIndexFileMode(c); + entry.setFileMode(mode); + + if (GITLINK != mode) { + entry.setLength(f.getEntryLength()); + entry.setLastModified(f.getEntryLastModified()); + long len = f.getEntryContentLength(); + // We read and filter the content multiple times. + // f.getEntryContentLength() reads and filters the input and + // inserter.insert(...) does it again. That's because an + // ObjectInserter needs to know the length before it starts + // inserting. TODO: Fix this by using Buffers. + try (InputStream in = f.openEntryStream()) { + ObjectId id = inserter.insert(OBJ_BLOB, len, in); + entry.setObjectId(id); } + } else { + entry.setLength(0); + entry.setLastModified(0); + entry.setObjectId(f.getEntryObjectId()); } + builder.add(entry); + lastAdded = path; } inserter.flush(); builder.commit(); setCallable(false); } catch (IOException e) { + Throwable cause = e.getCause(); + if (cause != null && cause instanceof FilterFailedException) + throw (FilterFailedException) cause; throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e); } finally { @@ -219,17 +265,18 @@ } /** + * Set whether to only match against already tracked files + * * @param update * If set to true, the command only matches {@code filepattern} * against already tracked files in the index rather than the * working tree. That means that it will never stage new files, * but that it will stage modified new contents of tracked files * and that it will remove files from the index if the - * corresponding files in the working tree have been removed. - * In contrast to the git command line a {@code filepattern} must - * exist also if update is set to true as there is no - * concept of a working directory here. - * + * corresponding files in the working tree have been removed. In + * contrast to the git command line a {@code filepattern} must + * exist also if update is set to true as there is no concept of + * a working directory here. * @return {@code this} */ public AddCommand setUpdate(boolean update) { @@ -238,7 +285,9 @@ } /** - * @return is the parameter update is set + * Whether to only match against already tracked files + * + * @return whether to only match against already tracked files */ public boolean isUpdate() { return update; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,26 +75,31 @@ private String notesRef = Constants.R_NOTES_COMMITS; /** + * Constructor for AddNoteCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected AddNoteCommand(Repository repo) { super(repo); } + /** {@inheritDoc} */ + @Override public Note call() throws GitAPIException { checkCallable(); NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; try (RevWalk walk = new RevWalk(repo); ObjectInserter inserter = repo.newObjectInserter()) { - Ref ref = repo.getRef(notesRef); + Ref ref = repo.findRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } map.set(id, message, inserter); - commitNoteMap(walk, map, notesCommit, inserter, + commitNoteMap(repo, notesRef, walk, map, notesCommit, inserter, "Notes added by 'git notes add'"); //$NON-NLS-1$ return map.getNote(id); } catch (IOException e) { @@ -107,6 +112,7 @@ * has a note, the existing note will be replaced. * * @param id + * a {@link org.eclipse.jgit.revwalk.RevObject} * @return {@code this} */ public AddNoteCommand setObjectId(RevObject id) { @@ -116,6 +122,8 @@ } /** + * Set the notes message + * * @param message * the notes message used when adding a note * @return {@code this} @@ -126,7 +134,8 @@ return this; } - private void commitNoteMap(RevWalk walk, NoteMap map, + static void commitNoteMap(Repository r, String ref, RevWalk walk, + NoteMap map, RevCommit notesCommit, ObjectInserter inserter, String msg) @@ -134,14 +143,14 @@ // commit the note CommitBuilder builder = new CommitBuilder(); builder.setTreeId(map.writeTree(inserter)); - builder.setAuthor(new PersonIdent(repo)); + builder.setAuthor(new PersonIdent(r)); builder.setCommitter(builder.getAuthor()); builder.setMessage(msg); if (notesCommit != null) builder.setParentIds(notesCommit); ObjectId commit = inserter.insert(builder); inserter.flush(); - RefUpdate refUpdate = repo.updateRef(notesRef); + RefUpdate refUpdate = r.updateRef(ref); if (notesCommit != null) refUpdate.setExpectedOldObjectId(notesCommit); else @@ -151,12 +160,13 @@ } /** + * Set name of a {@code Ref} to read notes from + * * @param notesRef * the ref to read notes from. Note, the default value of - * {@link Constants#R_NOTES_COMMITS} will be used if nothing is - * set + * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be + * used if nothing is set * @return {@code this} - * * @see Constants#R_NOTES_COMMITS */ public AddNoteCommand setNotesRef(String notesRef) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -57,6 +58,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; @@ -85,6 +87,8 @@ } /** + * Set patch + * * @param in * the patch to apply * @return this instance @@ -96,17 +100,15 @@ } /** + * {@inheritDoc} + *

* Executes the {@code ApplyCommand} command with all the options and * parameters collected by the setter methods (e.g. * {@link #setPatch(InputStream)} of this class. Each instance of this class * should only be used for one invocation of the command. Don't call this * method twice on an instance. - * - * @return an {@link ApplyResult} object representing the command result - * @throws GitAPIException - * @throws PatchFormatException - * @throws PatchApplyException */ + @Override public ApplyResult call() throws GitAPIException, PatchFormatException, PatchApplyException { checkCallable(); @@ -141,9 +143,13 @@ case RENAME: f = getFile(fh.getOldPath(), false); File dest = getFile(fh.getNewPath(), false); - if (!f.renameTo(dest)) + try { + FileUtils.rename(f, dest, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { throw new PatchApplyException(MessageFormat.format( - JGitText.get().renameFileFailed, f, dest)); + JGitText.get().renameFileFailed, f, dest), e); + } break; case COPY: f = getFile(fh.getOldPath(), false); @@ -191,16 +197,18 @@ private void apply(File f, FileHeader fh) throws IOException, PatchApplyException { RawText rt = new RawText(f); - List oldLines = new ArrayList(rt.size()); + List oldLines = new ArrayList<>(rt.size()); for (int i = 0; i < rt.size(); i++) oldLines.add(rt.getString(i)); - List newLines = new ArrayList(oldLines); + List newLines = new ArrayList<>(oldLines); for (HunkHeader hh : fh.getHunks()) { - StringBuilder hunk = new StringBuilder(); - for (int j = hh.getStartOffset(); j < hh.getEndOffset(); j++) - hunk.append((char) hh.getBuffer()[j]); - RawText hrt = new RawText(hunk.toString().getBytes()); - List hunkLines = new ArrayList(hrt.size()); + + byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()]; + System.arraycopy(hh.getBuffer(), hh.getStartOffset(), b, 0, + b.length); + RawText hrt = new RawText(b); + + List hunkLines = new ArrayList<>(hrt.size()); for (int i = 0; i < hrt.size(); i++) hunkLines.add(hrt.getString(i)); int pos = 0; @@ -216,12 +224,16 @@ pos++; break; case '-': - if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals( - hunkLine.substring(1))) { - throw new PatchApplyException(MessageFormat.format( - JGitText.get().patchApplyException, hh)); + if (hh.getNewStartLine() == 0) { + newLines.clear(); + } else { + if (!newLines.get(hh.getNewStartLine() - 1 + pos) + .equals(hunkLine.substring(1))) { + throw new PatchApplyException(MessageFormat.format( + JGitText.get().patchApplyException, hh)); + } + newLines.remove(hh.getNewStartLine() - 1 + pos); } - newLines.remove(hh.getNewStartLine() - 1 + pos); break; case '+': newLines.add(hh.getNewStartLine() - 1 + pos, @@ -243,10 +255,14 @@ // still there! sb.append(l).append('\n'); } - sb.deleteCharAt(sb.length() - 1); - FileWriter fw = new FileWriter(f); - fw.write(sb.toString()); - fw.close(); + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); + } + try (FileWriter fw = new FileWriter(f)) { + fw.write(sb.toString()); + } + + getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE); } private static boolean isChanged(List ol, List nl) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,15 +47,17 @@ import java.util.List; /** - * Encapsulates the result of a {@link ApplyCommand} + * Encapsulates the result of a {@link org.eclipse.jgit.api.ApplyCommand} * * @since 2.0 */ public class ApplyResult { - private List updatedFiles = new ArrayList(); + private List updatedFiles = new ArrayList<>(); /** + * Add updated file + * * @param f * an updated file * @return this instance @@ -67,6 +69,8 @@ } /** + * Get updated files + * * @return updated files */ public List getUpdatedFiles() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,19 +70,16 @@ /** * Create an archive of files from a named tree. *

- * Examples (git is a {@link Git} instance): + * Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Create a tarball from HEAD: * *

  * ArchiveCommand.registerFormat("tar", new TarFormat());
  * try {
- *	git.archive()
- *		.setTree(db.resolve("HEAD"))
- *		.setOutputStream(out)
- *		.call();
+ * 	git.archive().setTree(db.resolve("HEAD")).setOutputStream(out).call();
  * } finally {
- *	ArchiveCommand.unregisterFormat("tar");
+ * 	ArchiveCommand.unregisterFormat("tar");
  * }
  * 
*

@@ -103,7 +100,6 @@ * * @see Git * documentation about archive - * * @since 3.1 */ public class ArchiveCommand extends GitCommand { @@ -162,8 +158,8 @@ * @param out * archive object from createArchiveOutputStream * @param path - * full filename relative to the root of the archive - * (with trailing '/' for directories) + * full filename relative to the root of the archive (with + * trailing '/' for directories) * @param mode * mode (for example FileMode.REGULAR_FILE or * FileMode.SYMLINK) @@ -171,9 +167,36 @@ * blob object with data for this entry (null for * directories) * @throws IOException - * thrown by the underlying output stream for I/O errors + * thrown by the underlying output stream for I/O errors + * @deprecated use + * {@link #putEntry(Closeable, ObjectId, String, FileMode, ObjectLoader)} + * instead */ + @Deprecated void putEntry(T out, String path, FileMode mode, + ObjectLoader loader) throws IOException; + + /** + * Write an entry to an archive. + * + * @param out + * archive object from createArchiveOutputStream + * @param tree + * the tag, commit, or tree object to produce an archive for + * @param path + * full filename relative to the root of the archive (with + * trailing '/' for directories) + * @param mode + * mode (for example FileMode.REGULAR_FILE or + * FileMode.SYMLINK) + * @param loader + * blob object with data for this entry (null for + * directories) + * @throws IOException + * thrown by the underlying output stream for I/O errors + * @since 4.7 + */ + void putEntry(T out, ObjectId tree, String path, FileMode mode, ObjectLoader loader) throws IOException; /** @@ -232,7 +255,7 @@ * the --format= option) */ private static final ConcurrentMap formats = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); /** * Replaces the entry for a key only if currently mapped to a given @@ -350,13 +373,16 @@ private String prefix; private String format; private Map formatOptions = new HashMap<>(); - private List paths = new ArrayList(); + private List paths = new ArrayList<>(); /** Filename suffix, for automatically choosing a format. */ private String suffix; /** + * Constructor for ArchiveCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public ArchiveCommand(Repository repo) { super(repo); @@ -366,9 +392,10 @@ private OutputStream writeArchive(Format fmt) { try { try (TreeWalk walk = new TreeWalk(repo); - RevWalk rw = new RevWalk(walk.getObjectReader())) { + RevWalk rw = new RevWalk(walk.getObjectReader()); + T outa = fmt.createArchiveOutputStream(out, + formatOptions)) { String pfx = prefix == null ? "" : prefix; //$NON-NLS-1$ - T outa = fmt.createArchiveOutputStream(out, formatOptions); MutableObjectId idBuf = new MutableObjectId(); ObjectReader reader = walk.getObjectReader(); @@ -376,6 +403,12 @@ if (!paths.isEmpty()) walk.setFilter(PathFilterGroup.createFromStrings(paths)); + // Put base directory into archive + if (pfx.endsWith("/")) { //$NON-NLS-1$ + fmt.putEntry(outa, tree, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$ + FileMode.TREE, null); + } + while (walk.next()) { String name = pfx + walk.getPathString(); FileMode mode = walk.getFileMode(0); @@ -389,13 +422,12 @@ mode = FileMode.TREE; if (mode == FileMode.TREE) { - fmt.putEntry(outa, name + "/", mode, null); //$NON-NLS-1$ + fmt.putEntry(outa, tree, name + "/", mode, null); //$NON-NLS-1$ continue; } walk.getObjectId(idBuf, 0); - fmt.putEntry(outa, name, mode, reader.open(idBuf)); + fmt.putEntry(outa, tree, name, mode, reader.open(idBuf)); } - outa.close(); return out; } finally { out.close(); @@ -407,9 +439,7 @@ } } - /** - * @return the stream to which the archive has been written - */ + /** {@inheritDoc} */ @Override public OutputStream call() throws GitAPIException { checkCallable(); @@ -423,6 +453,8 @@ } /** + * Set the tag, commit, or tree object to produce an archive for + * * @param tree * the tag, commit, or tree object to produce an archive for * @return this @@ -437,6 +469,8 @@ } /** + * Set string prefixed to filenames in archive + * * @param prefix * string prefixed to filenames in archive (e.g., "master/"). * null means to not use any leading prefix. @@ -469,8 +503,10 @@ } /** + * Set output stream + * * @param out - * the stream to which to write the archive + * the stream to which to write the archive * @return this */ public ArchiveCommand setOutputStream(OutputStream out) { @@ -479,10 +515,11 @@ } /** + * Set archive format + * * @param fmt - * archive format (e.g., "tar" or "zip"). - * null means to choose automatically based on - * the archive filename. + * archive format (e.g., "tar" or "zip"). null means to choose + * automatically based on the archive filename. * @return this */ public ArchiveCommand setFormat(String fmt) { @@ -491,6 +528,8 @@ } /** + * Set archive format options + * * @param options * archive format options (e.g., level=9 for zip compression). * @return this diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,15 +61,16 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; +import org.eclipse.jgit.util.io.AutoLFInputStream; /** - * Blame command for building a {@link BlameResult} for a file path. + * Blame command for building a {@link org.eclipse.jgit.blame.BlameResult} for a + * file path. */ public class BlameCommand extends GitCommand { @@ -86,7 +87,10 @@ private Boolean followFileRenames; /** + * Constructor for BlameCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public BlameCommand(Repository repo) { super(repo); @@ -108,6 +112,7 @@ * Set diff algorithm * * @param diffAlgorithm + * a {@link org.eclipse.jgit.diff.DiffAlgorithm} object. * @return this command */ public BlameCommand setDiffAlgorithm(DiffAlgorithm diffAlgorithm) { @@ -119,6 +124,7 @@ * Set raw text comparator * * @param textComparator + * a {@link org.eclipse.jgit.diff.RawTextComparator} * @return this command */ public BlameCommand setTextComparator(RawTextComparator textComparator) { @@ -130,6 +136,7 @@ * Set start commit id * * @param commit + * id of a commit * @return this command */ public BlameCommand setStartCommit(AnyObjectId commit) { @@ -164,7 +171,7 @@ * most recent commit to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameCommand reverse(AnyObjectId start, AnyObjectId end) @@ -182,22 +189,23 @@ * most recent commits to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameCommand reverse(AnyObjectId start, Collection end) throws IOException { startCommit = start.toObjectId(); - reverseEndCommits = new ArrayList(end); + reverseEndCommits = new ArrayList<>(end); return this; } /** + * {@inheritDoc} + *

* Generate a list of lines with information about when the lines were * introduced into the file path. - * - * @return list of lines */ + @Override public BlameResult call() throws GitAPIException { checkCallable(); try (BlameGenerator gen = new BlameGenerator(repo, path)) { @@ -248,11 +256,12 @@ rawText = new RawText(inTree); break; case TRUE: - EolCanonicalizingInputStream in = new EolCanonicalizingInputStream( - new FileInputStream(inTree), true); - // Canonicalization should lead to same or shorter length - // (CRLF to LF), so the file size on disk is an upper size bound - rawText = new RawText(toByteArray(in, (int) inTree.length())); + try (AutoLFInputStream in = new AutoLFInputStream( + new FileInputStream(inTree), true)) { + // Canonicalization should lead to same or shorter length + // (CRLF to LF), so the file size on disk is an upper size bound + rawText = new RawText(toByteArray(in, (int) inTree.length())); + } break; default: throw new IllegalArgumentException( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,12 +43,16 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.errors.CheckoutConflictException; @@ -59,18 +63,23 @@ import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; @@ -84,7 +93,7 @@ /** * Checkout a branch to the working tree. *

- * Examples (git is a {@link Git} instance): + * Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Check out an existing branch: * @@ -119,9 +128,9 @@ * .setStartPoint("origin/stable").call(); * * - * @see Git documentation about Checkout + * @see Git + * documentation about Checkout */ public class CheckoutCommand extends GitCommand { @@ -173,27 +182,23 @@ private boolean checkoutAllPaths; + private Set actuallyModifiedPaths; + + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** + * Constructor for CheckoutCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected CheckoutCommand(Repository repo) { super(repo); - this.paths = new LinkedList(); + this.paths = new LinkedList<>(); } - /** - * @throws RefAlreadyExistsException - * when trying to create (without force) a branch with a name - * that already exists - * @throws RefNotFoundException - * if the start point or branch can not be found - * @throws InvalidRefNameException - * if the provided name is null or otherwise - * invalid - * @throws CheckoutConflictException - * if the checkout results in a conflict - * @return the newly created branch - */ + /** {@inheritDoc} */ + @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException, CheckoutConflictException { @@ -221,7 +226,13 @@ } } - Ref headRef = repo.getRef(Constants.HEAD); + Ref headRef = repo.exactRef(Constants.HEAD); + if (headRef == null) { + // TODO Git CLI supports checkout from unborn branch, we should + // also allow this + throw new UnsupportedOperationException( + JGitText.get().cannotCheckoutFromUnbornBranch); + } String shortHeadRef = getShortBranchName(headRef); String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$ ObjectId branch; @@ -234,7 +245,7 @@ JGitText.get().checkoutUnexpectedResult, r.name())); this.status = CheckoutResult.NOT_TRIED_RESULT; - return repo.getRef(Constants.HEAD); + return repo.exactRef(Constants.HEAD); } branch = getStartPointObjectId(); } else { @@ -259,6 +270,7 @@ dco = new DirCacheCheckout(repo, headTree, dc, newCommit.getTree()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); try { dco.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException e) { @@ -269,7 +281,7 @@ } finally { dc.unlock(); } - Ref ref = repo.getRef(name); + Ref ref = repo.findRef(name); if (ref != null && !ref.getName().startsWith(Constants.R_HEADS)) ref = null; String toName = Repository.shortenRefName(name); @@ -281,7 +293,7 @@ updateResult = refUpdate.link(ref.getName()); else if (orphan) { updateResult = refUpdate.link(getBranchName()); - ref = repo.getRef(Constants.HEAD); + ref = repo.exactRef(Constants.HEAD); } else { refUpdate.setNewObjectId(newCommit); updateResult = refUpdate.forceUpdate(); @@ -310,9 +322,11 @@ if (!dco.getToBeDeleted().isEmpty()) { status = new CheckoutResult(Status.NONDELETED, - dco.getToBeDeleted()); + dco.getToBeDeleted(), + new ArrayList<>(dco.getUpdated().keySet()), + dco.getRemoved()); } else - status = new CheckoutResult(new ArrayList(dco + status = new CheckoutResult(new ArrayList<>(dco .getUpdated().keySet()), dco.getRemoved()); return ref; @@ -325,9 +339,30 @@ } private String getShortBranchName(Ref headRef) { - if (headRef.getTarget().getName().equals(headRef.getName())) - return headRef.getTarget().getObjectId().getName(); - return Repository.shortenRefName(headRef.getTarget().getName()); + if (headRef.isSymbolic()) { + return Repository.shortenRefName(headRef.getTarget().getName()); + } + // Detached HEAD. Every non-symbolic ref in the ref database has an + // object id, so this cannot be null. + ObjectId id = headRef.getObjectId(); + if (id == null) { + throw new NullPointerException(); + } + return id.getName(); + } + + /** + * @param monitor + * a progress monitor + * @return this instance + * @since 4.11 + */ + public CheckoutCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; } /** @@ -350,6 +385,26 @@ } /** + * Add multiple slash-separated paths to the list of paths to check out. To + * check out all paths, use {@link #setAllPaths(boolean)}. + *

+ * If this option is set, neither the {@link #setCreateBranch(boolean)} nor + * {@link #setName(String)} option is considered. In other words, these + * options are exclusive. + * + * @param p + * paths to update in the working tree and index (with + * / as separator) + * @return {@code this} + * @since 4.6 + */ + public CheckoutCommand addPaths(List p) { + checkCallable(); + this.paths.addAll(p); + return this; + } + + /** * Set whether to checkout all paths. *

* This options should be used when you want to do a path checkout on the @@ -372,17 +427,21 @@ } /** - * Checkout paths into index and working directory + * Checkout paths into index and working directory, firing a + * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} if the working + * tree was modified. * * @return this instance - * @throws IOException - * @throws RefNotFoundException + * @throws java.io.IOException + * @throws org.eclipse.jgit.api.errors.RefNotFoundException */ protected CheckoutCommand checkoutPaths() throws IOException, RefNotFoundException { + actuallyModifiedPaths = new HashSet<>(); DirCache dc = repo.lockDirCache(); try (RevWalk revWalk = new RevWalk(repo); - TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader())) { + TreeWalk treeWalk = new TreeWalk(repo, + revWalk.getObjectReader())) { treeWalk.setRecursive(true); if (!checkoutAllPaths) treeWalk.setFilter(PathFilterGroup.createFromStrings(paths)); @@ -393,7 +452,16 @@ checkoutPathsFromCommit(treeWalk, dc, commit); } } finally { - dc.unlock(); + try { + dc.unlock(); + } finally { + WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent( + actuallyModifiedPaths, null); + actuallyModifiedPaths = null; + if (!event.isEmpty()) { + repo.fireEvent(event); + } + } } return this; } @@ -413,20 +481,30 @@ if (path.equals(previousPath)) continue; + final EolStreamType eolStreamType = treeWalk + .getEolStreamType(CHECKOUT_OP); + final String filterCommand = treeWalk + .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { int stage = ent.getStage(); if (stage > DirCacheEntry.STAGE_0) { if (checkoutStage != null) { - if (stage == checkoutStage.number) - checkoutPath(ent, r); + if (stage == checkoutStage.number) { + checkoutPath(ent, r, new CheckoutMetadata( + eolStreamType, filterCommand)); + actuallyModifiedPaths.add(path); + } } else { UnmergedPathException e = new UnmergedPathException( ent); throw new JGitInternalException(e.getMessage(), e); } } else { - checkoutPath(ent, r); + checkoutPath(ent, r, new CheckoutMetadata(eolStreamType, + filterCommand)); + actuallyModifiedPaths.add(path); } } }); @@ -444,20 +522,30 @@ while (treeWalk.next()) { final ObjectId blobId = treeWalk.getObjectId(0); final FileMode mode = treeWalk.getFileMode(0); - editor.add(new PathEdit(treeWalk.getPathString()) { + final EolStreamType eolStreamType = treeWalk + .getEolStreamType(CHECKOUT_OP); + final String filterCommand = treeWalk + .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); + final String path = treeWalk.getPathString(); + editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setObjectId(blobId); ent.setFileMode(mode); - checkoutPath(ent, r); + checkoutPath(ent, r, + new CheckoutMetadata(eolStreamType, filterCommand)); + actuallyModifiedPaths.add(path); } }); } editor.commit(); } - private void checkoutPath(DirCacheEntry entry, ObjectReader reader) { + private void checkoutPath(DirCacheEntry entry, ObjectReader reader, + CheckoutMetadata checkoutMetadata) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader); + DirCacheCheckout.checkoutEntry(repo, entry, reader, true, + checkoutMetadata); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, @@ -492,7 +580,7 @@ .get().branchNameInvalid, name == null ? "" : name)); //$NON-NLS-1$ if (orphan) { - Ref refToCheck = repo.getRef(getBranchName()); + Ref refToCheck = repo.exactRef(getBranchName()); if (refToCheck != null) throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().refAlreadyExists, name)); @@ -662,6 +750,8 @@ } /** + * Get the result, never null + * * @return the result, never null */ public CheckoutResult getResult() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,8 +46,7 @@ import java.util.List; /** - * Encapsulates the result of a {@link CheckoutCommand} - * + * Encapsulates the result of a {@link org.eclipse.jgit.api.CheckoutCommand} */ public class CheckoutResult { @@ -113,18 +112,40 @@ * {@link Status#CONFLICTS} or {@link Status#NONDELETED}. */ CheckoutResult(Status status, List fileList) { + this(status, fileList, null, null); + } + + /** + * Create a new fail result. If status is {@link Status#CONFLICTS}, + * fileList is a list of conflicting files, if status is + * {@link Status#NONDELETED}, fileList is a list of not deleted + * files. All other values ignore fileList. To create a result + * for {@link Status#OK}, see {@link #CheckoutResult(List, List)}. + * + * @param status + * the failure status + * @param fileList + * the list of files to store, status has to be either + * {@link Status#CONFLICTS} or {@link Status#NONDELETED}. + * @param modified + * the modified files + * @param removed + * the removed files. + */ + CheckoutResult(Status status, List fileList, List modified, + List removed) { myStatus = status; if (status == Status.CONFLICTS) this.conflictList = fileList; else - this.conflictList = new ArrayList(0); + this.conflictList = new ArrayList<>(0); if (status == Status.NONDELETED) this.undeletedList = fileList; else - this.undeletedList = new ArrayList(0); + this.undeletedList = new ArrayList<>(0); - this.modifiedList = new ArrayList(0); - this.removedList = new ArrayList(0); + this.modifiedList = modified; + this.removedList = removed; } /** @@ -138,14 +159,16 @@ CheckoutResult(List modified, List removed) { myStatus = Status.OK; - this.conflictList = new ArrayList(0); - this.undeletedList = new ArrayList(0); + this.conflictList = new ArrayList<>(0); + this.undeletedList = new ArrayList<>(0); this.modifiedList = modified; this.removedList = removed; } /** + * Get status + * * @return the status */ public Status getStatus() { @@ -153,33 +176,44 @@ } /** + * Get list of file that created a checkout conflict + * * @return the list of files that created a checkout conflict, or an empty - * list if {@link #getStatus()} is not {@link Status#CONFLICTS}; + * list if {@link #getStatus()} is not + * {@link org.eclipse.jgit.api.CheckoutResult.Status#CONFLICTS}; */ public List getConflictList() { return conflictList; } /** + * Get the list of files that could not be deleted during checkout + * * @return the list of files that could not be deleted during checkout, or * an empty list if {@link #getStatus()} is not - * {@link Status#NONDELETED}; + * {@link org.eclipse.jgit.api.CheckoutResult.Status#NONDELETED}; */ public List getUndeletedList() { return undeletedList; } /** + * Get the list of files that where modified during checkout + * * @return the list of files that where modified during checkout, or an - * empty list if {@link #getStatus()} is not {@link Status#OK} + * empty list if {@link #getStatus()} is not + * {@link org.eclipse.jgit.api.CheckoutResult.Status#OK} */ public List getModifiedList() { return modifiedList; } /** + * Get the list of files that where removed during checkout + * * @return the list of files that where removed during checkout, or an empty - * list if {@link #getStatus()} is not {@link Status#OK} + * list if {@link #getStatus()} is not + * {@link org.eclipse.jgit.api.CheckoutResult.Status#OK} */ public List getRemovedList() { return removedList; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,8 +60,10 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; @@ -85,7 +87,7 @@ public class CherryPickCommand extends GitCommand { private String reflogPrefix = "cherry-pick:"; //$NON-NLS-1$ - private List commits = new LinkedList(); + private List commits = new LinkedList<>(); private String ourCommitName = null; @@ -95,38 +97,38 @@ private boolean noCommit = false; + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** + * Constructor for CherryPickCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected CherryPickCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code Cherry-Pick} command with all the options and * parameters collected by the setter methods (e.g. {@link #include(Ref)} of * this class. Each instance of this class should only be used for one * invocation of the command. Don't call this method twice on an instance. - * - * @return the result of the cherry-pick - * @throws GitAPIException - * @throws WrongRepositoryStateException - * @throws ConcurrentRefUpdateException - * @throws UnmergedPathsException - * @throws NoMessageException - * @throws NoHeadException */ + @Override public CherryPickResult call() throws GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { RevCommit newHead = null; - List cherryPickedRefs = new LinkedList(); + List cherryPickedRefs = new LinkedList<>(); checkCallable(); try (RevWalk revWalk = new RevWalk(repo)) { // get the head commit - Ref headRef = repo.getRef(Constants.HEAD); + Ref headRef = repo.exactRef(Constants.HEAD); if (headRef == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); @@ -162,6 +164,7 @@ newHead.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); if (!noCommit) newHead = new Git(getRepository()).commit() @@ -223,6 +226,8 @@ } /** + * Include a reference to a commit + * * @param commit * a reference to a commit which is cherry-picked to the current * head @@ -235,6 +240,8 @@ } /** + * Include a commit + * * @param commit * the Id of a commit which is cherry-picked to the current head * @return {@code this} @@ -244,6 +251,8 @@ } /** + * Include a commit + * * @param name * a name given to the commit * @param commit @@ -256,6 +265,8 @@ } /** + * Set the name that should be used in the "OURS" place for conflict markers + * * @param ourCommitName * the name that should be used in the "OURS" place for conflict * markers @@ -283,6 +294,8 @@ } /** + * Set the {@code MergeStrategy} + * * @param strategy * The merge strategy to use during this Cherry-pick. * @return {@code this} @@ -294,6 +307,8 @@ } /** + * Set the (1-based) parent number to diff against + * * @param mainlineParentNumber * the (1-based) parent number to diff against. This allows * cherry-picking of merges. @@ -322,6 +337,24 @@ return this; } + /** + * The progress monitor associated with the cherry-pick operation. By + * default, this is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} + * @return {@code this} + * @since 4.11 + */ + public CherryPickCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } + private String calculateOurName(Ref headRef) { if (ourCommitName != null) return ourCommitName; @@ -330,4 +363,16 @@ String headName = Repository.shortenRefName(targetRefName); return headName; } + + /** {@inheritDoc} */ + @SuppressWarnings("nls") + @Override + public String toString() { + return "CherryPickCommand [repo=" + repo + ",\ncommits=" + commits + + ",\nmainlineParentNumber=" + mainlineParentNumber + + ", noCommit=" + noCommit + ", ourCommitName=" + ourCommitName + + ", reflogPrefix=" + reflogPrefix + ", strategy=" + strategy + + "]"; + } + } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,12 +46,11 @@ import java.util.Map; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; /** - * Encapsulates the result of a {@link CherryPickCommand}. + * Encapsulates the result of a {@link org.eclipse.jgit.api.CherryPickCommand}. */ public class CherryPickResult { @@ -91,6 +90,8 @@ private final Map failingPaths; /** + * Constructor for CherryPickResult + * * @param newHead * commit the head points at after this cherry-pick * @param cherryPickedRefs @@ -104,9 +105,12 @@ } /** + * Constructor for CherryPickResult + * * @param failingPaths * list of paths causing this cherry-pick to fail (see - * {@link ResolveMerger#getFailingPaths()} for details) + * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} + * for details) */ public CherryPickResult(Map failingPaths) { this.status = CherryPickStatus.FAILED; @@ -130,6 +134,8 @@ CherryPickStatus.CONFLICTING); /** + * Get status + * * @return the status this cherry-pick resulted in */ public CherryPickStatus getStatus() { @@ -137,28 +143,34 @@ } /** + * Get the new head after this cherry-pick + * * @return the commit the head points at after this cherry-pick, * null if {@link #getStatus} is not - * {@link CherryPickStatus#OK} + * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#OK} */ public RevCommit getNewHead() { return newHead; } /** + * Get the cherry-picked {@code Ref}s + * * @return the list of successfully cherry-picked Ref's, * null if {@link #getStatus} is not - * {@link CherryPickStatus#OK} + * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#OK} */ public List getCherryPickedRefs() { return cherryPickedRefs; } /** + * Get the list of paths causing this cherry-pick to fail + * * @return the list of paths causing this cherry-pick to fail (see - * {@link ResolveMerger#getFailingPaths()} for details), - * null if {@link #getStatus} is not - * {@link CherryPickStatus#FAILED} + * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} + * for details), null if {@link #getStatus} is not + * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#FAILED} */ public Map getFailingPaths() { return failingPaths; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.DOT_GIT; + import java.io.File; import java.io.IOException; import java.util.Collections; @@ -52,6 +54,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; @@ -73,77 +76,129 @@ private boolean ignore = true; + private boolean force = false; + /** + * Constructor for CleanCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected CleanCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code clean} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) - * - * @return a set of strings representing each file cleaned. - * @throws GitAPIException - * @throws NoWorkTreeException */ + @Override public Set call() throws NoWorkTreeException, GitAPIException { - Set files = new TreeSet(); + Set files = new TreeSet<>(); try { StatusCommand command = new StatusCommand(repo); Status status = command.call(); - Set untrackedAndIgnoredFiles = new TreeSet( - status.getUntracked()); - Set untrackedAndIgnoredDirs = new TreeSet( + Set untrackedFiles = new TreeSet<>(status.getUntracked()); + Set untrackedDirs = new TreeSet<>( status.getUntrackedFolders()); FS fs = getRepository().getFS(); for (String p : status.getIgnoredNotInIndex()) { File f = new File(repo.getWorkTree(), p); - if (fs.isFile(f) || fs.isSymLink(f)) - untrackedAndIgnoredFiles.add(p); - else if (fs.isDirectory(f)) - untrackedAndIgnoredDirs.add(p); + if (fs.isFile(f) || fs.isSymLink(f)) { + untrackedFiles.add(p); + } else if (fs.isDirectory(f)) { + untrackedDirs.add(p); + } } - Set filtered = filterFolders(untrackedAndIgnoredFiles, - untrackedAndIgnoredDirs); + Set filtered = filterFolders(untrackedFiles, untrackedDirs); Set notIgnoredFiles = filterIgnorePaths(filtered, status.getIgnoredNotInIndex(), true); - Set notIgnoredDirs = filterIgnorePaths( - untrackedAndIgnoredDirs, + Set notIgnoredDirs = filterIgnorePaths(untrackedDirs, status.getIgnoredNotInIndex(), false); for (String file : notIgnoredFiles) if (paths.isEmpty() || paths.contains(file)) { - if (!dryRun) - FileUtils.delete(new File(repo.getWorkTree(), file)); - files.add(file); + files = cleanPath(file, files); } - if (directories) - for (String dir : notIgnoredDirs) - if (paths.isEmpty() || paths.contains(dir)) { - if (!dryRun) - FileUtils.delete(new File(repo.getWorkTree(), dir), - FileUtils.RECURSIVE); - files.add(dir + "/"); //$NON-NLS-1$ - } + for (String dir : notIgnoredDirs) + if (paths.isEmpty() || paths.contains(dir)) { + files = cleanPath(dir, files); + } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); + } finally { + if (!files.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent(null, files)); + } } return files; } + /** + * When dryRun is false, deletes the specified path from disk. If dryRun + * is true, no paths are actually deleted. In both cases, the paths that + * would have been deleted are added to inFiles and returned. + * + * Paths that are directories are recursively deleted when + * {@link #directories} is true. + * Paths that are git repositories are recursively deleted when + * {@link #directories} and {@link #force} are both true. + * + * @param path + * The path to be cleaned + * @param inFiles + * A set of strings representing the files that have been cleaned + * already, the path to be cleaned will be added to this set + * before being returned. + * + * @return a set of strings with the cleaned path added to it + * @throws IOException + */ + private Set cleanPath(String path, Set inFiles) + throws IOException { + File curFile = new File(repo.getWorkTree(), path); + if (curFile.isDirectory()) { + if (directories) { + // Is this directory a git repository? + if (new File(curFile, DOT_GIT).exists()) { + if (force) { + if (!dryRun) { + FileUtils.delete(curFile, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING); + } + inFiles.add(path + "/"); //$NON-NLS-1$ + } + } else { + if (!dryRun) { + FileUtils.delete(curFile, + FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + } + inFiles.add(path + "/"); //$NON-NLS-1$ + } + } + } else { + if (!dryRun) { + FileUtils.delete(curFile, FileUtils.SKIP_MISSING); + } + inFiles.add(path); + } + + return inFiles; + } + private Set filterIgnorePaths(Set inputPaths, Set ignoredNotInIndex, boolean exact) { if (ignore) { - Set filtered = new TreeSet(inputPaths); + Set filtered = new TreeSet<>(inputPaths); for (String path : inputPaths) for (String ignored : ignoredNotInIndex) if ((exact && path.equals(ignored)) @@ -159,7 +214,7 @@ private Set filterFolders(Set untracked, Set untrackedFolders) { - Set filtered = new TreeSet(untracked); + Set filtered = new TreeSet<>(untracked); for (String file : untracked) for (String folder : untrackedFolders) if (file.startsWith(folder)) { @@ -195,6 +250,20 @@ return this; } + /** + * If force is set, directories that are git repositories will also be + * deleted. + * + * @param force + * whether or not to delete git repositories + * @return {@code this} + * @since 4.5 + */ + public CleanCommand setForce(boolean force) { + this.force = force; + return this; + } + /** * If dirs is set, in addition to files, also clean directories. * diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2013 Chris Aniszczyk + * Copyright (C) 2011, 2017 Chris Aniszczyk * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -50,6 +50,7 @@ import java.util.Collection; import java.util.List; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; @@ -58,9 +59,12 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -73,6 +77,8 @@ import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.FS; /** * Clone a repository into a new working directory @@ -90,6 +96,8 @@ private boolean bare; + private FS fs; + private String remote = Constants.DEFAULT_REMOTE_NAME; private String branch = Constants.HEAD; @@ -104,6 +112,46 @@ private Collection branchesToClone; + private Callback callback; + + private boolean directoryExistsInitially; + + private boolean gitDirExistsInitially; + + /** + * Callback for status of clone operation. + * + * @since 4.8 + */ + public interface Callback { + /** + * Notify initialized submodules. + * + * @param submodules + * the submodules + * + */ + void initializedSubmodules(Collection submodules); + + /** + * Notify starting to clone a submodule. + * + * @param path + * the submodule path + */ + void cloningSubmodule(String path); + + /** + * Notify checkout of commit + * + * @param commit + * the id of the commit being checked out + * @param path + * the submodule path + */ + void checkingOut(AnyObjectId commit, String path); + } + /** * Create clone command with no repository set */ @@ -112,6 +160,18 @@ } /** + * Get the git directory. This is primarily used for tests. + * + * @return the git directory + */ + @Nullable + File getDirectory() { + return directory; + } + + /** + * {@inheritDoc} + *

* Executes the {@code Clone} command. * * The Git instance returned by this command needs to be closed by the @@ -119,52 +179,98 @@ * instance. It is recommended to call this method as soon as you don't need * a reference to this {@link Git} instance and the underlying * {@link Repository} instance anymore. - * - * @return the newly created {@code Git} object with associated repository - * @throws InvalidRemoteException - * @throws org.eclipse.jgit.api.errors.TransportException - * @throws GitAPIException */ + @Override public Git call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { + URIish u = null; + try { + u = new URIish(uri); + verifyDirectories(u); + } catch (URISyntaxException e) { + throw new InvalidRemoteException( + MessageFormat.format(JGitText.get().invalidURL, uri)); + } Repository repository = null; + FetchResult fetchResult = null; + Thread cleanupHook = new Thread(() -> cleanup()); + Runtime.getRuntime().addShutdownHook(cleanupHook); try { - URIish u = new URIish(uri); - repository = init(u); - FetchResult result = fetch(repository, u); - if (!noCheckout) - checkout(repository, result); - return new Git(repository, true); + repository = init(); + fetchResult = fetch(repository, u); } catch (IOException ioe) { if (repository != null) { repository.close(); } + cleanup(); throw new JGitInternalException(ioe.getMessage(), ioe); } catch (URISyntaxException e) { if (repository != null) { repository.close(); } + cleanup(); throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); + } catch (GitAPIException | RuntimeException e) { + if (repository != null) { + repository.close(); + } + cleanup(); + throw e; + } finally { + Runtime.getRuntime().removeShutdownHook(cleanupHook); + } + if (!noCheckout) { + try { + checkout(repository, fetchResult); + } catch (IOException ioe) { + repository.close(); + throw new JGitInternalException(ioe.getMessage(), ioe); + } catch (GitAPIException | RuntimeException e) { + repository.close(); + throw e; + } } + return new Git(repository, true); } - private Repository init(URIish u) throws GitAPIException { - InitCommand command = Git.init(); - command.setBare(bare); - if (directory == null && gitDir == null) - directory = new File(u.getHumanishName(), Constants.DOT_GIT); - if (directory != null && directory.exists() - && directory.listFiles().length != 0) + private static boolean isNonEmptyDirectory(File dir) { + if (dir != null && dir.exists()) { + File[] files = dir.listFiles(); + return files != null && files.length != 0; + } + return false; + } + + void verifyDirectories(URIish u) { + if (directory == null && gitDir == null) { + directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : "")); //$NON-NLS-1$ + } + directoryExistsInitially = directory != null && directory.exists(); + gitDirExistsInitially = gitDir != null && gitDir.exists(); + validateDirs(directory, gitDir, bare); + if (isNonEmptyDirectory(directory)) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, directory.getName())); - if (gitDir != null && gitDir.exists() && gitDir.listFiles().length != 0) + } + if (isNonEmptyDirectory(gitDir)) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, gitDir.getName())); - if (directory != null) + } + } + + private Repository init() throws GitAPIException { + InitCommand command = Git.init(); + command.setBare(bare); + if (fs != null) { + command.setFs(fs); + } + if (directory != null) { command.setDirectory(directory); - if (gitDir != null) + } + if (gitDir != null) { command.setGitDir(gitDir); + } return command.call().getRepository(); } @@ -204,7 +310,7 @@ RefSpec wcrs = new RefSpec(); wcrs = wcrs.setForceUpdate(true); wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$ - List specs = new ArrayList(); + List specs = new ArrayList<>(); if (cloneAllBranches) specs.add(wcrs); else if (branchesToClone != null @@ -235,7 +341,7 @@ } if (head == null || head.getObjectId() == null) - return; // throw exception? + return; // TODO throw exception? if (head.getName().startsWith(Constants.R_HEADS)) { final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD); @@ -255,6 +361,7 @@ DirCache dc = clonedRepo.lockDirCache(); DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc, commit.getTree()); + co.setProgressMonitor(monitor); co.checkout(); if (cloneSubmodules) cloneSubmodules(clonedRepo); @@ -264,12 +371,18 @@ private void cloneSubmodules(Repository clonedRepo) throws IOException, GitAPIException { SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo); - if (init.call().isEmpty()) + Collection submodules = init.call(); + if (submodules.isEmpty()) { return; + } + if (callback != null) { + callback.initializedSubmodules(submodules); + } SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo); configure(update); update.setProgressMonitor(monitor); + update.setCallback(callback); if (!update.call().isEmpty()) { SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo); while (walk.next()) { @@ -287,20 +400,24 @@ private Ref findBranchToCheckout(FetchResult result) { final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD); - if (idHEAD == null) + ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null; + if (headId == null) { return null; + } Ref master = result.getAdvertisedRef(Constants.R_HEADS + Constants.MASTER); - if (master != null && master.getObjectId().equals(idHEAD.getObjectId())) + ObjectId objectId = master != null ? master.getObjectId() : null; + if (headId.equals(objectId)) { return master; + } Ref foundBranch = null; for (final Ref r : result.getAdvertisedRefs()) { final String n = r.getName(); if (!n.startsWith(Constants.R_HEADS)) continue; - if (r.getObjectId().equals(idHEAD.getObjectId())) { + if (headId.equals(r.getObjectId())) { foundBranch = r; break; } @@ -320,9 +437,9 @@ ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE); if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase) || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase)) - clonedRepo.getConfig().setBoolean( + clonedRepo.getConfig().setEnum( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, - ConfigConstants.CONFIG_KEY_REBASE, true); + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE); clonedRepo.getConfig().save(); } @@ -337,9 +454,11 @@ } /** + * Set the URI to clone from + * * @param uri - * the URI to clone from, or {@code null} to unset the URI. - * The URI must be set before {@link #call} is called. + * 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) { @@ -352,12 +471,11 @@ * directory isn't set, a name associated with the source uri will be used. * * @see URIish#getHumanishName() - * * @param directory * the directory to clone to, or {@code null} if the directory * name should be taken from the source uri * @return this instance - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -370,11 +488,13 @@ } /** + * Set the repository meta directory (.git) + * * @param gitDir * the repository meta directory, or {@code null} to choose one * automatically at clone time * @return this instance - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -388,10 +508,12 @@ } /** + * Set whether the cloned repository shall be bare + * * @param bare * whether the cloned repository is bare or not * @return this instance - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -404,6 +526,20 @@ } /** + * Set the file system abstraction to be used for repositories created by + * this command. + * + * @param fs + * the abstraction. + * @return {@code this} (for chaining calls). + * @since 4.10 + */ + public CloneCommand setFs(FS fs) { + this.fs = fs; + return this; + } + + /** * The remote name used to keep track of the upstream repository for the * clone operation. If no remote name is set, the default value of * Constants.DEFAULT_REMOTE_NAME will be used. @@ -423,13 +559,15 @@ } /** + * Set the initial branch + * * @param branch * the initial branch to check out when cloning the repository. * Can be specified as ref name (refs/heads/master), - * branch name (master) or tag name (v1.2.3). - * The default is to use the branch pointed to by the cloned - * repository's HEAD and can be requested by passing {@code null} - * or HEAD. + * branch name (master) or tag name + * (v1.2.3). The default is to use the branch + * pointed to by the cloned repository's HEAD and can be + * requested by passing {@code null} or HEAD. * @return this instance */ public CloneCommand setBranch(String branch) { @@ -445,8 +583,8 @@ * this is set to NullProgressMonitor * * @see NullProgressMonitor - * * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public CloneCommand setProgressMonitor(ProgressMonitor monitor) { @@ -458,6 +596,8 @@ } /** + * Set whether all branches have to be fetched + * * @param cloneAllBranches * true when all branches have to be fetched (indicates wildcard * in created fetch refspec), false otherwise. @@ -469,6 +609,8 @@ } /** + * Set whether to clone submodules + * * @param cloneSubmodules * true to initialize and update submodules. Ignored when * {@link #setBare(boolean)} is set to true. @@ -480,6 +622,8 @@ } /** + * Set branches to clone + * * @param branchesToClone * collection of branches to clone. Ignored when allSelected is * true. Must be specified as full ref names (e.g. @@ -492,6 +636,8 @@ } /** + * Set whether to skip checking out a branch + * * @param noCheckout * if set to true no branch will be checked out * after the clone. This enhances performance of the clone @@ -503,9 +649,31 @@ return this; } + /** + * Register a progress callback. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public CloneCommand setCallback(Callback callback) { + this.callback = callback; + return this; + } + private static void validateDirs(File directory, File gitDir, boolean bare) throws IllegalStateException { if (directory != null) { + if (directory.exists() && !directory.isDirectory()) { + throw new IllegalStateException(MessageFormat.format( + JGitText.get().initFailedDirIsNoDirectory, directory)); + } + if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) { + throw new IllegalStateException(MessageFormat.format( + JGitText.get().initFailedGitDirIsNoDirectory, + gitDir)); + } if (bare) { if (gitDir != null && !gitDir.equals(directory)) throw new IllegalStateException(MessageFormat.format( @@ -519,4 +687,39 @@ } } } + + private void cleanup() { + try { + if (directory != null) { + if (!directoryExistsInitially) { + FileUtils.delete(directory, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + if (gitDir != null) { + if (!gitDirExistsInitially) { + FileUtils.delete(gitDir, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + } catch (IOException e) { + // Ignore; this is a best-effort cleanup in error cases, and + // IOException should not be raised anyway + } + } + + private void deleteChildren(File file) throws IOException { + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File child : files) { + FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING + | FileUtils.IGNORE_ERRORS); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,11 +48,13 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; @@ -66,7 +68,10 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.hooks.CommitMsgHook; import org.eclipse.jgit.hooks.Hooks; +import org.eclipse.jgit.hooks.PostCommitHook; +import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; @@ -86,6 +91,7 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.util.ChangeIdUtil; /** @@ -106,7 +112,7 @@ private boolean all; - private List only = new ArrayList(); + private List only = new ArrayList<>(); private boolean[] onlyProcessed; @@ -118,46 +124,40 @@ * parents this commit should have. The current HEAD will be in this list * and also all commits mentioned in .git/MERGE_HEAD */ - private List parents = new LinkedList(); + private List parents = new LinkedList<>(); private String reflogComment; + private boolean useDefaultReflogMessage = true; + /** * Setting this option bypasses the pre-commit and commit-msg hooks. */ private boolean noVerify; - private PrintStream hookOutRedirect; + private HashMap hookOutRedirect = new HashMap<>(3); + + private Boolean allowEmpty; /** + * Constructor for CommitCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected CommitCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code commit} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) - * - * @return a {@link RevCommit} object representing the successful commit. - * @throws NoHeadException - * when called on a git repo without a HEAD reference - * @throws NoMessageException - * when called without specifying a commit message - * @throws UnmergedPathsException - * when the current index contained unmerged paths (conflicts) - * @throws ConcurrentRefUpdateException - * when HEAD or branch ref is updated concurrently by someone - * else - * @throws WrongRepositoryStateException - * when repository is not in the right state for committing - * @throws AbortedByHookException - * if there are either pre-commit or commit-msg hooks present in - * the repository and one of them rejects the commit. */ + @Override public RevCommit call() throws GitAPIException, NoHeadException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, @@ -173,12 +173,13 @@ state.name())); if (!noVerify) { - Hooks.preCommit(repo, hookOutRedirect).call(); + Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME)) + .call(); } processOptions(state, rw); - if (all && !repo.isBare() && repo.getWorkTree() != null) { + if (all && !repo.isBare()) { try (Git git = new Git(repo)) { git.add() .addFilepattern(".") //$NON-NLS-1$ @@ -189,7 +190,7 @@ } } - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); if (head == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); @@ -212,7 +213,9 @@ } if (!noVerify) { - message = Hooks.commitMsg(repo, hookOutRedirect) + message = Hooks + .commitMsg(repo, + hookOutRedirect.get(CommitMsgHook.NAME)) .setCommitMessage(message).call(); } @@ -230,6 +233,16 @@ if (insertChangeId) insertChangeId(indexTreeId); + // Check for empty commits + if (headId != null && !allowEmpty.booleanValue()) { + RevCommit headCommit = rw.parseCommit(headId); + headCommit.getTree(); + if (indexTreeId.equals(headCommit.getTree())) { + throw new EmtpyCommitException( + JGitText.get().emptyCommit); + } + } + // Create a Commit object, populate it and write it CommitBuilder commit = new CommitBuilder(); commit.setCommitter(committer); @@ -244,7 +257,7 @@ RevCommit revCommit = rw.parseCommit(commitId); RefUpdate ru = repo.updateRef(Constants.HEAD); ru.setNewObjectId(commitId); - if (reflogComment != null) { + if (!useDefaultReflogMessage) { ru.setRefLogMessage(reflogComment, false); } else { String prefix = amend ? "commit (amend): " //$NON-NLS-1$ @@ -276,6 +289,9 @@ repo.writeMergeCommitMsg(null); repo.writeRevertHead(null); } + Hooks.postCommit(repo, + hookOutRedirect.get(PostCommitHook.NAME)).call(); + return revCommit; } case REJECTED: @@ -298,7 +314,7 @@ } } - private void insertChangeId(ObjectId treeId) throws IOException { + private void insertChangeId(ObjectId treeId) { ObjectId firstParentId = null; if (!parents.isEmpty()) firstParentId = parents.get(0); @@ -328,9 +344,12 @@ boolean emptyCommit = true; try (TreeWalk treeWalk = new TreeWalk(repo)) { + treeWalk.setOperationType(OperationType.CHECKIN_OP); int dcIdx = treeWalk .addTree(new DirCacheBuildIterator(existingBuilder)); - int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); + FileTreeIterator fti = new FileTreeIterator(repo); + fti.setDirCacheIterator(treeWalk, 0); + int fIdx = treeWalk.addTree(fti); int hIdx = -1; if (headId != null) hIdx = treeWalk.addTree(rw.parseTree(headId)); @@ -390,14 +409,11 @@ inserter = repo.newObjectInserter(); long contentLength = fTree .getEntryContentLength(); - InputStream inputStream = fTree - .openEntryStream(); - try { + try (InputStream inputStream = fTree + .openEntryStream()) { dcEntry.setObjectId(inserter.insert( Constants.OBJ_BLOB, contentLength, inputStream)); - } finally { - inputStream.close(); } } } @@ -452,7 +468,9 @@ JGitText.get().entryNotFoundByPath, only.get(i))); // there must be at least one change - if (emptyCommit) + if (emptyCommit && !allowEmpty.booleanValue()) + // Would like to throw a EmptyCommitException. But this would break the API + // TODO(ch): Change this in the next release throw new JGitInternalException(JGitText.get().emptyCommit); // update index @@ -506,6 +524,12 @@ committer = new PersonIdent(repo); if (author == null && !amend) author = committer; + if (allowEmpty == null) + // JGit allows empty commits by default. Only when pathes are + // specified the commit should not be empty. This behaviour differs + // from native git but can only be adapted in the next release. + // TODO(ch) align the defaults with native git + allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE; // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files if (state == RepositoryState.MERGING_RESOLVED @@ -564,6 +588,8 @@ } /** + * Set the commit message + * * @param message * the commit message used for the {@code commit} * @return {@code this} @@ -575,6 +601,32 @@ } /** + * Set whether to allow to create an empty commit + * + * @param allowEmpty + * whether it should be allowed to create a commit which has the + * same tree as it's sole predecessor (a commit which doesn't + * change anything). By default when creating standard commits + * (without specifying paths) JGit allows to create such commits. + * When this flag is set to false an attempt to create an "empty" + * standard commit will lead to an EmptyCommitException. + *

+ * By default when creating a commit containing only specified + * paths an attempt to create an empty commit leads to a + * {@link org.eclipse.jgit.api.errors.JGitInternalException}. By + * setting this flag to true this exception will not + * be thrown. + * @return {@code this} + * @since 4.2 + */ + public CommitCommand setAllowEmpty(boolean allowEmpty) { + this.allowEmpty = Boolean.valueOf(allowEmpty); + return this; + } + + /** + * Get the commit message + * * @return the commit message used for the commit */ public String getMessage() { @@ -614,10 +666,12 @@ } /** + * Get the committer + * * @return the committer used for the {@code commit}. If no committer was * specified {@code null} is returned and the default - * {@link PersonIdent} of this repo is used during execution of the - * command + * {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used + * during execution of the command */ public PersonIdent getCommitter() { return committer; @@ -656,10 +710,12 @@ } /** + * Get the author + * * @return the author used for the {@code commit}. If no author was * specified {@code null} is returned and the default - * {@link PersonIdent} of this repo is used during execution of the - * command + * {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used + * during execution of the command */ public PersonIdent getAuthor() { return author; @@ -671,13 +727,15 @@ * not affected. This corresponds to the parameter -a on the command line. * * @param all + * whether to auto-stage all files that have been modified and + * deleted * @return {@code this} * @throws JGitInternalException * in case of an illegal combination of arguments/ options */ public CommitCommand setAll(boolean all) { checkCallable(); - if (!only.isEmpty()) + if (all && !only.isEmpty()) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$ "--only")); //$NON-NLS-1$ @@ -686,11 +744,12 @@ } /** - * Used to amend the tip of the current branch. If set to true, the previous - * commit will be amended. This is equivalent to --amend on the command - * line. + * Used to amend the tip of the current branch. If set to {@code true}, the + * previous commit will be amended. This is equivalent to --amend on the + * command line. * * @param amend + * whether to ammend the tip of the current branch * @return {@code this} */ public CommitCommand setAmend(boolean amend) { @@ -731,7 +790,7 @@ * will be replaced by the change id. * * @param insertChangeId - * + * whether to insert a change id * @return {@code this} */ public CommitCommand setInsertChangeId(boolean insertChangeId) { @@ -744,10 +803,13 @@ * Override the message written to the reflog * * @param reflogComment + * the comment to be written into the reflog or null + * to specify that no reflog should be written * @return {@code this} */ public CommitCommand setReflogComment(String reflogComment) { this.reflogComment = reflogComment; + useDefaultReflogMessage = false; return this; } @@ -771,8 +833,9 @@ } /** - * Set the output stream for hook scripts executed by this command. If not - * set it defaults to {@code System.out}. + * Set the output stream for all hook scripts executed by this command + * (pre-commit, commit-msg, post-commit). If not set it defaults to + * {@code System.out}. * * @param hookStdOut * the output stream for hook scripts executed by this command @@ -780,7 +843,34 @@ * @since 3.7 */ public CommitCommand setHookOutputStream(PrintStream hookStdOut) { - this.hookOutRedirect = hookStdOut; + setHookOutputStream(PreCommitHook.NAME, hookStdOut); + setHookOutputStream(CommitMsgHook.NAME, hookStdOut); + setHookOutputStream(PostCommitHook.NAME, hookStdOut); + return this; + } + + /** + * Set the output stream for a selected hook script executed by this command + * (pre-commit, commit-msg, post-commit). If not set it defaults to + * {@code System.out}. + * + * @param hookName + * name of the hook to set the output stream for + * @param hookStdOut + * the output stream to use for the selected hook + * @return {@code this} + * @since 4.5 + */ + public CommitCommand setHookOutputStream(String hookName, + PrintStream hookStdOut) { + if (!(PreCommitHook.NAME.equals(hookName) + || CommitMsgHook.NAME.equals(hookName) + || PostCommitHook.NAME.equals(hookName))) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().illegalHookName, + hookName)); + } + hookOutRedirect.put(hookName, hookStdOut); return this; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -103,29 +103,23 @@ } /** + * Constructor for CreateBranchCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected CreateBranchCommand(Repository repo) { super(repo); } - /** - * @throws RefAlreadyExistsException - * when trying to create (without force) a branch with a name - * that already exists - * @throws RefNotFoundException - * if the start point can not be found - * @throws InvalidRefNameException - * if the provided name is null or otherwise - * invalid - * @return the newly created branch - */ + /** {@inheritDoc} */ + @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException { checkCallable(); processOptions(); try (RevWalk revWalk = new RevWalk(repo)) { - Ref refToCheck = repo.getRef(name); + Ref refToCheck = repo.findRef(name); boolean exists = refToCheck != null && refToCheck.getName().startsWith(Constants.R_HEADS); if (!force && exists) @@ -135,7 +129,7 @@ ObjectId startAt = getStartPointObjectId(); String startPointFullName = null; if (startPoint != null) { - Ref baseRef = repo.getRef(startPoint); + Ref baseRef = repo.findRef(startPoint); if (baseRef != null) startPointFullName = baseRef.getName(); } @@ -207,7 +201,7 @@ .get().createBranchUnexpectedResult, updateResult .name())); - Ref result = repo.getRef(name); + Ref result = repo.findRef(name); if (result == null) throw new JGitInternalException( JGitText.get().createBranchFailedUnknownReason); @@ -296,6 +290,8 @@ } /** + * Set the name of the new branch + * * @param name * the name of the new branch * @return this instance @@ -307,6 +303,8 @@ } /** + * Set whether to create the branch forcefully + * * @param force * if true and the branch with the given name * already exists, the start-point of an existing branch will be @@ -321,6 +319,8 @@ } /** + * Set the start point + * * @param startPoint * corresponds to the start-point option; if null, * the current HEAD will be used @@ -334,6 +334,8 @@ } /** + * Set the start point + * * @param startPoint * corresponds to the start-point option; if null, * the current HEAD will be used @@ -347,6 +349,8 @@ } /** + * Set the upstream mode + * * @param mode * corresponds to the --track/--no-track/--set-upstream options; * may be null diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -79,28 +79,26 @@ * >Git documentation about Branch */ public class DeleteBranchCommand extends GitCommand> { - private final Set branchNames = new HashSet(); + private final Set branchNames = new HashSet<>(); private boolean force; /** + * Constructor for DeleteBranchCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected DeleteBranchCommand(Repository repo) { super(repo); } - /** - * @throws NotMergedException - * when trying to delete a branch which has not been merged into - * the currently checked out branch without force - * @throws CannotDeleteCurrentBranchException - * @return the list with the (full) names of the deleted branches - */ + /** {@inheritDoc} */ + @Override public List call() throws GitAPIException, NotMergedException, CannotDeleteCurrentBranchException { checkCallable(); - List result = new ArrayList(); + List result = new ArrayList<>(); if (branchNames.isEmpty()) return result; try { @@ -114,7 +112,7 @@ for (String branchName : branchNames) { if (branchName == null) continue; - Ref currentRef = repo.getRef(branchName); + Ref currentRef = repo.findRef(branchName); if (currentRef == null) continue; @@ -130,7 +128,7 @@ for (String branchName : branchNames) { if (branchName == null) continue; - Ref currentRef = repo.getRef(branchName); + Ref currentRef = repo.findRef(branchName); if (currentRef == null) continue; String fullName = currentRef.getName(); @@ -180,6 +178,8 @@ } /** + * Set the names of the branches to delete + * * @param branchnames * the names of the branches to delete; if not set, this will do * nothing; invalid branch names will simply be ignored @@ -194,6 +194,8 @@ } /** + * Set whether to forcefully delete branches + * * @param force * true corresponds to the -D option, * false to the -d option (default)
diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,21 +68,23 @@ */ public class DeleteTagCommand extends GitCommand> { - private final Set tags = new HashSet(); + private final Set tags = new HashSet<>(); /** + * Constructor for DeleteTagCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected DeleteTagCommand(Repository repo) { super(repo); } - /** - * @return the list with the full names of the deleted tags - */ + /** {@inheritDoc} */ + @Override public List call() throws GitAPIException { checkCallable(); - List result = new ArrayList(); + List result = new ArrayList<>(); if (tags.isEmpty()) return result; try { @@ -90,7 +92,7 @@ for (String tagName : tags) { if (tagName == null) continue; - Ref currentRef = repo.getRef(tagName); + Ref currentRef = repo.findRef(tagName); if (currentRef == null) continue; String fullName = currentRef.getName(); @@ -123,6 +125,8 @@ } /** + * Set names of the tags to delete + * * @param tags * the names of the tags to delete; if not set, this will do * nothing; invalid tag names will simply be ignored diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,17 +47,24 @@ import java.io.IOException; 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.Date; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.ignore.internal.IMatcher; +import org.eclipse.jgit.ignore.internal.PathMatcher; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -66,6 +73,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevFlagSet; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; /** @@ -94,8 +102,15 @@ private boolean longDesc; /** + * Pattern matchers to be applied to tags under consideration + */ + private List matchers = new ArrayList<>(); + + /** + * Constructor for DescribeCommand. * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected DescribeCommand(Repository repo) { super(repo); @@ -113,7 +128,7 @@ * the supplied commit does not exist. * @throws IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public DescribeCommand setTarget(ObjectId target) throws IOException { @@ -125,14 +140,15 @@ * Sets the commit to be described. * * @param rev - * Commit ID, tag, branch, ref, etc. - * See {@link Repository#resolve(String)} for allowed syntax. + * Commit ID, tag, branch, ref, etc. See + * {@link org.eclipse.jgit.lib.Repository#resolve(String)} for + * allowed syntax. * @return {@code this} * @throws IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. - * @throws RefNotFoundException - * the given rev didn't resolve to any object. - * @throws IOException + * @throws org.eclipse.jgit.api.errors.RefNotFoundException + * the given rev didn't resolve to any object. + * @throws java.io.IOException * a pack file or loose object could not be read. */ public DescribeCommand setTarget(String rev) throws IOException, @@ -150,7 +166,6 @@ * @param longDesc * true if always the long format should be used. * @return {@code this} - * * @see Git documentation about describe @@ -170,16 +185,77 @@ } /** + * Sets one or more {@code glob(7)} patterns that tags must match to be + * considered. If multiple patterns are provided, tags only need match one + * of them. + * + * @param patterns + * the {@code glob(7)} pattern or patterns + * @return {@code this} + * @throws org.eclipse.jgit.errors.InvalidPatternException + * if the pattern passed in was invalid. + * @see Git documentation about describe + * @since 4.9 + */ + public DescribeCommand setMatch(String... patterns) throws InvalidPatternException { + for (String p : patterns) { + matchers.add(PathMatcher.createPathMatcher(p, null, false)); + } + return this; + } + + private final Comparator TAG_TIE_BREAKER = new Comparator() { + + @Override + public int compare(Ref o1, Ref o2) { + try { + return tagDate(o2).compareTo(tagDate(o1)); + } catch (IOException e) { + return 0; + } + } + + private Date tagDate(Ref tag) throws IOException { + RevTag t = w.parseTag(tag.getObjectId()); + w.parseBody(t); + return t.getTaggerIdent().getWhen(); + } + }; + + private Optional getBestMatch(List tags) { + if (tags == null || tags.size() == 0) { + return Optional.empty(); + } else if (matchers.size() == 0) { + Collections.sort(tags, TAG_TIE_BREAKER); + return Optional.of(tags.get(0)); + } else { + // Find the first tag that matches in the stream of all tags + // filtered by matchers ordered by tie break order + Stream matchingTags = Stream.empty(); + for (IMatcher matcher : matchers) { + Stream m = tags.stream().filter( + tag -> matcher.matches(tag.getName(), false, false)); + matchingTags = Stream.of(matchingTags, m).flatMap(i -> i); + } + return matchingTags.sorted(TAG_TIE_BREAKER).findFirst(); + } + } + + private ObjectId getObjectIdFromRef(Ref r) { + ObjectId key = repo.peel(r).getPeeledObjectId(); + if (key == null) { + key = r.getObjectId(); + } + return key; + } + + /** + * {@inheritDoc} + *

* Describes the specified commit. Target defaults to HEAD if no commit was * set explicitly. - * - * @return if there's a tag that points to the commit being described, this - * tag name is returned. Otherwise additional suffix is added to the - * nearest tag, just like git-describe(1). - *

- * If none of the ancestors of the commit being described has any - * tags at all, then this method returns null, indicating that - * there's no way to describe this tag. */ @Override public String call() throws GitAPIException { @@ -189,14 +265,9 @@ if (target == null) setTarget(Constants.HEAD); - Map tags = new HashMap(); - - for (Ref r : repo.getRefDatabase().getRefs(R_TAGS).values()) { - ObjectId key = repo.peel(r).getPeeledObjectId(); - if (key == null) - key = r.getObjectId(); - tags.put(key, r); - } + Collection tagList = repo.getRefDatabase().getRefs(R_TAGS).values(); + Map> tags = tagList.stream() + .collect(Collectors.groupingBy(this::getObjectIdFromRef)); // combined flags of all the candidate instances final RevFlagSet allFlags = new RevFlagSet(); @@ -240,13 +311,13 @@ } } - List candidates = new ArrayList(); // all the candidates we find + List candidates = new ArrayList<>(); // all the candidates we find - // is the target already pointing to a tag? if so, we are done! - Ref lucky = tags.get(target); - if (lucky != null) { - return longDesc ? longDescription(lucky, 0, target) : lucky - .getName().substring(R_TAGS.length()); + // is the target already pointing to a suitable tag? if so, we are done! + Optional bestMatch = getBestMatch(tags.get(target)); + if (bestMatch.isPresent()) { + return longDesc ? longDescription(bestMatch.get(), 0, target) : + bestMatch.get().getName().substring(R_TAGS.length()); } w.markStart(target); @@ -258,9 +329,9 @@ // if a tag already dominates this commit, // then there's no point in picking a tag on this commit // since the one that dominates it is always more preferable - Ref t = tags.get(c); - if (t != null) { - Candidate cd = new Candidate(c, t); + bestMatch = getBestMatch(tags.get(c)); + if (bestMatch.isPresent()) { + Candidate cd = new Candidate(c, bestMatch.get()); candidates.add(cd); cd.depth = seen; } @@ -305,6 +376,7 @@ return null; Candidate best = Collections.min(candidates, new Comparator() { + @Override public int compare(Candidate o1, Candidate o2) { return o1.depth - o2.depth; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -95,29 +95,34 @@ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** + * Constructor for DiffCommand + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected DiffCommand(Repository repo) { super(repo); } + private DiffFormatter getDiffFormatter() { + return out != null && !showNameAndStatusOnly + ? new DiffFormatter(new BufferedOutputStream(out)) + : new DiffFormatter(NullOutputStream.INSTANCE); + } + /** + * {@inheritDoc} + *

* Executes the {@code Diff} command with all the options and parameters * collected by the setter methods (e.g. {@link #setCached(boolean)} of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. - * - * @return a DiffEntry for each path which is different */ + @Override public List call() throws GitAPIException { - final DiffFormatter diffFmt; - if (out != null && !showNameAndStatusOnly) - diffFmt = new DiffFormatter(new BufferedOutputStream(out)); - else - diffFmt = new DiffFormatter(NullOutputStream.INSTANCE); - diffFmt.setRepository(repo); - diffFmt.setProgressMonitor(monitor); - try { + try (DiffFormatter diffFmt = getDiffFormatter()) { + diffFmt.setRepository(repo); + diffFmt.setProgressMonitor(monitor); if (cached) { if (oldTree == null) { ObjectId head = repo.resolve(HEAD + "^{tree}"); //$NON-NLS-1$ @@ -155,15 +160,14 @@ } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); - } finally { - diffFmt.close(); } } /** + * Whether to view the changes staged for the next commit * * @param cached - * whether to view the changes you staged for the next commit + * whether to view the changes staged for the next commit * @return this instance */ public DiffCommand setCached(boolean cached) { @@ -172,6 +176,8 @@ } /** + * Set path filter + * * @param pathFilter * parameter, used to limit the diff to the named path * @return this instance @@ -182,6 +188,8 @@ } /** + * Set old tree + * * @param oldTree * the previous state * @return this instance @@ -192,6 +200,8 @@ } /** + * Set new tree + * * @param newTree * the updated state * @return this instance @@ -202,6 +212,8 @@ } /** + * Set whether to return only names and status of changed files + * * @param showNameAndStatusOnly * whether to return only names and status of changed files * @return this instance @@ -212,6 +224,8 @@ } /** + * Set output stream + * * @param out * the stream to write line data * @return this instance @@ -262,7 +276,6 @@ * is set to NullProgressMonitor * * @see NullProgressMonitor - * * @param monitor * a progress monitor * @return this instance diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,6 +67,8 @@ private final int returnCode; /** + * Constructor for AbortedByHookException + * * @param message * The error details. * @param hookName @@ -83,6 +85,8 @@ } /** + * Get hook name + * * @return the type of the hook that interrupted the git command. */ public String getHookName() { @@ -90,12 +94,15 @@ } /** + * Get return code + * * @return the hook process result. */ public int getReturnCode() { return returnCode; } + /** {@inheritDoc} */ @Override public String getMessage() { return MessageFormat.format(JGitText.get().commandRejectedByHook, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CanceledException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CanceledException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CanceledException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CanceledException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ private static final long serialVersionUID = 1L; /** - * @param message + *

Constructor for CanceledException.

+ * + * @param message a {@link java.lang.String} object. */ public CanceledException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for CannotDeleteCurrentBranchException + * * @param message - * the message + * error message */ public CannotDeleteCurrentBranchException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,6 @@ * * @param conflictingPaths * list of conflicting paths - * * @param e * a {@link org.eclipse.jgit.errors.CheckoutConflictException} * exception @@ -82,7 +81,11 @@ this.conflictingPaths = conflictingPaths; } - /** @return all the paths where unresolved conflicts have been detected */ + /** + * Get conflicting paths + * + * @return all the paths where unresolved conflicts have been detected + */ public List getConflictingPaths() { return conflictingPaths; } @@ -95,7 +98,7 @@ */ CheckoutConflictException addConflictingPath(String conflictingPath) { if (conflictingPaths == null) - conflictingPaths = new LinkedList(); + conflictingPaths = new LinkedList<>(); conflictingPaths.add(conflictingPath); return this; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/ConcurrentRefUpdateException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/ConcurrentRefUpdateException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/ConcurrentRefUpdateException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/ConcurrentRefUpdateException.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,10 +55,16 @@ private Ref ref; /** + * Constructor for ConcurrentRefUpdateException. + * * @param message + * error message * @param ref + * a {@link org.eclipse.jgit.lib.Ref} * @param rc + * a {@link org.eclipse.jgit.lib.RefUpdate.Result} * @param cause + * a {@link java.lang.Throwable} */ public ConcurrentRefUpdateException(String message, Ref ref, RefUpdate.Result rc, Throwable cause) { @@ -69,9 +75,14 @@ } /** + * Constructor for ConcurrentRefUpdateException. + * * @param message + * error message * @param ref + * a {@link org.eclipse.jgit.lib.Ref} * @param rc + * a {@link org.eclipse.jgit.lib.RefUpdate.Result} */ public ConcurrentRefUpdateException(String message, Ref ref, RefUpdate.Result rc) { @@ -82,15 +93,21 @@ } /** - * @return the {@link Ref} which was tried to by updated + * Get Ref + * + * @return the {@link org.eclipse.jgit.lib.Ref} which was tried to by + * updated */ public Ref getRef() { return ref; } /** - * @return the result which was returned by {@link RefUpdate#update()} and - * which caused this error + * Get result + * + * @return the result which was returned by + * {@link org.eclipse.jgit.lib.RefUpdate#update()} and which caused + * this error */ public RefUpdate.Result getResult() { return rc; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/DetachedHeadException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/DetachedHeadException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/DetachedHeadException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/DetachedHeadException.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,15 +55,22 @@ } /** + * Constructor for DetachedHeadException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} object. */ public DetachedHeadException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for DetachedHeadException. + * * @param message + * error message */ public DetachedHeadException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api.errors; + +/** + * Exception thrown when a newly created commit does not contain any changes + * + * @since 4.2 + */ +public class EmtpyCommitException extends GitAPIException { // TODO: Correct spelling of this class name in 5.0 + private static final long serialVersionUID = 1L; + + /** + * Constructor for EmtpyCommitException + * + * @param message + * error message + * @param cause + * a {@link java.lang.Throwable} + */ + public EmtpyCommitException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructor for EmtpyCommitException. + * + * @param message + * error message + */ + public EmtpyCommitException(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015, Christian Halstrick and + * other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.api.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Exception thrown when the execution of a filter command failed + * + * @since 4.2 + */ +public class FilterFailedException extends GitAPIException { + private static final long serialVersionUID = 1L; + + private String filterCommand; + + private String path; + + private byte[] stdout; + + private String stderr; + + private int rc; + + /** + * Thrown if during execution of filter command an exception occurred + * + * @param cause + * the exception + * @param filterCommand + * the command which failed + * @param path + * the path processed by the filter + */ + public FilterFailedException(Exception cause, String filterCommand, + String path) { + super(MessageFormat.format(JGitText.get().filterExecutionFailed, + filterCommand, path), cause); + this.filterCommand = filterCommand; + this.path = path; + } + + /** + * Thrown if a filter command returns a non-zero return code + * + * @param rc + * the return code + * @param filterCommand + * the command which failed + * @param path + * the path processed by the filter + * @param stdout + * the output the filter generated so far. This should be limited + * to reasonable size. + * @param stderr + * the stderr output of the filter + */ + @SuppressWarnings("boxing") + public FilterFailedException(int rc, String filterCommand, String path, + byte[] stdout, String stderr) { + super(MessageFormat.format(JGitText.get().filterExecutionFailedRc, + filterCommand, path, rc, stderr)); + this.rc = rc; + this.filterCommand = filterCommand; + this.path = path; + this.stdout = stdout; + this.stderr = stderr; + } + + /** + * Get filter command + * + * @return the filterCommand + */ + public String getFilterCommand() { + return filterCommand; + } + + /** + * Get path + * + * @return the path of the file processed by the filter command + */ + public String getPath() { + return path; + } + + /** + * Get output + * + * @return the output generated by the filter command. Might be truncated to + * limit memory consumption. + */ + public byte[] getOutput() { + return stdout; + } + + /** + * Get error + * + * @return the error output returned by the filter command + */ + public String getError() { + return stderr; + } + + /** + * Get return code + * + * @return the return code returned by the filter command + */ + public int getReturnCode() { + return rc; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/GitAPIException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/GitAPIException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/GitAPIException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/GitAPIException.java 2019-09-03 12:37:49.000000000 +0000 @@ -40,7 +40,6 @@ /** * Superclass of all exceptions thrown by the API classes in * {@code org.eclipse.jgit.api} - * */ public abstract class GitAPIException extends Exception { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidConfigurationException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidConfigurationException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidConfigurationException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidConfigurationException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,15 +45,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for InvalidConfigurationException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public InvalidConfigurationException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for InvalidConfigurationException. + * * @param message + * error message */ public InvalidConfigurationException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidMergeHeadsException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidMergeHeadsException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidMergeHeadsException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidMergeHeadsException.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for InvalidMergeHeadsException. + * * @param msg + * error message */ public InvalidMergeHeadsException(String msg) { super(msg); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRebaseStepException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRebaseStepException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRebaseStepException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRebaseStepException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,16 +45,24 @@ */ public class InvalidRebaseStepException extends GitAPIException { private static final long serialVersionUID = 1L; + /** + * Constructor for InvalidRebaseStepException. + * * @param msg + * error message */ public InvalidRebaseStepException(String msg) { super(msg); } /** + * Constructor for InvalidRebaseStepException. + * * @param msg + * error message * @param cause + * a {@link java.lang.Throwable} */ public InvalidRebaseStepException(String msg, Throwable cause) { super(msg, cause); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,15 +44,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for InvalidRefNameException + * * @param msg + * error message */ public InvalidRefNameException(String msg) { super(msg); } /** + * Constructor for InvalidRefNameException. + * * @param msg + * error message * @param cause + * a {@link java.lang.Throwable} */ public InvalidRefNameException(String msg, Throwable cause) { super(msg, cause); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRemoteException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRemoteException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRemoteException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRemoteException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,15 +44,22 @@ private static final long serialVersionUID = 1L; /** - * @param msg message describing the invalid remote. + * Constructor for InvalidRemoteException + * + * @param msg + * message describing the invalid remote. */ public InvalidRemoteException(String msg) { super(msg); } /** - * @param msg message describing the invalid remote. - * @param cause why the remote is invalid. + * Constructor for InvalidRemoteException + * + * @param msg + * message describing the invalid remote. + * @param cause + * why the remote is invalid. */ public InvalidRemoteException(String msg, Throwable cause) { super(msg, cause); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidTagNameException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidTagNameException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidTagNameException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidTagNameException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for InvalidTagNameException. + * * @param msg + * error message */ public InvalidTagNameException(String msg) { super(msg); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,7 +63,9 @@ * Construct an exception for low-level internal exceptions * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public JGitInternalException(String message, Throwable cause) { super(message, cause); @@ -73,6 +75,7 @@ * Construct an exception for low-level internal exceptions * * @param message + * error message */ public JGitInternalException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/MultipleParentsNotAllowedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/MultipleParentsNotAllowedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/MultipleParentsNotAllowedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/MultipleParentsNotAllowedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,15 +49,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for MultipleParentsNotAllowedException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public MultipleParentsNotAllowedException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for MultipleParentsNotAllowedException. + * * @param message + * error message */ public MultipleParentsNotAllowedException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoFilepatternException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoFilepatternException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoFilepatternException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoFilepatternException.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,15 +48,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for NoFilepatternException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public NoFilepatternException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for NoFilepatternException. + * * @param message + * error message */ public NoFilepatternException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoHeadException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoHeadException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoHeadException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoHeadException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,15 +45,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for NoHeadException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public NoHeadException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for NoHeadException + * * @param message + * error message */ public NoHeadException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoMessageException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoMessageException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoMessageException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NoMessageException.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,15 +47,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for NoMessageException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public NoMessageException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for NoMessageException + * * @param message + * error message */ public NoMessageException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,23 +44,29 @@ /** * Exception thrown when applying a patch fails - * + * * @since 2.0 - * */ public class PatchApplyException extends GitAPIException { private static final long serialVersionUID = 1L; /** + * Constructor for PatchApplyException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public PatchApplyException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for PatchApplyException + * * @param message + * error message */ public PatchApplyException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,9 +50,8 @@ /** * Exception thrown when applying a patch fails due to an invalid format - * + * * @since 2.0 - * */ public class PatchFormatException extends GitAPIException { private static final long serialVersionUID = 1L; @@ -60,7 +59,10 @@ private List errors; /** + * Constructor for PatchFormatException + * * @param errors + * a {@link java.util.List} of {@link FormatError}s */ public PatchFormatException(List errors) { super(MessageFormat.format(JGitText.get().patchFormatException, errors)); @@ -68,6 +70,8 @@ } /** + * Get list of errors + * * @return all the errors where unresolved conflicts have been detected */ public List getErrors() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java 2019-09-03 12:37:49.000000000 +0000 @@ -37,17 +37,18 @@ */ package org.eclipse.jgit.api.errors; -import org.eclipse.jgit.lib.Ref; - /** - * Thrown when trying to create a {@link Ref} with the same name as an existing - * one + * Thrown when trying to create a {@link org.eclipse.jgit.lib.Ref} with the same + * name as an existing one */ public class RefAlreadyExistsException extends GitAPIException { private static final long serialVersionUID = 1L; /** + * Constructor for RefAlreadyExistsException + * * @param message + * error message */ public RefAlreadyExistsException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotAdvertisedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotAdvertisedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotAdvertisedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotAdvertisedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for RefNotAdvertisedException + * * @param message + * error message */ public RefNotAdvertisedException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,12 @@ private static final long serialVersionUID = 1L; /** + * Constructor for RefNotFoundException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} * @since 4.1 */ public RefNotFoundException(String message, Throwable cause) { @@ -53,7 +57,10 @@ } /** + * Constructor for RefNotFoundException + * * @param message + * error message */ public RefNotFoundException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,7 +1,5 @@ package org.eclipse.jgit.api.errors; -import org.eclipse.jgit.api.errors.GitAPIException; - /** * Thrown from StashApplyCommand when stash apply fails */ @@ -10,8 +8,12 @@ private static final long serialVersionUID = 1L; /** + * Constructor for StashApplyFailureException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} * @since 4.1 */ public StashApplyFailureException(String message, Throwable cause) { @@ -22,6 +24,7 @@ * Create a StashApplyFailedException * * @param message + * error message */ public StashApplyFailureException(final String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016, Matthias Sohn and + * other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api.errors; + +/** + * Exception thrown when PackParser finds an object larger than a predefined + * limit + * + * @since 4.4 + */ +public class TooLargeObjectInPackException extends TransportException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for TooLargeObjectInPackException + * + * @param msg + * message describing the transport failure. + */ + public TooLargeObjectInPackException(String msg) { + super(msg); + } + + /** + * Constructor for TooLargeObjectInPackException + * + * @param msg + * message describing the transport exception. + * @param cause + * why the transport failed. + */ + public TooLargeObjectInPackException(String msg, Throwable cause) { + super(msg, cause); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargePackException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargePackException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargePackException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargePackException.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,8 @@ private static final long serialVersionUID = 1L; /** + * Constructor for TooLargePackException + * * @param msg * message describing the transport failure. */ @@ -54,6 +56,8 @@ } /** + * Constructor for TooLargePackException + * * @param msg * message describing the transport exception. * @param cause diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TransportException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TransportException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TransportException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TransportException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,8 @@ private static final long serialVersionUID = 1L; /** + * Constructor for TransportException + * * @param msg * message describing the transport failure. */ @@ -52,6 +54,8 @@ } /** + * Constructor for TransportException + * * @param msg * message describing the transport exception. * @param cause diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,8 +63,12 @@ } /** + * Constructor for UnmergedPathsException + * * @param message + * the message * @param cause + * a {@link java.lang.Throwable} * @since 4.1 */ public UnmergedPathsException(String message, Throwable cause) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongRepositoryStateException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongRepositoryStateException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongRepositoryStateException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongRepositoryStateException.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,15 +46,22 @@ private static final long serialVersionUID = 1L; /** + * Constructor for WrongRepositoryStateException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} */ public WrongRepositoryStateException(String message, Throwable cause) { super(message, cause); } /** + * Constructor for WrongRepositoryStateException. + * * @param message + * error message */ public WrongRepositoryStateException(String message) { super(message); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,14 +42,21 @@ */ package org.eclipse.jgit.api; +import static java.util.stream.Collectors.toList; + +import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; @@ -57,9 +64,13 @@ import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.TagOpt; @@ -74,7 +85,6 @@ * >Git documentation about Fetch */ public class FetchCommand extends TransportCommand { - private String remote = Constants.DEFAULT_REMOTE_NAME; private List refSpecs; @@ -91,47 +101,149 @@ private TagOpt tagOption; + private FetchRecurseSubmodulesMode submoduleRecurseMode = null; + + private Callback callback; + + /** + * Callback for status of fetch operation. + * + * @since 4.8 + * + */ + public interface Callback { + /** + * Notify fetching a submodule. + * + * @param name + * the submodule name. + */ + void fetchingSubmodule(String name); + } + /** + * Constructor for FetchCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected FetchCommand(Repository repo) { super(repo); - refSpecs = new ArrayList(3); + refSpecs = new ArrayList<>(3); + } + + private FetchRecurseSubmodulesMode getRecurseMode(String path) { + // Use the caller-specified mode, if set + if (submoduleRecurseMode != null) { + return submoduleRecurseMode; + } + + // Fall back to submodule.name.fetchRecurseSubmodules, if set + FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum( + FetchRecurseSubmodulesMode.values(), + ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null); + if (mode != null) { + return mode; + } + + // Fall back to fetch.recurseSubmodules, if set + mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(), + ConfigConstants.CONFIG_FETCH_SECTION, null, + ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null); + if (mode != null) { + return mode; + } + + // Default to on-demand mode + return FetchRecurseSubmodulesMode.ON_DEMAND; + } + + private void fetchSubmodules(FetchResult results) + throws org.eclipse.jgit.api.errors.TransportException, + GitAPIException, InvalidConfigurationException { + try (SubmoduleWalk walk = new SubmoduleWalk(repo); + RevWalk revWalk = new RevWalk(repo)) { + // Walk over submodules in the parent repository's FETCH_HEAD. + ObjectId fetchHead = repo.resolve(Constants.FETCH_HEAD); + if (fetchHead == null) { + return; + } + walk.setTree(revWalk.parseTree(fetchHead)); + while (walk.next()) { + try (Repository submoduleRepo = walk.getRepository()) { + + // Skip submodules that don't exist locally (have not been + // cloned), are not registered in the .gitmodules file, or + // not registered in the parent repository's config. + if (submoduleRepo == null || walk.getModulesPath() == null + || walk.getConfigUrl() == null) { + continue; + } + + FetchRecurseSubmodulesMode recurseMode = getRecurseMode( + walk.getPath()); + + // When the fetch mode is "yes" we always fetch. When the + // mode + // is "on demand", we only fetch if the submodule's revision + // was + // updated to an object that is not currently present in the + // submodule. + if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND + && !submoduleRepo.hasObject(walk.getObjectId())) + || recurseMode == FetchRecurseSubmodulesMode.YES) { + FetchCommand f = new FetchCommand(submoduleRepo) + .setProgressMonitor(monitor) + .setTagOpt(tagOption) + .setCheckFetchedObjects(checkFetchedObjects) + .setRemoveDeletedRefs(isRemoveDeletedRefs()) + .setThin(thin).setRefSpecs(refSpecs) + .setDryRun(dryRun) + .setRecurseSubmodules(recurseMode); + configure(f); + if (callback != null) { + callback.fetchingSubmodule(walk.getPath()); + } + results.addSubmodule(walk.getPath(), f.call()); + } + } + } + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (ConfigInvalidException e) { + throw new InvalidConfigurationException(e.getMessage(), e); + } } /** - * Executes the {@code fetch} command with all the options and parameters + * {@inheritDoc} + *

+ * Execute the {@code fetch} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) - * - * @return a {@link FetchResult} object representing the successful fetch - * result - * @throws InvalidRemoteException - * when called with an invalid remote uri - * @throws org.eclipse.jgit.api.errors.TransportException - * when an error occurs during transport */ + @Override public FetchResult call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); - try { - Transport transport = Transport.open(repo, remote); - try { - transport.setCheckFetchedObjects(checkFetchedObjects); - transport.setRemoveDeletedRefs(isRemoveDeletedRefs()); - transport.setDryRun(dryRun); - if (tagOption != null) - transport.setTagOpt(tagOption); - transport.setFetchThin(thin); - configure(transport); - - FetchResult result = transport.fetch(monitor, refSpecs); - return result; - } finally { - transport.close(); + try (Transport transport = Transport.open(repo, remote)) { + transport.setCheckFetchedObjects(checkFetchedObjects); + transport.setRemoveDeletedRefs(isRemoveDeletedRefs()); + transport.setDryRun(dryRun); + if (tagOption != null) + transport.setTagOpt(tagOption); + transport.setFetchThin(thin); + configure(transport); + + FetchResult result = transport.fetch(monitor, refSpecs); + if (!repo.isBare()) { + fetchSubmodules(result); } + + return result; } catch (NoRemoteRepositoryException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote), e); @@ -150,12 +262,35 @@ } /** + * Set the mode to be used for recursing into submodules. + * + * @param recurse + * corresponds to the + * --recurse-submodules/--no-recurse-submodules options. If + * {@code null} use the value of the + * {@code submodule.name.fetchRecurseSubmodules} option + * configured per submodule. If not specified there, use the + * value of the {@code fetch.recurseSubmodules} option configured + * in git config. If not configured in either, "on-demand" is the + * built-in default. + * @return {@code this} + * @since 4.7 + */ + public FetchCommand setRecurseSubmodules( + @Nullable FetchRecurseSubmodulesMode recurse) { + checkCallable(); + submoduleRecurseMode = recurse; + return this; + } + + /** * The remote (uri or name) used for the fetch operation. If no remote is * set, the default value of Constants.DEFAULT_REMOTE_NAME will * be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote + * name of a remote * @return {@code this} */ public FetchCommand setRemote(String remote) { @@ -165,6 +300,8 @@ } /** + * Get the remote + * * @return the remote used for the remote operation */ public String getRemote() { @@ -172,6 +309,8 @@ } /** + * Get timeout + * * @return the timeout used for the fetch operation */ public int getTimeout() { @@ -179,16 +318,19 @@ } /** - * @return whether to check received objects checked for validity + * Whether to check received objects for validity + * + * @return whether to check received objects for validity */ public boolean isCheckFetchedObjects() { return checkFetchedObjects; } /** - * If set to true, objects received will be checked for validity + * If set to {@code true}, objects received will be checked for validity * * @param checkFetchedObjects + * whether to check objects for validity * @return {@code this} */ public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) { @@ -198,7 +340,9 @@ } /** - * @return whether or not to remove refs which no longer exist in the source + * Whether to remove refs which no longer exist in the source + * + * @return whether to remove refs which no longer exist in the source */ public boolean isRemoveDeletedRefs() { if (removeDeletedRefs != null) @@ -215,9 +359,11 @@ } /** - * If set to true, refs are removed which no longer exist in the source + * If set to {@code true}, refs are removed which no longer exist in the + * source * * @param removeDeletedRefs + * whether to remove deleted {@code Ref}s * @return {@code this} */ public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) { @@ -227,6 +373,8 @@ } /** + * Get progress monitor + * * @return the progress monitor for the fetch operation */ public ProgressMonitor getProgressMonitor() { @@ -238,8 +386,8 @@ * this is set to NullProgressMonitor * * @see NullProgressMonitor - * * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public FetchCommand setProgressMonitor(ProgressMonitor monitor) { @@ -252,6 +400,8 @@ } /** + * Get list of {@code RefSpec}s + * * @return the ref specs */ public List getRefSpecs() { @@ -262,20 +412,31 @@ * The ref specs to be used in the fetch operation * * @param specs + * String representation of {@code RefSpec}s + * @return {@code this} + * @since 4.9 + */ + public FetchCommand setRefSpecs(String... specs) { + return setRefSpecs( + Arrays.stream(specs).map(RefSpec::new).collect(toList())); + } + + /** + * The ref specs to be used in the fetch operation + * + * @param specs + * one or multiple {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public FetchCommand setRefSpecs(RefSpec... specs) { - checkCallable(); - this.refSpecs.clear(); - for (RefSpec spec : specs) - refSpecs.add(spec); - return this; + return setRefSpecs(Arrays.asList(specs)); } /** * The ref specs to be used in the fetch operation * * @param specs + * list of {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public FetchCommand setRefSpecs(List specs) { @@ -286,6 +447,8 @@ } /** + * Whether to do a dry run + * * @return the dry run preference for the fetch operation */ public boolean isDryRun() { @@ -296,6 +459,7 @@ * Sets whether the fetch operation should be a dry run * * @param dryRun + * whether to do a dry run * @return {@code this} */ public FetchCommand setDryRun(boolean dryRun) { @@ -305,6 +469,8 @@ } /** + * Get thin-pack preference + * * @return the thin-pack preference for fetch operation */ public boolean isThin() { @@ -317,6 +483,7 @@ * Default setting is Transport.DEFAULT_FETCH_THIN * * @param thin + * the thin-pack preference * @return {@code this} */ public FetchCommand setThin(boolean thin) { @@ -329,6 +496,7 @@ * Sets the specification of annotated tag behavior during fetch * * @param tagOpt + * the {@link org.eclipse.jgit.transport.TagOpt} * @return {@code this} */ public FetchCommand setTagOpt(TagOpt tagOpt) { @@ -336,4 +504,17 @@ this.tagOption = tagOpt; return this; } + + /** + * Register a progress callback. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public FetchCommand setCallback(Callback callback) { + this.callback = callback; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,7 +61,6 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.pack.PackConfig; -import org.eclipse.jgit.util.GitDateParser; /** * A class used to execute a {@code gc} command. It has setters for all @@ -97,7 +96,10 @@ private PackConfig pconfig; /** + * Constructor for GarbageCollectCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected GarbageCollectCommand(Repository repo) { super(repo); @@ -105,6 +107,8 @@ } /** + * Set progress monitor + * * @param monitor * a progress monitor * @return this instance @@ -118,8 +122,8 @@ * During gc() or prune() each unreferenced, loose object which has been * created or modified after expire will not be pruned. Only * older objects may be pruned. If set to null then every object is a - * candidate for pruning. Use {@link GitDateParser} to parse time formats - * used by git gc. + * candidate for pruning. Use {@link org.eclipse.jgit.util.GitDateParser} to + * parse time formats used by git gc. * * @param expire * minimal age of objects to be pruned. @@ -159,6 +163,39 @@ return this; } + /** + * Whether to preserve old pack files instead of deleting them. + * + * @since 4.7 + * @param preserveOldPacks + * whether to preserve old pack files + * @return this instance + */ + public GarbageCollectCommand setPreserveOldPacks(boolean preserveOldPacks) { + if (pconfig == null) + pconfig = new PackConfig(repo); + + pconfig.setPreserveOldPacks(preserveOldPacks); + return this; + } + + /** + * Whether to prune preserved pack files in the preserved directory. + * + * @since 4.7 + * @param prunePreserved + * whether to prune preserved pack files + * @return this instance + */ + public GarbageCollectCommand setPrunePreserved(boolean prunePreserved) { + if (pconfig == null) + pconfig = new PackConfig(repo); + + pconfig.setPrunePreserved(prunePreserved); + return this; + } + + /** {@inheritDoc} */ @Override public Properties call() throws GitAPIException { checkCallable(); @@ -197,7 +234,7 @@ * Computes and returns the repository statistics. * * @return the repository statistics - * @throws GitAPIException + * @throws org.eclipse.jgit.api.errors.GitAPIException * thrown if the repository statistics cannot be computed * @since 3.0 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,18 +48,20 @@ /** * Common superclass of all commands in the package {@code org.eclipse.jgit.api} *

- * This class ensures that all commands fulfill the {@link Callable} interface. - * It also has a property {@link #repo} holding a reference to the git - * {@link Repository} this command should work with. + * This class ensures that all commands fulfill the + * {@link java.util.concurrent.Callable} interface. It also has a property + * {@link #repo} holding a reference to the git + * {@link org.eclipse.jgit.lib.Repository} this command should work with. *

* Finally this class stores a state telling whether it is allowed to call - * {@link #call()} on this instance. Instances of {@link GitCommand} can only be - * used for one single successful call to {@link #call()}. Afterwards this - * instance may not be used anymore to set/modify any properties or to call - * {@link #call()} again. This is achieved by setting the {@link #callable} - * property to false after the successful execution of {@link #call()} and to - * check the state (by calling {@link #checkCallable()}) before setting of - * properties and inside {@link #call()}. + * {@link #call()} on this instance. Instances of + * {@link org.eclipse.jgit.api.GitCommand} can only be used for one single + * successful call to {@link #call()}. Afterwards this instance may not be used + * anymore to set/modify any properties or to call {@link #call()} again. This + * is achieved by setting the {@link #callable} property to false after the + * successful execution of {@link #call()} and to check the state (by calling + * {@link #checkCallable()}) before setting of properties and inside + * {@link #call()}. * * @param * the return type which is expected from {@link #call()} @@ -78,14 +80,18 @@ * Creates a new command which interacts with a single repository * * @param repo - * the {@link Repository} this command should interact with + * the {@link org.eclipse.jgit.lib.Repository} this command + * should interact with */ protected GitCommand(Repository repo) { this.repo = repo; } /** - * @return the {@link Repository} this command is interacting with + * Get repository this command is working on + * + * @return the {@link org.eclipse.jgit.lib.Repository} this command is + * interacting with */ public Repository getRepository() { return repo; @@ -106,9 +112,9 @@ /** * Checks that the property {@link #callable} is {@code true}. If not then - * an {@link IllegalStateException} is thrown + * an {@link java.lang.IllegalStateException} is thrown * - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * when this method is called and the property {@link #callable} * is {@code false} */ @@ -120,11 +126,10 @@ } /** - * Executes the command - * - * @return T a result. Each command has its own return type - * @throws GitAPIException - * or subclass thereof when an error occurs + * {@inheritDoc} + *

+ * Execute the command */ + @Override public abstract T call() throws GitAPIException; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java 2019-09-03 12:37:49.000000000 +0000 @@ -89,25 +89,30 @@ private final boolean closeRepo; /** + * Open repository + * * @param dir * the repository to open. May be either the GIT_DIR, or the * working tree directory that contains {@code .git}. - * @return a {@link Git} object for the existing git repository - * @throws IOException + * @return a {@link org.eclipse.jgit.api.Git} object for the existing git + * repository + * @throws java.io.IOException */ public static Git open(File dir) throws IOException { return open(dir, FS.DETECTED); } /** + * Open repository + * * @param dir * the repository to open. May be either the GIT_DIR, or the * working tree directory that contains {@code .git}. * @param fs * filesystem abstraction to use when accessing the repository. - * @return a {@link Git} object for the existing git repository. Closing this - * instance will close the repo. - * @throws IOException + * @return a {@link org.eclipse.jgit.api.Git} object for the existing git + * repository. Closing this instance will close the repo. + * @throws java.io.IOException */ public static Git open(File dir, FS fs) throws IOException { RepositoryCache.FileKey key; @@ -119,57 +124,63 @@ } /** + * Wrap repository + * * @param repo * the git repository this class is interacting with; * {@code null} is not allowed. - * @return a {@link Git} object for the existing git repository. The caller is - * responsible for closing the repository; {@link #close()} on this - * instance does not close the repo. + * @return a {@link org.eclipse.jgit.api.Git} object for the existing git + * repository. The caller is responsible for closing the repository; + * {@link #close()} on this instance does not close the repo. */ public static Git wrap(Repository repo) { return new Git(repo); } /** - * Frees resources associated with this instance. + * {@inheritDoc} *

- * If the repository was opened by a static factory method in this class, then - * this method calls {@link Repository#close()} on the underlying repository - * instance. (Whether this actually releases underlying resources, such as - * file handles, may vary; see {@link Repository} for more details.) + * Free resources associated with this instance. *

- * If the repository was created by a caller and passed into {@link - * #Git(Repository)} or a static factory method in this class, then this - * method does not call close on the underlying repository. + * If the repository was opened by a static factory method in this class, + * then this method calls {@link Repository#close()} on the underlying + * repository instance. (Whether this actually releases underlying + * resources, such as file handles, may vary; see {@link Repository} for + * more details.) *

- * In all cases, after calling this method you should not use this {@link Git} - * instance anymore. + * If the repository was created by a caller and passed into + * {@link #Git(Repository)} or a static factory method in this class, then + * this method does not call close on the underlying repository. + *

+ * In all cases, after calling this method you should not use this + * {@link Git} instance anymore. * * @since 3.2 */ + @Override public void close() { if (closeRepo) repo.close(); } /** - * Returns a command object to execute a {@code clone} command + * Return a command object to execute a {@code clone} command * - * @see Git documentation about clone - * @return a {@link CloneCommand} used to collect all optional parameters - * and to finally execute the {@code clone} command + * @return a {@link org.eclipse.jgit.api.CloneCommand} used to collect all + * optional parameters and to finally execute the {@code clone} + * command */ public static CloneCommand cloneRepository() { return new CloneCommand(); } /** - * Returns a command to list remote branches/tags without a local - * repository. + * Return a command to list remote branches/tags without a local repository. * - * @return a {@link LsRemoteCommand} + * @return a {@link org.eclipse.jgit.api.LsRemoteCommand} * @since 3.1 */ public static LsRemoteCommand lsRemoteRepository() { @@ -177,24 +188,25 @@ } /** - * Returns a command object to execute a {@code init} command + * Return a command object to execute a {@code init} command * - * @see Git documentation about init - * @return a {@link InitCommand} used to collect all optional parameters and - * to finally execute the {@code init} command + * @see Git + * documentation about init + * @return a {@link org.eclipse.jgit.api.InitCommand} used to collect all + * optional parameters and to finally execute the {@code init} + * command */ public static InitCommand init() { return new InitCommand(); } /** - * Constructs a new {@link Git} object which can interact with the specified - * git repository. + * Construct a new {@link org.eclipse.jgit.api.Git} object which can + * interact with the specified git repository. *

- * All command classes returned by methods of this class will always interact - * with this git repository. + * All command classes returned by methods of this class will always + * interact with this git repository. *

* The caller is responsible for closing the repository; {@link #close()} on * this instance does not close the repo. @@ -215,75 +227,78 @@ } /** - * Returns a command object to execute a {@code Commit} command + * Return a command object to execute a {@code Commit} command * - * @see Git documentation about Commit - * @return a {@link CommitCommand} used to collect all optional parameters - * and to finally execute the {@code Commit} command + * @return a {@link org.eclipse.jgit.api.CommitCommand} used to collect all + * optional parameters and to finally execute the {@code Commit} + * command */ public CommitCommand commit() { return new CommitCommand(repo); } /** - * Returns a command object to execute a {@code Log} command + * Return a command object to execute a {@code Log} command * - * @see Git documentation about Log - * @return a {@link LogCommand} used to collect all optional parameters and - * to finally execute the {@code Log} command + * @see Git + * documentation about Log + * @return a {@link org.eclipse.jgit.api.LogCommand} used to collect all + * optional parameters and to finally execute the {@code Log} + * command */ public LogCommand log() { return new LogCommand(repo); } /** - * Returns a command object to execute a {@code Merge} command + * Return a command object to execute a {@code Merge} command * - * @see Git documentation about Merge - * @return a {@link MergeCommand} used to collect all optional parameters - * and to finally execute the {@code Merge} command + * @return a {@link org.eclipse.jgit.api.MergeCommand} used to collect all + * optional parameters and to finally execute the {@code Merge} + * command */ public MergeCommand merge() { return new MergeCommand(repo); } /** - * Returns a command object to execute a {@code Pull} command + * Return a command object to execute a {@code Pull} command * - * @return a {@link PullCommand} + * @return a {@link org.eclipse.jgit.api.PullCommand} */ public PullCommand pull() { return new PullCommand(repo); } /** - * Returns a command object used to create branches + * Return a command object used to create branches * - * @return a {@link CreateBranchCommand} + * @return a {@link org.eclipse.jgit.api.CreateBranchCommand} */ public CreateBranchCommand branchCreate() { return new CreateBranchCommand(repo); } /** - * Returns a command object used to delete branches + * Return a command object used to delete branches * - * @return a {@link DeleteBranchCommand} + * @return a {@link org.eclipse.jgit.api.DeleteBranchCommand} */ public DeleteBranchCommand branchDelete() { return new DeleteBranchCommand(repo); } /** - * Returns a command object used to list branches + * Return a command object used to list branches * - * @return a {@link ListBranchCommand} + * @return a {@link org.eclipse.jgit.api.ListBranchCommand} */ public ListBranchCommand branchList() { return new ListBranchCommand(repo); @@ -291,170 +306,180 @@ /** * - * Returns a command object used to list tags + * Return a command object used to list tags * - * @return a {@link ListTagCommand} + * @return a {@link org.eclipse.jgit.api.ListTagCommand} */ public ListTagCommand tagList() { return new ListTagCommand(repo); } /** - * Returns a command object used to rename branches + * Return a command object used to rename branches * - * @return a {@link RenameBranchCommand} + * @return a {@link org.eclipse.jgit.api.RenameBranchCommand} */ public RenameBranchCommand branchRename() { return new RenameBranchCommand(repo); } /** - * Returns a command object to execute a {@code Add} command + * Return a command object to execute a {@code Add} command * - * @see Git documentation about Add - * @return a {@link AddCommand} used to collect all optional parameters and - * to finally execute the {@code Add} command + * @see Git + * documentation about Add + * @return a {@link org.eclipse.jgit.api.AddCommand} used to collect all + * optional parameters and to finally execute the {@code Add} + * command */ public AddCommand add() { return new AddCommand(repo); } /** - * Returns a command object to execute a {@code Tag} command + * Return a command object to execute a {@code Tag} command * - * @see Git documentation about Tag - * @return a {@link TagCommand} used to collect all optional parameters and - * to finally execute the {@code Tag} command + * @see Git + * documentation about Tag + * @return a {@link org.eclipse.jgit.api.TagCommand} used to collect all + * optional parameters and to finally execute the {@code Tag} + * command */ public TagCommand tag() { return new TagCommand(repo); } /** - * Returns a command object to execute a {@code Fetch} command + * Return a command object to execute a {@code Fetch} command * - * @see Git documentation about Fetch - * @return a {@link FetchCommand} used to collect all optional parameters - * and to finally execute the {@code Fetch} command + * @return a {@link org.eclipse.jgit.api.FetchCommand} used to collect all + * optional parameters and to finally execute the {@code Fetch} + * command */ public FetchCommand fetch() { return new FetchCommand(repo); } /** - * Returns a command object to execute a {@code Push} command + * Return a command object to execute a {@code Push} command * - * @see Git documentation about Push - * @return a {@link PushCommand} used to collect all optional parameters and - * to finally execute the {@code Push} command + * @see Git + * documentation about Push + * @return a {@link org.eclipse.jgit.api.PushCommand} used to collect all + * optional parameters and to finally execute the {@code Push} + * command */ public PushCommand push() { return new PushCommand(repo); } /** - * Returns a command object to execute a {@code cherry-pick} command + * Return a command object to execute a {@code cherry-pick} command * - * @see Git documentation about cherry-pick - * @return a {@link CherryPickCommand} used to collect all optional - * parameters and to finally execute the {@code cherry-pick} command + * @return a {@link org.eclipse.jgit.api.CherryPickCommand} used to collect + * all optional parameters and to finally execute the + * {@code cherry-pick} command */ public CherryPickCommand cherryPick() { return new CherryPickCommand(repo); } /** - * Returns a command object to execute a {@code revert} command + * Return a command object to execute a {@code revert} command * - * @see Git documentation about reverting changes - * @return a {@link RevertCommand} used to collect all optional parameters - * and to finally execute the {@code cherry-pick} command + * @return a {@link org.eclipse.jgit.api.RevertCommand} used to collect all + * optional parameters and to finally execute the + * {@code cherry-pick} command */ public RevertCommand revert() { return new RevertCommand(repo); } /** - * Returns a command object to execute a {@code Rebase} command + * Return a command object to execute a {@code Rebase} command * - * @see Git documentation about rebase - * @return a {@link RebaseCommand} used to collect all optional parameters - * and to finally execute the {@code rebase} command + * @return a {@link org.eclipse.jgit.api.RebaseCommand} used to collect all + * optional parameters and to finally execute the {@code rebase} + * command */ public RebaseCommand rebase() { return new RebaseCommand(repo); } /** - * Returns a command object to execute a {@code rm} command + * Return a command object to execute a {@code rm} command * - * @see Git documentation about rm - * @return a {@link RmCommand} used to collect all optional parameters and - * to finally execute the {@code rm} command + * @see Git + * documentation about rm + * @return a {@link org.eclipse.jgit.api.RmCommand} used to collect all + * optional parameters and to finally execute the {@code rm} command */ public RmCommand rm() { return new RmCommand(repo); } /** - * Returns a command object to execute a {@code checkout} command + * Return a command object to execute a {@code checkout} command * - * @see Git documentation about checkout - * @return a {@link CheckoutCommand} used to collect all optional parameters - * and to finally execute the {@code checkout} command + * @return a {@link org.eclipse.jgit.api.CheckoutCommand} used to collect + * all optional parameters and to finally execute the + * {@code checkout} command */ public CheckoutCommand checkout() { return new CheckoutCommand(repo); } /** - * Returns a command object to execute a {@code reset} command + * Return a command object to execute a {@code reset} command * - * @see Git documentation about reset - * @return a {@link ResetCommand} used to collect all optional parameters - * and to finally execute the {@code reset} command + * @return a {@link org.eclipse.jgit.api.ResetCommand} used to collect all + * optional parameters and to finally execute the {@code reset} + * command */ public ResetCommand reset() { return new ResetCommand(repo); } /** - * Returns a command object to execute a {@code status} command + * Return a command object to execute a {@code status} command * - * @see Git documentation about status - * @return a {@link StatusCommand} used to collect all optional parameters - * and to finally execute the {@code status} command + * @return a {@link org.eclipse.jgit.api.StatusCommand} used to collect all + * optional parameters and to finally execute the {@code status} + * command */ public StatusCommand status() { return new StatusCommand(repo); } /** - * Returns a command to create an archive from a tree + * Return a command to create an archive from a tree * - * @return a {@link ArchiveCommand} + * @return a {@link org.eclipse.jgit.api.ArchiveCommand} * @since 3.1 */ public ArchiveCommand archive() { @@ -462,179 +487,196 @@ } /** - * Returns a command to add notes to an object + * Return a command to add notes to an object * - * @return a {@link AddNoteCommand} + * @return a {@link org.eclipse.jgit.api.AddNoteCommand} */ public AddNoteCommand notesAdd() { return new AddNoteCommand(repo); } /** - * Returns a command to remove notes on an object + * Return a command to remove notes on an object * - * @return a {@link RemoveNoteCommand} + * @return a {@link org.eclipse.jgit.api.RemoveNoteCommand} */ public RemoveNoteCommand notesRemove() { return new RemoveNoteCommand(repo); } /** - * Returns a command to list all notes + * Return a command to list all notes * - * @return a {@link ListNotesCommand} + * @return a {@link org.eclipse.jgit.api.ListNotesCommand} */ public ListNotesCommand notesList() { return new ListNotesCommand(repo); } /** - * Returns a command to show notes on an object + * Return a command to show notes on an object * - * @return a {@link ShowNoteCommand} + * @return a {@link org.eclipse.jgit.api.ShowNoteCommand} */ public ShowNoteCommand notesShow() { return new ShowNoteCommand(repo); } /** - * Returns a command object to execute a {@code ls-remote} command + * Return a command object to execute a {@code ls-remote} command * - * @see Git documentation about ls-remote - * @return a {@link LsRemoteCommand} used to collect all optional parameters - * and to finally execute the {@code status} command + * @return a {@link org.eclipse.jgit.api.LsRemoteCommand} used to collect + * all optional parameters and to finally execute the {@code status} + * command */ public LsRemoteCommand lsRemote() { return new LsRemoteCommand(repo); } /** - * Returns a command object to execute a {@code clean} command + * Return a command object to execute a {@code clean} command * - * @see Git documentation about Clean - * @return a {@link CleanCommand} used to collect all optional parameters - * and to finally execute the {@code clean} command + * @return a {@link org.eclipse.jgit.api.CleanCommand} used to collect all + * optional parameters and to finally execute the {@code clean} + * command */ public CleanCommand clean() { return new CleanCommand(repo); } /** - * Returns a command object to execute a {@code blame} command + * Return a command object to execute a {@code blame} command * - * @see Git documentation about Blame - * @return a {@link BlameCommand} used to collect all optional parameters - * and to finally execute the {@code blame} command + * @return a {@link org.eclipse.jgit.api.BlameCommand} used to collect all + * optional parameters and to finally execute the {@code blame} + * command */ public BlameCommand blame() { return new BlameCommand(repo); } /** - * Returns a command object to execute a {@code reflog} command + * Return a command object to execute a {@code reflog} command * - * @see Git documentation about reflog - * @return a {@link ReflogCommand} used to collect all optional parameters - * and to finally execute the {@code reflog} command + * @return a {@link org.eclipse.jgit.api.ReflogCommand} used to collect all + * optional parameters and to finally execute the {@code reflog} + * command */ public ReflogCommand reflog() { return new ReflogCommand(repo); } /** - * Returns a command object to execute a {@code diff} command + * Return a command object to execute a {@code diff} command * - * @see Git documentation about diff - * @return a {@link DiffCommand} used to collect all optional parameters and - * to finally execute the {@code diff} command + * @see Git + * documentation about diff + * @return a {@link org.eclipse.jgit.api.DiffCommand} used to collect all + * optional parameters and to finally execute the {@code diff} + * command */ public DiffCommand diff() { return new DiffCommand(repo); } /** - * Returns a command object used to delete tags + * Return a command object used to delete tags * - * @return a {@link DeleteTagCommand} + * @return a {@link org.eclipse.jgit.api.DeleteTagCommand} */ public DeleteTagCommand tagDelete() { return new DeleteTagCommand(repo); } /** - * Returns a command object to execute a {@code submodule add} command + * Return a command object to execute a {@code submodule add} command * - * @return a {@link SubmoduleAddCommand} used to add a new submodule to a - * parent repository + * @return a {@link org.eclipse.jgit.api.SubmoduleAddCommand} used to add a + * new submodule to a parent repository */ public SubmoduleAddCommand submoduleAdd() { return new SubmoduleAddCommand(repo); } /** - * Returns a command object to execute a {@code submodule init} command + * Return a command object to execute a {@code submodule init} command * - * @return a {@link SubmoduleInitCommand} used to initialize the - * repository's config with settings from the .gitmodules file in - * the working tree + * @return a {@link org.eclipse.jgit.api.SubmoduleInitCommand} used to + * initialize the repository's config with settings from the + * .gitmodules file in the working tree */ public SubmoduleInitCommand submoduleInit() { return new SubmoduleInitCommand(repo); } /** + * Returns a command object to execute a {@code submodule deinit} command + * + * @return a {@link org.eclipse.jgit.api.SubmoduleDeinitCommand} used to + * remove a submodule's working tree manifestation + * @since 4.10 + */ + public SubmoduleDeinitCommand submoduleDeinit() { + return new SubmoduleDeinitCommand(repo); + } + + /** * Returns a command object to execute a {@code submodule status} command * - * @return a {@link SubmoduleStatusCommand} used to report the status of a - * repository's configured submodules + * @return a {@link org.eclipse.jgit.api.SubmoduleStatusCommand} used to + * report the status of a repository's configured submodules */ public SubmoduleStatusCommand submoduleStatus() { return new SubmoduleStatusCommand(repo); } /** - * Returns a command object to execute a {@code submodule sync} command + * Return a command object to execute a {@code submodule sync} command * - * @return a {@link SubmoduleSyncCommand} used to update the URL of a - * submodule from the parent repository's .gitmodules file + * @return a {@link org.eclipse.jgit.api.SubmoduleSyncCommand} used to + * update the URL of a submodule from the parent repository's + * .gitmodules file */ public SubmoduleSyncCommand submoduleSync() { return new SubmoduleSyncCommand(repo); } /** - * Returns a command object to execute a {@code submodule update} command + * Return a command object to execute a {@code submodule update} command * - * @return a {@link SubmoduleUpdateCommand} used to update the submodules in - * a repository to the configured revision + * @return a {@link org.eclipse.jgit.api.SubmoduleUpdateCommand} used to + * update the submodules in a repository to the configured revision */ public SubmoduleUpdateCommand submoduleUpdate() { return new SubmoduleUpdateCommand(repo); } /** - * Returns a command object used to list stashed commits + * Return a command object used to list stashed commits * - * @return a {@link StashListCommand} + * @return a {@link org.eclipse.jgit.api.StashListCommand} */ public StashListCommand stashList() { return new StashListCommand(repo); } /** - * Returns a command object used to create a stashed commit + * Return a command object used to create a stashed commit * - * @return a {@link StashCreateCommand} + * @return a {@link org.eclipse.jgit.api.StashCreateCommand} * @since 2.0 */ public StashCreateCommand stashCreate() { @@ -642,9 +684,9 @@ } /** - * Returns a command object used to apply a stashed commit + * Returs a command object used to apply a stashed commit * - * @return a {@link StashApplyCommand} + * @return a {@link org.eclipse.jgit.api.StashApplyCommand} * @since 2.0 */ public StashApplyCommand stashApply() { @@ -652,9 +694,9 @@ } /** - * Returns a command object used to drop a stashed commit + * Return a command object used to drop a stashed commit * - * @return a {@link StashDropCommand} + * @return a {@link org.eclipse.jgit.api.StashDropCommand} * @since 2.0 */ public StashDropCommand stashDrop() { @@ -662,14 +704,14 @@ } /** - * Returns a command object to execute a {@code apply} command + * Return a command object to execute a {@code apply} command * - * @see Git documentation about apply - * - * @return a {@link ApplyCommand} used to collect all optional parameters - * and to finally execute the {@code apply} command + * @return a {@link org.eclipse.jgit.api.ApplyCommand} used to collect all + * optional parameters and to finally execute the {@code apply} + * command * @since 2.0 */ public ApplyCommand apply() { @@ -677,14 +719,14 @@ } /** - * Returns a command object to execute a {@code gc} command + * Return a command object to execute a {@code gc} command * - * @see Git documentation about gc - * - * @return a {@link GarbageCollectCommand} used to collect all optional - * parameters and to finally execute the {@code gc} command + * @see Git + * documentation about gc + * @return a {@link org.eclipse.jgit.api.GarbageCollectCommand} used to + * collect all optional parameters and to finally execute the + * {@code gc} command * @since 2.2 */ public GarbageCollectCommand gc() { @@ -692,9 +734,9 @@ } /** - * Returns a command object to find human-readable names of revisions. + * Return a command object to find human-readable names of revisions. * - * @return a {@link NameRevCommand}. + * @return a {@link org.eclipse.jgit.api.NameRevCommand}. * @since 3.0 */ public NameRevCommand nameRev() { @@ -702,10 +744,10 @@ } /** - * Returns a command object to come up with a short name that describes a + * Return a command object to come up with a short name that describes a * commit in terms of the nearest git tag. * - * @return a {@link DescribeCommand}. + * @return a {@link org.eclipse.jgit.api.DescribeCommand}. * @since 3.2 */ public DescribeCommand describe() { @@ -713,13 +755,56 @@ } /** - * @return the git repository this class is interacting with; see {@link - * #close()} for notes on closing this repository. + * Return a command used to list the available remotes. + * + * @return a {@link org.eclipse.jgit.api.RemoteListCommand} + * @since 4.2 + */ + public RemoteListCommand remoteList() { + return new RemoteListCommand(repo); + } + + /** + * Return a command used to add a new remote. + * + * @return a {@link org.eclipse.jgit.api.RemoteAddCommand} + * @since 4.2 + */ + public RemoteAddCommand remoteAdd() { + return new RemoteAddCommand(repo); + } + + /** + * Return a command used to remove an existing remote. + * + * @return a {@link org.eclipse.jgit.api.RemoteRemoveCommand} + * @since 4.2 + */ + public RemoteRemoveCommand remoteRemove() { + return new RemoteRemoveCommand(repo); + } + + /** + * Return a command used to change the URL of an existing remote. + * + * @return a {@link org.eclipse.jgit.api.RemoteSetUrlCommand} + * @since 4.2 + */ + public RemoteSetUrlCommand remoteSetUrl() { + return new RemoteSetUrlCommand(repo); + } + + /** + * Get repository + * + * @return the git repository this class is interacting with; see + * {@link #close()} for notes on closing this repository. */ public Repository getRepository() { return repo; } + /** {@inheritDoc} */ @Override public String toString() { return "Git[" + repo + "]"; //$NON-NLS-1$//$NON-NLS-2$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; /** @@ -68,16 +69,25 @@ private boolean bare; + private FS fs; + /** + * {@inheritDoc} + *

* Executes the {@code Init} command. * - * @return the newly created {@code Git} object with associated repository + * @return a {@code Git} instance that owns the {@code Repository} that it + * wraps. */ + @Override public Git call() throws GitAPIException { try { RepositoryBuilder builder = new RepositoryBuilder(); if (bare) builder.setBare(); + if (fs != null) { + builder.setFS(fs); + } builder.readEnvironment(); if (gitDir != null) builder.setGitDir(gitDir); @@ -113,7 +123,7 @@ Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) repository.create(bare); - return new Git(repository); + return new Git(repository, true); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } @@ -126,7 +136,7 @@ * @param directory * the directory to init to * @return this instance - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -140,10 +150,12 @@ } /** + * Set the repository meta directory (.git) + * * @param gitDir * the repository meta directory * @return this instance - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -175,9 +187,11 @@ } /** + * Set whether the repository is bare or not + * * @param bare * whether the repository is bare or not - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both @@ -189,4 +203,18 @@ this.bare = bare; return this; } + + /** + * Set the file system abstraction to be used for repositories created by + * this command. + * + * @param fs + * the abstraction. + * @return {@code this} (for chaining calls). + * @since 4.10 + */ + public InitCommand setFs(FS fs) { + this.fs = fs; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -95,20 +95,25 @@ } /** + * Constructor for ListBranchCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected ListBranchCommand(Repository repo) { super(repo); } + /** {@inheritDoc} */ + @Override public List call() throws GitAPIException { checkCallable(); List resultRefs; try { - Collection refs = new ArrayList(); + Collection refs = new ArrayList<>(); // Also return HEAD if it's detached - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); if (head != null && head.getLeaf().getName().equals(Constants.HEAD)) refs.add(head); @@ -120,12 +125,13 @@ refs.addAll(getRefs(Constants.R_HEADS)); refs.addAll(getRefs(Constants.R_REMOTES)); } - resultRefs = new ArrayList(filterRefs(refs)); + resultRefs = new ArrayList<>(filterRefs(refs)); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } Collections.sort(resultRefs, new Comparator() { + @Override public int compare(Ref o1, Ref o2) { return o1.getName().compareTo(o2.getName()); } @@ -152,6 +158,8 @@ } /** + * Set the list mode + * * @param listMode * optional: corresponds to the -r/-a options; by default, only * local branches will be listed diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,21 +68,23 @@ private String notesRef = Constants.R_NOTES_COMMITS; /** + * Constructor for ListNotesCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected ListNotesCommand(Repository repo) { super(repo); } - /** - * @return the requested notes - */ + /** {@inheritDoc} */ + @Override public List call() throws GitAPIException { checkCallable(); - List notes = new ArrayList(); + List notes = new ArrayList<>(); NoteMap map = NoteMap.newEmptyMap(); try (RevWalk walk = new RevWalk(repo)) { - Ref ref = repo.getRef(notesRef); + Ref ref = repo.findRef(notesRef); // if we have a notes ref, use it if (ref != null) { RevCommit notesCommit = walk.parseCommit(ref.getObjectId()); @@ -100,12 +102,14 @@ } /** + * Set the {@code Ref} to read notes from + * * @param notesRef - * the ref to read notes from. Note, the default value of - * {@link Constants#R_NOTES_COMMITS} will be used if nothing is - * set + * the name of the {@code Ref} to read notes from. Note, the + * default value of + * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be + * used if nothing is set * @return {@code this} - * * @see Constants#R_NOTES_COMMITS */ public ListNotesCommand setNotesRef(String notesRef) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,19 +65,21 @@ public class ListTagCommand extends GitCommand> { /** + * Constructor for ListTagCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected ListTagCommand(Repository repo) { super(repo); } - /** - * @return the tags available - */ + /** {@inheritDoc} */ + @Override public List call() throws GitAPIException { checkCallable(); Map refList; - List tags = new ArrayList(); + List tags = new ArrayList<>(); try (RevWalk revWalk = new RevWalk(repo)) { refList = repo.getRefDatabase().getRefs(Constants.R_TAGS); for (Ref ref : refList.values()) { @@ -87,6 +89,7 @@ throw new JGitInternalException(e.getMessage(), e); } Collections.sort(tags, new Comparator() { + @Override public int compare(Ref o1, Ref o2) { return o1.getName().compareTo(o2.getName()); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,6 +65,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.AndRevFilter; import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter; +import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.revwalk.filter.SkipRevFilter; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; @@ -77,7 +78,7 @@ * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) *

- * Examples (git is a {@link Git} instance): + * Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Get newest 10 commits, starting from the current branch: * @@ -104,14 +105,19 @@ private boolean startSpecified = false; - private final List pathFilters = new ArrayList(); + private RevFilter revFilter; + + private final List pathFilters = new ArrayList<>(); private int maxCount = -1; private int skip = -1; /** + * Constructor for LogCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected LogCommand(Repository repo) { super(repo); @@ -119,16 +125,15 @@ } /** + * {@inheritDoc} + *

* Executes the {@code Log} command with all the options and parameters * collected by the setter methods (e.g. {@link #add(AnyObjectId)}, * {@link #not(AnyObjectId)}, ..) of this class. Each instance of this class * should only be used for one invocation of the command. Don't call this * method twice on an instance. - * - * @return an iteration over RevCommits - * @throws NoHeadException - * of the references ref cannot be resolved */ + @Override public Iterable call() throws GitAPIException, NoHeadException { checkCallable(); if (pathFilters.size() > 0) @@ -156,6 +161,11 @@ e); } } + + if (this.revFilter != null) { + walk.setRevFilter(this.revFilter); + } + setCallable(false); return walk; } @@ -165,23 +175,26 @@ * * @see RevWalk#markStart(RevCommit) * @param start + * the id of the commit to start from * @return {@code this} - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier - * invocation to {@link RevWalk#lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to - * {@link RevWalk#lookupCommit(AnyObjectId)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. Expect only + * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of - * {@link IOException} (e.g. {@link MissingObjectException}) are + * {@link java.io.IOException} (e.g. + * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand add(AnyObjectId start) throws MissingObjectException, @@ -193,23 +206,26 @@ * Same as {@code --not start}, or {@code ^start} * * @param start + * a {@link org.eclipse.jgit.lib.AnyObjectId} * @return {@code this} - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier - * invocation to {@link RevWalk#lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to - * {@link RevWalk#lookupCommit(AnyObjectId)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. Expect only + * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of - * {@link IOException} (e.g. {@link MissingObjectException}) are + * {@link java.io.IOException} (e.g. + * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand not(AnyObjectId start) throws MissingObjectException, @@ -221,24 +237,28 @@ * Adds the range {@code since..until} * * @param since + * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @param until + * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @return {@code this} - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier - * invocation to {@link RevWalk#lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to - * {@link RevWalk#lookupCommit(AnyObjectId)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. Expect only + * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of - * {@link IOException} (e.g. {@link MissingObjectException}) are + * {@link java.io.IOException} (e.g. + * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand addRange(AnyObjectId since, AnyObjectId until) @@ -251,7 +271,7 @@ * * @see #add(AnyObjectId) * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the references could not be accessed */ public LogCommand all() throws IOException { @@ -342,4 +362,20 @@ , start), e); } } + + + /** + * Set a filter for the LogCommand. + * + * @param aFilter + * the filter that this instance of LogCommand + * should use + * @return {@code this} + * @since 4.4 + */ + public LogCommand setRevFilter(RevFilter aFilter) { + checkCallable(); + this.revFilter = aFilter; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,6 +83,8 @@ private String uploadPack; /** + * Constructor for LsRemoteCommand + * * @param repo * local repository or null for operation without local * repository @@ -98,6 +100,7 @@ * * @see Constants#DEFAULT_REMOTE_NAME * @param remote + * a {@link java.lang.String} object. * @return {@code this} */ public LsRemoteCommand setRemote(String remote) { @@ -110,6 +113,7 @@ * Include refs/heads in references results * * @param heads + * whether to include refs/heads * @return {@code this} */ public LsRemoteCommand setHeads(boolean heads) { @@ -121,6 +125,7 @@ * Include refs/tags in references results * * @param tags + * whether to include tags * @return {@code this} */ public LsRemoteCommand setTags(boolean tags) { @@ -132,6 +137,8 @@ * The full path of git-upload-pack on the remote host * * @param uploadPack + * the full path of executable providing the git-upload-pack + * service on remote host * @return {@code this} */ public LsRemoteCommand setUploadPack(String uploadPack) { @@ -140,19 +147,14 @@ } /** - * Executes the {@code LsRemote} command with all the options and parameters + * {@inheritDoc} + *

+ * Execute the {@code LsRemote} command with all the options and parameters * collected by the setter methods (e.g. {@link #setHeads(boolean)}) of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. - * - * @return a collection of references in the remote repository - * @throws GitAPIException - * or subclass thereof when an error occurs - * @throws InvalidRemoteException - * when called with an invalid remote uri - * @throws org.eclipse.jgit.api.errors.TransportException - * for errors that occurs during transport */ + @Override public Collection call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { @@ -163,9 +165,9 @@ * Same as {@link #call()}, but return Map instead of Collection. * * @return a map from names to references in the remote repository - * @throws GitAPIException + * @throws org.eclipse.jgit.api.errors.GitAPIException * or subclass thereof when an error occurs - * @throws InvalidRemoteException + * @throws org.eclipse.jgit.api.errors.InvalidRemoteException * when called with an invalid remote uri * @throws org.eclipse.jgit.api.errors.TransportException * for errors that occurs during transport @@ -182,36 +184,33 @@ org.eclipse.jgit.api.errors.TransportException { checkCallable(); - Transport transport = null; - FetchConnection fc = null; - try { - if (repo != null) - transport = Transport.open(repo, remote); - else - transport = Transport.open(new URIish(remote)); + try (Transport transport = repo != null + ? Transport.open(repo, remote) + : Transport.open(new URIish(remote))) { transport.setOptionUploadPack(uploadPack); configure(transport); - Collection refSpecs = new ArrayList(1); + Collection refSpecs = new ArrayList<>(1); if (tags) refSpecs.add(new RefSpec( "refs/tags/*:refs/remotes/origin/tags/*")); //$NON-NLS-1$ if (heads) refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$ Collection refs; - Map refmap = new HashMap(); - fc = transport.openFetch(); - refs = fc.getRefs(); - if (refSpecs.isEmpty()) - for (Ref r : refs) - refmap.put(r.getName(), r); - else - for (Ref r : refs) - for (RefSpec rs : refSpecs) - if (rs.matchSource(r)) { - refmap.put(r.getName(), r); - break; - } - return refmap; + Map refmap = new HashMap<>(); + try (FetchConnection fc = transport.openFetch()) { + refs = fc.getRefs(); + if (refSpecs.isEmpty()) + for (Ref r : refs) + refmap.put(r.getName(), r); + else + for (Ref r : refs) + for (RefSpec rs : refSpecs) + if (rs.matchSource(r)) { + refmap.put(r.getName(), r); + break; + } + return refmap; + } } catch (URISyntaxException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); @@ -223,11 +222,6 @@ throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); - } finally { - if (fc != null) - fc.close(); - if (transport != null) - transport.close(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,7 @@ /* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010-2014, Stefan Lay + * Copyright (C) 2016, Laurent Delaigue * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -49,8 +50,10 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; @@ -61,12 +64,15 @@ import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config.ConfigEnum; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; @@ -98,7 +104,7 @@ private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE; - private List commits = new LinkedList(); + private List commits = new LinkedList<>(); private Boolean squash; @@ -106,6 +112,8 @@ private String message; + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** * The modes available for fast forward merges corresponding to the * --ff, --no-ff and --ff-only @@ -128,10 +136,12 @@ */ FF_ONLY; + @Override public String toConfigValue() { - return "--" + name().toLowerCase().replace('_', '-'); //$NON-NLS-1$ + return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$ } + @Override public boolean matchConfigValue(String in) { if (StringUtils.isEmptyOrNull(in)) return false; @@ -201,20 +211,24 @@ private Boolean commit; /** + * Constructor for MergeCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected MergeCommand(Repository repo) { super(repo); } /** - * Executes the {@code Merge} command with all the options and parameters + * {@inheritDoc} + *

+ * Execute the {@code Merge} command with all the options and parameters * collected by the setter methods (e.g. {@link #include(Ref)}) of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. - * - * @return the result of the merge */ + @Override @SuppressWarnings("boxing") public MergeResult call() throws GitAPIException, NoHeadException, ConcurrentRefUpdateException, CheckoutConflictException, @@ -223,17 +237,15 @@ fallBackToConfiguration(); checkParameters(); - RevWalk revWalk = null; DirCacheCheckout dco = null; - try { - Ref head = repo.getRef(Constants.HEAD); + try (RevWalk revWalk = new RevWalk(repo)) { + Ref head = repo.exactRef(Constants.HEAD); if (head == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$ // Check for FAST_FORWARD, ALREADY_UP_TO_DATE - revWalk = new RevWalk(repo); // we know for now there is only one commit Ref ref = commits.get(0); @@ -254,6 +266,7 @@ dco = new DirCacheCheckout(repo, repo.lockDirCache(), srcCommit.getTree()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); RefUpdate refUpdate = repo .updateRef(head.getTarget().getName()); @@ -284,6 +297,7 @@ dco = new DirCacheCheckout(repo, headCommit.getTree(), repo.lockDirCache(), srcCommit.getTree()); + dco.setProgressMonitor(monitor); dco.setFailOnConflict(true); dco.checkout(); String msg = null; @@ -330,6 +344,7 @@ repo.writeSquashCommitMsg(squashMessage); } Merger merger = mergeStrategy.newMerger(repo); + merger.setProgressMonitor(monitor); boolean noProblems; Map> lowLevelResults = null; Map failingPaths = null; @@ -344,6 +359,10 @@ .getMergeResults(); failingPaths = resolveMerger.getFailingPaths(); unmergedPaths = resolveMerger.getUnmergedPaths(); + if (!resolveMerger.getModifiedFiles().isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent( + resolveMerger.getModifiedFiles(), null)); + } } else noProblems = merger.merge(headCommit, srcCommit); refLogMessage.append(": Merge made by "); //$NON-NLS-1$ @@ -357,6 +376,7 @@ headCommit.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); String msg = null; @@ -375,6 +395,7 @@ .call().getId(); } mergeStatus = MergeStatus.MERGED; + getRepository().autoGC(monitor); } if (commit && squash) { msg = JGitText.get().squashCommitNotUpdatingHEAD; @@ -416,9 +437,6 @@ MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand, e), e); - } finally { - if (revWalk != null) - revWalk.close(); } } @@ -475,9 +493,10 @@ } /** + * Set merge strategy * * @param mergeStrategy - * the {@link MergeStrategy} to be used + * the {@link org.eclipse.jgit.merge.MergeStrategy} to be used * @return {@code this} */ public MergeCommand setStrategy(MergeStrategy mergeStrategy) { @@ -487,6 +506,8 @@ } /** + * Reference to a commit to be merged with the current head + * * @param aCommit * a reference to a commit which is merged with the current head * @return {@code this} @@ -498,6 +519,8 @@ } /** + * Id of a commit which is to be merged with the current head + * * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} @@ -507,8 +530,10 @@ } /** + * Include a commit + * * @param name - * a name given to the commit + * a name of a {@code Ref} pointing to the commit * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} @@ -524,9 +549,10 @@ * HEAD. Otherwise, perform the merge and commit the result. *

* In case the merge was successful but this flag was set to - * true a {@link MergeResult} with status - * {@link MergeStatus#MERGED_SQUASHED} or - * {@link MergeStatus#FAST_FORWARD_SQUASHED} is returned. + * true a {@link org.eclipse.jgit.api.MergeResult} with status + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_SQUASHED} or + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#FAST_FORWARD_SQUASHED} + * is returned. * * @param squash * whether to squash commits or not @@ -543,12 +569,15 @@ * Sets the fast forward mode. * * @param fastForwardMode - * corresponds to the --ff/--no-ff/--ff-only options. --ff is the - * default option. + * corresponds to the --ff/--no-ff/--ff-only options. If + * {@code null} use the value of the {@code merge.ff} option + * configured in git config. If this option is not configured + * --ff is the built-in default. * @return {@code this} * @since 2.2 */ - public MergeCommand setFastForward(FastForwardMode fastForwardMode) { + public MergeCommand setFastForward( + @Nullable FastForwardMode fastForwardMode) { checkCallable(); this.fastForwardMode = fastForwardMode; return this; @@ -562,9 +591,11 @@ * true if this command should commit (this is the * default behavior). false if this command should * not commit. In case the merge was successful but this flag was - * set to false a {@link MergeResult} with type - * {@link MergeResult} with status - * {@link MergeStatus#MERGED_NOT_COMMITTED} is returned + * set to false a + * {@link org.eclipse.jgit.api.MergeResult} with type + * {@link org.eclipse.jgit.api.MergeResult} with status + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_NOT_COMMITTED} + * is returned * @return {@code this} * @since 3.0 */ @@ -586,4 +617,22 @@ this.message = message; return this; } + + /** + * The progress monitor associated with the diff operation. By default, this + * is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * A progress monitor + * @return this instance + * @since 4.2 + */ + public MergeCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,11 +53,10 @@ import org.eclipse.jgit.merge.MergeChunk; import org.eclipse.jgit.merge.MergeChunk.ConflictState; import org.eclipse.jgit.merge.MergeStrategy; -import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; /** - * Encapsulates the result of a {@link MergeCommand}. + * Encapsulates the result of a {@link org.eclipse.jgit.api.MergeCommand}. */ public class MergeResult { @@ -185,6 +184,7 @@ * @since 3.0 **/ MERGED_NOT_COMMITTED { + @Override public String toString() { return "Merged-not-committed"; //$NON-NLS-1$ } @@ -212,6 +212,7 @@ * files (i.e. local modifications prevent checkout of files). */ CHECKOUT_CONFLICT { + @Override public String toString() { return "Checkout Conflict"; //$NON-NLS-1$ } @@ -247,6 +248,8 @@ private List checkoutConflicts; /** + * Constructor for MergeResult. + * * @param newHead * the object the head points at after the merge * @param base @@ -258,10 +261,10 @@ * @param mergeStatus * the status the merge resulted in * @param mergeStrategy - * the used {@link MergeStrategy} + * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults * merge results as returned by - * {@link ResolveMerger#getMergeResults()} + * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @since 2.0 */ public MergeResult(ObjectId newHead, ObjectId base, @@ -273,6 +276,8 @@ } /** + * Constructor for MergeResult. + * * @param newHead * the object the head points at after the merge * @param base @@ -284,9 +289,10 @@ * @param mergeStatus * the status the merge resulted in * @param mergeStrategy - * the used {@link MergeStrategy} + * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults - * merge results as returned by {@link ResolveMerger#getMergeResults()} + * merge results as returned by + * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @param description * a user friendly description of the merge result */ @@ -300,6 +306,8 @@ } /** + * Constructor for MergeResult. + * * @param newHead * the object the head points at after the merge * @param base @@ -311,13 +319,13 @@ * @param mergeStatus * the status the merge resulted in * @param mergeStrategy - * the used {@link MergeStrategy} + * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults * merge results as returned by - * {@link ResolveMerger#getMergeResults()} + * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @param failingPaths * list of paths causing this merge to fail as returned by - * {@link ResolveMerger#getFailingPaths()} + * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * @param description * a user friendly description of the merge result */ @@ -352,6 +360,8 @@ } /** + * Get the object the head points at after the merge + * * @return the object the head points at after the merge */ public ObjectId getNewHead() { @@ -359,6 +369,8 @@ } /** + * Get the merge status + * * @return the status the merge resulted in */ public MergeStatus getMergeStatus() { @@ -366,6 +378,8 @@ } /** + * Get the commits which have been merged + * * @return all the commits which have been merged together */ public ObjectId[] getMergedCommits() { @@ -373,6 +387,8 @@ } /** + * Get the common base + * * @return base the common base which was used to produce a content-merge. * May be null if the merge-result was produced without * computing a common base @@ -381,6 +397,7 @@ return base; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -400,6 +417,8 @@ } /** + * Set conflicts + * * @param conflicts * the conflicts to set */ @@ -408,25 +427,32 @@ } /** + * Add a conflict + * * @param path + * path of the file to add a conflict for * @param conflictingRanges * the conflicts to set */ public void addConflict(String path, int[][] conflictingRanges) { if (conflicts == null) - conflicts = new HashMap(); + conflicts = new HashMap<>(); conflicts.put(path, conflictingRanges); } /** + * Add a conflict + * * @param path + * path of the file to add a conflict for * @param lowLevelResult + * a {@link org.eclipse.jgit.merge.MergeResult} */ public void addConflict(String path, org.eclipse.jgit.merge.MergeResult lowLevelResult) { if (!lowLevelResult.containsConflicts()) return; if (conflicts == null) - conflicts = new HashMap(); + conflicts = new HashMap<>(); int nrOfConflicts = 0; // just counting for (MergeChunk mergeChunk : lowLevelResult) { @@ -460,9 +486,10 @@ /** * Returns information about the conflicts which occurred during a - * {@link MergeCommand}. The returned value maps the path of a conflicting - * file to a two-dimensional int-array of line-numbers telling where in the - * file conflict markers for which merged commit can be found. + * {@link org.eclipse.jgit.api.MergeCommand}. The returned value maps the + * path of a conflicting file to a two-dimensional int-array of line-numbers + * telling where in the file conflict markers for which merged commit can be + * found. *

* If the returned value contains a mapping "path"->[x][y]=z then this * means @@ -503,7 +530,7 @@ /** * Returns a list of paths causing this merge to fail as returned by - * {@link ResolveMerger#getFailingPaths()} + * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * * @return the list of paths causing this merge to fail or null * if no failure occurred diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,6 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.FIFORevQueue; import org.eclipse.jgit.revwalk.RevCommit; @@ -120,12 +119,13 @@ * Create a new name-rev command. * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected NameRevCommand(Repository repo) { super(repo); mergeCost = MERGE_COST; - prefixes = new ArrayList(2); - revs = new ArrayList(2); + prefixes = new ArrayList<>(2); + revs = new ArrayList<>(2); walk = new RevWalk(repo) { @Override public NameRevCommit createCommit(AnyObjectId id) { @@ -134,10 +134,11 @@ }; } + /** {@inheritDoc} */ @Override public Map call() throws GitAPIException { try { - Map nonCommits = new HashMap(); + Map nonCommits = new HashMap<>(); FIFORevQueue pending = new FIFORevQueue(); if (refs != null) { for (Ref ref : refs) @@ -170,7 +171,7 @@ } Map result = - new LinkedHashMap(revs.size()); + new LinkedHashMap<>(revs.size()); for (ObjectId id : revs) { RevObject o = walk.parseAny(id); if (o instanceof NameRevCommit) { @@ -199,13 +200,13 @@ * @param id * object ID to add. * @return {@code this} - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. - * @throws JGitInternalException + * @throws org.eclipse.jgit.api.errors.JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. + * {@link java.lang.Exception#getCause()}. */ public NameRevCommand add(ObjectId id) throws MissingObjectException, JGitInternalException { @@ -227,13 +228,13 @@ * @param ids * object IDs to add. * @return {@code this} - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. - * @throws JGitInternalException + * @throws org.eclipse.jgit.api.errors.JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. + * {@link java.lang.Exception#getCause()}. */ public NameRevCommand add(Iterable ids) throws MissingObjectException, JGitInternalException { @@ -250,7 +251,8 @@ * prefix added by {@link #addPrefix(String)}. * * @param prefix - * prefix to add; see {@link RefDatabase#getRefs(String)} + * prefix to add; see + * {@link org.eclipse.jgit.lib.RefDatabase#getRefs(String)} * @return {@code this} */ public NameRevCommand addPrefix(String prefix) { @@ -260,8 +262,8 @@ } /** - * Add all annotated tags under {@code refs/tags/} to the set that all results - * must match. + * Add all annotated tags under {@code refs/tags/} to the set that all + * results must match. *

* Calls {@link #addRef(Ref)}; see that method for a note on matching * priority. @@ -270,12 +272,12 @@ * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling - * {@link Exception#getCause()}. + * {@link java.lang.Exception#getCause()}. */ public NameRevCommand addAnnotatedTags() { checkCallable(); if (refs == null) - refs = new ArrayList(); + refs = new ArrayList<>(); try { for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_TAGS).values()) { ObjectId id = ref.getObjectId(); @@ -302,7 +304,7 @@ public NameRevCommand addRef(Ref ref) { checkCallable(); if (refs == null) - refs = new ArrayList(); + refs = new ArrayList<>(); refs.add(ref); return this; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,7 @@ /* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, Mathias Kinzler + * Copyright (C) 2016, Laurent Delaigue * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -46,9 +47,11 @@ import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.MergeCommand.FastForwardMode; +import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge; import org.eclipse.jgit.api.RebaseCommand.Operation; import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; @@ -59,6 +62,7 @@ import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; @@ -67,8 +71,10 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.TagOpt; /** * The Pull command @@ -82,7 +88,7 @@ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; - private PullRebaseMode pullRebaseMode = null; + private BranchRebaseMode pullRebaseMode = null; private String remote; @@ -90,41 +96,25 @@ private MergeStrategy strategy = MergeStrategy.RECURSIVE; - private enum PullRebaseMode implements Config.ConfigEnum { - REBASE_PRESERVE("preserve", true, true), //$NON-NLS-1$ - REBASE("true", true, false), //$NON-NLS-1$ - NO_REBASE("false", false, false); //$NON-NLS-1$ + private TagOpt tagOption; - private final String configValue; + private FastForwardMode fastForwardMode; - private final boolean rebase; - - private final boolean preserveMerges; - - PullRebaseMode(String configValue, boolean rebase, - boolean preserveMerges) { - this.configValue = configValue; - this.rebase = rebase; - this.preserveMerges = preserveMerges; - } - - public String toConfigValue() { - return configValue; - } - - public boolean matchConfigValue(String in) { - return in.equals(configValue); - } - } + private FetchRecurseSubmodulesMode submoduleRecurseMode = null; /** + * Constructor for PullCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected PullCommand(Repository repo) { super(repo); } /** + * Set progress monitor + * * @param monitor * a progress monitor * @return this instance @@ -153,91 +143,125 @@ * branch.[name].rebase and branch.autosetuprebase. * * @param useRebase + * whether to use rebase after fetching * @return {@code this} */ public PullCommand setRebase(boolean useRebase) { checkCallable(); - pullRebaseMode = useRebase ? PullRebaseMode.REBASE : PullRebaseMode.NO_REBASE; + pullRebaseMode = useRebase ? BranchRebaseMode.REBASE + : BranchRebaseMode.NONE; return this; } /** - * Executes the {@code Pull} command with all the options and parameters + * Sets the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} to + * use after fetching. + * + *

+ *
BranchRebaseMode.REBASE
+ *
Equivalent to {@code --rebase} on the command line: use rebase + * instead of merge after fetching.
+ *
BranchRebaseMode.PRESERVE
+ *
Equivalent to {@code --preserve-merges} on the command line: rebase + * preserving local merge commits.
+ *
BranchRebaseMode.INTERACTIVE
+ *
Equivalent to {@code --interactive} on the command line: use + * interactive rebase.
+ *
BranchRebaseMode.NONE
+ *
Equivalent to {@code --no-rebase}: merge instead of rebasing. + *
{@code null}
+ *
Use the setting defined in the git configuration, either {@code + * branch.[name].rebase} or, if not set, {@code pull.rebase}
+ *
+ * + * This setting overrides the settings in the configuration file. By + * default, the setting in the repository configuration file is used. + *

+ * A branch can be configured to use rebase by default. See + * {@code branch.[name].rebase}, {@code branch.autosetuprebase}, and + * {@code pull.rebase}. + * + * @param rebaseMode + * the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} + * to use + * @return {@code this} + * @since 4.5 + */ + public PullCommand setRebase(BranchRebaseMode rebaseMode) { + checkCallable(); + pullRebaseMode = rebaseMode; + return this; + } + + /** + * {@inheritDoc} + *

+ * Execute the {@code Pull} command with all the options and parameters * collected by the setter methods (e.g. * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each * instance of this class should only be used for one invocation of the * command. Don't call this method twice on an instance. - * - * @return the result of the pull - * @throws WrongRepositoryStateException - * @throws InvalidConfigurationException - * @throws DetachedHeadException - * @throws InvalidRemoteException - * @throws CanceledException - * @throws RefNotFoundException - * @throws RefNotAdvertisedException - * @throws NoHeadException - * @throws org.eclipse.jgit.api.errors.TransportException - * @throws GitAPIException */ + @Override public PullResult call() throws GitAPIException, WrongRepositoryStateException, InvalidConfigurationException, - DetachedHeadException, InvalidRemoteException, CanceledException, + InvalidRemoteException, CanceledException, RefNotFoundException, RefNotAdvertisedException, NoHeadException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); monitor.beginTask(JGitText.get().pullTaskName, 2); + Config repoConfig = repo.getConfig(); - String branchName; + String branchName = null; try { String fullBranch = repo.getFullBranch(); - if (fullBranch == null) - throw new NoHeadException( - JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported); - if (!fullBranch.startsWith(Constants.R_HEADS)) { - // we can not pull if HEAD is detached and branch is not - // specified explicitly - throw new DetachedHeadException(); + if (fullBranch != null + && fullBranch.startsWith(Constants.R_HEADS)) { + branchName = fullBranch.substring(Constants.R_HEADS.length()); } - branchName = fullBranch.substring(Constants.R_HEADS.length()); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, e); } + if (remoteBranchName == null && branchName != null) { + // get the name of the branch in the remote repository + // stored in configuration key branch..merge + remoteBranchName = repoConfig.getString( + ConfigConstants.CONFIG_BRANCH_SECTION, branchName, + ConfigConstants.CONFIG_KEY_MERGE); + } + if (remoteBranchName == null) { + remoteBranchName = branchName; + } + if (remoteBranchName == null) { + throw new NoHeadException( + JGitText.get().cannotCheckoutFromUnbornBranch); + } if (!repo.getRepositoryState().equals(RepositoryState.SAFE)) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().cannotPullOnARepoWithState, repo .getRepositoryState().name())); - Config repoConfig = repo.getConfig(); - if (remote == null) { + if (remote == null && branchName != null) { // get the configured remote for the currently checked out branch // stored in configuration key branch..remote remote = repoConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE); } - if (remote == null) + if (remote == null) { // fall back to default remote remote = Constants.DEFAULT_REMOTE_NAME; - - if (remoteBranchName == null) - // get the name of the branch in the remote repository - // stored in configuration key branch..merge - remoteBranchName = repoConfig.getString( - ConfigConstants.CONFIG_BRANCH_SECTION, branchName, - ConfigConstants.CONFIG_KEY_MERGE); + } // determines whether rebase should be used after fetching - if (pullRebaseMode == null) { + if (pullRebaseMode == null && branchName != null) { pullRebaseMode = getRebaseMode(branchName, repoConfig); } - if (remoteBranchName == null) - remoteBranchName = branchName; final boolean isRemote = !remote.equals("."); //$NON-NLS-1$ String remoteUri; @@ -258,9 +282,9 @@ JGitText.get().operationCanceled, JGitText.get().pullTaskName)); - FetchCommand fetch = new FetchCommand(repo); - fetch.setRemote(remote); - fetch.setProgressMonitor(monitor); + FetchCommand fetch = new FetchCommand(repo).setRemote(remote) + .setProgressMonitor(monitor).setTagOpt(tagOption) + .setRecurseSubmodules(submoduleRecurseMode); configure(fetch); fetchRes = fetch.call(); @@ -314,19 +338,20 @@ Repository.shortenRefName(remoteBranchName), remoteUri); PullResult result; - if (pullRebaseMode.rebase) { + if (pullRebaseMode != BranchRebaseMode.NONE) { RebaseCommand rebase = new RebaseCommand(repo); RebaseResult rebaseRes = rebase.setUpstream(commitToMerge) .setUpstreamName(upstreamName).setProgressMonitor(monitor) .setOperation(Operation.BEGIN).setStrategy(strategy) - .setPreserveMerges(pullRebaseMode.preserveMerges) + .setPreserveMerges( + pullRebaseMode == BranchRebaseMode.PRESERVE) .call(); result = new PullResult(fetchRes, remote, rebaseRes); } else { MergeCommand merge = new MergeCommand(repo); - merge.include(upstreamName, commitToMerge); - merge.setStrategy(strategy); - MergeResult mergeRes = merge.call(); + MergeResult mergeRes = merge.include(upstreamName, commitToMerge) + .setStrategy(strategy).setProgressMonitor(monitor) + .setFastForward(getFastForwardMode()).call(); monitor.update(1); result = new PullResult(fetchRes, remote, mergeRes); } @@ -342,6 +367,7 @@ * * @see Constants#DEFAULT_REMOTE_NAME * @param remote + * name of the remote to pull from * @return {@code this} * @since 3.3 */ @@ -358,6 +384,7 @@ * the current branch is used. * * @param remoteBranchName + * remote branch name to be used for pull operation * @return {@code this} * @since 3.3 */ @@ -368,6 +395,8 @@ } /** + * Get the remote name used for pull operation + * * @return the remote used for the pull operation if it was set explicitly * @since 3.3 */ @@ -376,6 +405,8 @@ } /** + * Get the remote branch name for the pull operation + * * @return the remote branch name used for the pull operation if it was set * explicitly * @since 3.3 @@ -385,6 +416,8 @@ } /** + * Set the @{code MergeStrategy} + * * @param strategy * The merge strategy to use during this pull operation. * @return {@code this} @@ -395,13 +428,93 @@ return this; } - private static PullRebaseMode getRebaseMode(String branchName, Config config) { - PullRebaseMode mode = config.getEnum(PullRebaseMode.values(), - ConfigConstants.CONFIG_PULL_SECTION, null, - ConfigConstants.CONFIG_KEY_REBASE, PullRebaseMode.NO_REBASE); - mode = config.getEnum(PullRebaseMode.values(), + /** + * Set the specification of annotated tag behavior during fetch + * + * @param tagOpt + * the {@link org.eclipse.jgit.transport.TagOpt} + * @return {@code this} + * @since 4.7 + */ + public PullCommand setTagOpt(TagOpt tagOpt) { + checkCallable(); + this.tagOption = tagOpt; + return this; + } + + /** + * Set the fast forward mode. It is used if pull is configured to do a merge + * as opposed to rebase. If non-{@code null} takes precedence over the + * fast-forward mode configured in git config. + * + * @param fastForwardMode + * corresponds to the --ff/--no-ff/--ff-only options. If + * {@code null} use the value of {@code pull.ff} configured in + * git config. If {@code pull.ff} is not configured fall back to + * the value of {@code merge.ff}. If {@code merge.ff} is not + * configured --ff is the built-in default. + * @return {@code this} + * @since 4.9 + */ + public PullCommand setFastForward( + @Nullable FastForwardMode fastForwardMode) { + checkCallable(); + this.fastForwardMode = fastForwardMode; + return this; + } + + /** + * Set the mode to be used for recursing into submodules. + * + * @param recurse + * the + * {@link org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode} + * to be used for recursing into submodules + * @return {@code this} + * @since 4.7 + * @see FetchCommand#setRecurseSubmodules(FetchRecurseSubmodulesMode) + */ + public PullCommand setRecurseSubmodules( + @Nullable FetchRecurseSubmodulesMode recurse) { + this.submoduleRecurseMode = recurse; + return this; + } + + /** + * Reads the rebase mode to use for a pull command from the repository + * configuration. This is the value defined for the configurations + * {@code branch.[branchName].rebase}, or,if not set, {@code pull.rebase}. + * If neither is set, yields + * {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode#NONE}. + * + * @param branchName + * name of the local branch + * @param config + * the {@link org.eclipse.jgit.lib.Config} to read the value from + * @return the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} + * @since 4.5 + */ + public static BranchRebaseMode getRebaseMode(String branchName, + Config config) { + BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(), ConfigConstants.CONFIG_BRANCH_SECTION, - branchName, ConfigConstants.CONFIG_KEY_REBASE, mode); + branchName, ConfigConstants.CONFIG_KEY_REBASE, null); + if (mode == null) { + mode = config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_PULL_SECTION, null, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); + } return mode; } + + private FastForwardMode getFastForwardMode() { + if (fastForwardMode != null) { + return fastForwardMode; + } + Config config = repo.getConfig(); + Merge ffMode = config.getEnum(Merge.values(), + ConfigConstants.CONFIG_PULL_SECTION, null, + ConfigConstants.CONFIG_KEY_FF, null); + return ffMode != null ? FastForwardMode.valueOf(ffMode) : null; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,7 @@ import org.eclipse.jgit.transport.FetchResult; /** - * Encapsulates the result of a {@link PullCommand} + * Encapsulates the result of a {@link org.eclipse.jgit.api.PullCommand} */ public class PullResult { private final FetchResult fetchResult; @@ -73,6 +73,8 @@ } /** + * Get fetch result + * * @return the fetch result, or null */ public FetchResult getFetchResult() { @@ -80,6 +82,8 @@ } /** + * Get merge result + * * @return the merge result, or null */ public MergeResult getMergeResult() { @@ -87,6 +91,8 @@ } /** + * Get rebase result + * * @return the rebase result, or null */ public RebaseResult getRebaseResult() { @@ -94,6 +100,8 @@ } /** + * Get name of the remote configuration from which fetch was tried + * * @return the name of the remote configuration from which fetch was tried, * or null */ @@ -102,6 +110,8 @@ } /** + * Whether the pull was successful + * * @return whether the pull was successful */ public boolean isSuccessful() { @@ -112,6 +122,7 @@ return true; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,14 +47,18 @@ import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; @@ -64,6 +68,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.RemoteRefUpdate; @@ -84,45 +89,50 @@ private final List refSpecs; + private final Map refLeaseSpecs; + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; private boolean dryRun; - + private boolean atomic; private boolean force; - private boolean thin = Transport.DEFAULT_PUSH_THIN; private OutputStream out; + private List pushOptions; + /** + *

+ * Constructor for PushCommand. + *

+ * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected PushCommand(Repository repo) { super(repo); - refSpecs = new ArrayList(3); + refSpecs = new ArrayList<>(3); + refLeaseSpecs = new HashMap<>(); } /** - * Executes the {@code push} command with all the options and parameters + * {@inheritDoc} + *

+ * Execute the {@code push} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) - * - * @return an iteration over {@link PushResult} objects - * @throws InvalidRemoteException - * when called with an invalid remote uri - * @throws org.eclipse.jgit.api.errors.TransportException - * when an error occurs with the transport - * @throws GitAPIException */ + @Override public Iterable call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); - ArrayList pushResults = new ArrayList(3); + ArrayList pushResults = new ArrayList<>(3); try { if (refSpecs.isEmpty()) { @@ -131,7 +141,7 @@ refSpecs.addAll(config.getPushRefSpecs()); } if (refSpecs.isEmpty()) { - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) refSpecs.add(new RefSpec(head.getLeaf().getName())); } @@ -145,13 +155,15 @@ transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); for (final Transport transport : transports) { transport.setPushThin(thin); + transport.setPushAtomic(atomic); if (receivePack != null) transport.setOptionReceivePack(receivePack); transport.setDryRun(dryRun); + transport.setPushOptions(pushOptions); configure(transport); final Collection toPush = transport - .findRemoteRefUpdatesFor(refSpecs); + .findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs); try { PushResult result = transport.push(monitor, toPush, out); @@ -160,6 +172,9 @@ } catch (TooLargePackException e) { throw new org.eclipse.jgit.api.errors.TooLargePackException( e.getMessage(), e); + } catch (TooLargeObjectInPackException e) { + throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException( + e.getMessage(), e); } catch (TransportException e) { throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); @@ -185,7 +200,6 @@ } return pushResults; - } /** @@ -195,6 +209,7 @@ * * @see Constants#DEFAULT_REMOTE_NAME * @param remote + * the remote name * @return {@code this} */ public PushCommand setRemote(String remote) { @@ -204,6 +219,8 @@ } /** + * Get remote name + * * @return the remote used for the remote operation */ public String getRemote() { @@ -217,6 +234,8 @@ * * @see RemoteConfig#DEFAULT_RECEIVE_PACK * @param receivePack + * name of the remote executable providing the receive-pack + * service * @return {@code this} */ public PushCommand setReceivePack(String receivePack) { @@ -226,6 +245,8 @@ } /** + * Get the name of the remote executable providing the receive-pack service + * * @return the receive-pack used for the remote operation */ public String getReceivePack() { @@ -233,6 +254,8 @@ } /** + * Get timeout used for push operation + * * @return the timeout used for the push operation */ public int getTimeout() { @@ -240,6 +263,8 @@ } /** + * Get the progress monitor + * * @return the progress monitor for the push operation */ public ProgressMonitor getProgressMonitor() { @@ -251,8 +276,8 @@ * is set to NullProgressMonitor * * @see NullProgressMonitor - * * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public PushCommand setProgressMonitor(ProgressMonitor monitor) { @@ -265,6 +290,49 @@ } /** + * Get the RefLeaseSpecs. + * + * @return the RefLeaseSpecs + * @since 4.7 + */ + public List getRefLeaseSpecs() { + return new ArrayList<>(refLeaseSpecs.values()); + } + + /** + * The ref lease specs to be used in the push operation, for a + * force-with-lease push operation. + * + * @param specs + * a {@link org.eclipse.jgit.transport.RefLeaseSpec} object. + * @return {@code this} + * @since 4.7 + */ + public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) { + return setRefLeaseSpecs(Arrays.asList(specs)); + } + + /** + * The ref lease specs to be used in the push operation, for a + * force-with-lease push operation. + * + * @param specs + * list of {@code RefLeaseSpec}s + * @return {@code this} + * @since 4.7 + */ + public PushCommand setRefLeaseSpecs(List specs) { + checkCallable(); + this.refLeaseSpecs.clear(); + for (RefLeaseSpec spec : specs) { + refLeaseSpecs.put(spec.getRef(), spec); + } + return this; + } + + /** + * Get {@code RefSpec}s. + * * @return the ref specs */ public List getRefSpecs() { @@ -274,7 +342,7 @@ /** * The ref specs to be used in the push operation * - * @param specs + * @param specs a {@link org.eclipse.jgit.transport.RefSpec} object. * @return {@code this} */ public PushCommand setRefSpecs(RefSpec... specs) { @@ -288,6 +356,7 @@ * The ref specs to be used in the push operation * * @param specs + * list of {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public PushCommand setRefSpecs(List specs) { @@ -344,7 +413,7 @@ } else { Ref src; try { - src = repo.getRef(nameOrSpec); + src = repo.findRef(nameOrSpec); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, @@ -357,6 +426,8 @@ } /** + * Whether to run the push operation as a dry run + * * @return the dry run preference for the push operation */ public boolean isDryRun() { @@ -366,7 +437,7 @@ /** * Sets whether the push operation should be a dry run * - * @param dryRun + * @param dryRun a boolean. * @return {@code this} */ public PushCommand setDryRun(boolean dryRun) { @@ -376,6 +447,8 @@ } /** + * Get the thin-pack preference + * * @return the thin-pack preference for push operation */ public boolean isThin() { @@ -383,11 +456,12 @@ } /** - * Sets the thin-pack preference for push operation. + * Set the thin-pack preference for push operation. * * Default setting is Transport.DEFAULT_PUSH_THIN * * @param thin + * the thin-pack preference value * @return {@code this} */ public PushCommand setThin(boolean thin) { @@ -397,6 +471,35 @@ } /** + * Whether this push should be executed atomically (all references updated, + * or none) + * + * @return true if all-or-nothing behavior is requested. + * @since 4.2 + */ + public boolean isAtomic() { + return atomic; + } + + /** + * Requests atomic push (all references updated, or no updates). + * + * Default setting is false. + * + * @param atomic + * whether to run the push atomically + * @return {@code this} + * @since 4.2 + */ + public PushCommand setAtomic(boolean atomic) { + checkCallable(); + this.atomic = atomic; + return this; + } + + /** + * Whether to push forcefully + * * @return the force preference for push operation */ public boolean isForce() { @@ -407,6 +510,7 @@ * Sets the force preference for push operation. * * @param force + * whether to push forcefully * @return {@code this} */ public PushCommand setForce(boolean force) { @@ -419,6 +523,7 @@ * Sets the output stream to write sideband messages to * * @param out + * an {@link java.io.OutputStream} * @return {@code this} * @since 3.0 */ @@ -426,4 +531,27 @@ this.out = out; return this; } + + /** + * Get push options + * + * @return the option strings associated with the push operation + * @since 4.5 + */ + public List getPushOptions() { + return pushOptions; + } + + /** + * Set the option strings associated with the push operation. + * + * @param pushOptions + * a {@link java.util.List} of push option strings + * @return {@code this} + * @since 4.5 + */ + public PushCommand setPushOptions(List pushOptions) { + this.pushOptions = pushOptions; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2010, 2013 Mathias Kinzler + * Copyright (C) 2016, Laurent Delaigue * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -237,7 +238,12 @@ private boolean preserveMerges = false; /** + *

+ * Constructor for RebaseCommand. + *

+ * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected RebaseCommand(Repository repo) { super(repo); @@ -246,17 +252,14 @@ } /** + * {@inheritDoc} + *

* Executes the {@code Rebase} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command. Don't call * this method twice on an instance. - * - * @return an object describing the result of this command - * @throws GitAPIException - * @throws WrongRepositoryStateException - * @throws NoHeadException - * @throws RefNotFoundException */ + @Override public RebaseResult call() throws GitAPIException, NoHeadException, RefNotFoundException, WrongRepositoryStateException { newHead = null; @@ -297,7 +300,7 @@ org.eclipse.jgit.api.Status status = Git.wrap(repo) .status().setIgnoreSubmodules(IgnoreSubmoduleMode.ALL).call(); if (status.hasUncommittedChanges()) { - List list = new ArrayList(); + List list = new ArrayList<>(); list.addAll(status.getUncommittedChanges()); return RebaseResult.uncommittedChanges(list); } @@ -399,8 +402,8 @@ boolean conflicts = false; if (rebaseState.getFile(AUTOSTASH).exists()) { String stash = rebaseState.readFile(AUTOSTASH); - try { - Git.wrap(repo).stashApply().setStashRef(stash) + try (Git git = Git.wrap(repo)) { + git.stashApply().setStashRef(stash) .ignoreRepositoryState(true).setStrategy(strategy) .call(); } catch (StashApplyFailureException e) { @@ -418,11 +421,12 @@ private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, String refLogMessage) throws IOException { - Ref currentRef = repo.getRef(Constants.R_STASH); + Ref currentRef = repo.exactRef(Constants.R_STASH); RefUpdate refUpdate = repo.updateRef(Constants.R_STASH); refUpdate.setNewObjectId(commitId); refUpdate.setRefLogIdent(refLogIdent); refUpdate.setRefLogMessage(refLogMessage, false); + refUpdate.setForceRefLog(true); if (currentRef != null) refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); else @@ -462,8 +466,10 @@ String oldMessage = commitToPick.getFullMessage(); String newMessage = interactiveHandler .modifyCommitMessage(oldMessage); - newHead = new Git(repo).commit().setMessage(newMessage) - .setAmend(true).setNoVerify(true).call(); + try (Git git = new Git(repo)) { + newHead = git.commit().setMessage(newMessage).setAmend(true) + .setNoVerify(true).call(); + } return null; case EDIT: rebaseState.createFile(AMEND, commitToPick.name()); @@ -560,6 +566,8 @@ lastStepWasForward = newHead != null; if (!lastStepWasForward) { ObjectId headId = getHead().getObjectId(); + // getHead() checks for null + assert headId != null; if (!AnyObjectId.equals(headId, newParents.get(0))) checkoutCommit(headId.getName(), newParents.get(0)); @@ -609,6 +617,7 @@ // their non-first parents rewritten MergeCommand merge = git.merge() .setFastForward(MergeCommand.FastForwardMode.NO_FF) + .setProgressMonitor(monitor) .setCommit(false); for (int i = 1; i < commitToPick.getParentCount(); i++) merge.include(newParents.get(i)); @@ -643,7 +652,7 @@ // Get the rewritten equivalents for the parents of the given commit private List getNewParents(RevCommit commitToPick) throws IOException { - List newParents = new ArrayList(); + List newParents = new ArrayList<>(); for (int p = 0; p < commitToPick.getParentCount(); p++) { String parentHash = commitToPick.getParent(p).getName(); if (!new File(rebaseState.getRewrittenDir(), parentHash).exists()) @@ -668,12 +677,15 @@ } private void writeRewrittenHashes() throws RevisionSyntaxException, - IOException { + IOException, RefNotFoundException { File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT); if (!currentCommitFile.exists()) return; - String head = repo.resolve(Constants.HEAD).getName(); + ObjectId headId = getHead().getObjectId(); + // getHead() checks for null + assert headId != null; + String head = headId.getName(); String currentCommits = rebaseState.readFile(CURRENT_COMMIT); for (String current : currentCommits.split("\n")) //$NON-NLS-1$ RebaseState @@ -686,6 +698,7 @@ String headName = rebaseState.readFile(HEAD_NAME); updateHead(headName, finalHead, upstreamCommit); boolean stashConflicts = autoStashApply(); + getRepository().autoGC(monitor); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); if (stashConflicts) return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; @@ -743,19 +756,19 @@ private void resetSoftToParent() throws IOException, GitAPIException, CheckoutConflictException { - Ref orig_head = repo.getRef(Constants.ORIG_HEAD); - ObjectId orig_headId = orig_head.getObjectId(); - try { - // we have already commited the cherry-picked commit. + Ref ref = repo.exactRef(Constants.ORIG_HEAD); + ObjectId orig_head = ref == null ? null : ref.getObjectId(); + try (Git git = Git.wrap(repo)) { + // we have already committed the cherry-picked commit. // what we need is to have changes introduced by this // commit to be on the index // resetting is a workaround - Git.wrap(repo).reset().setMode(ResetType.SOFT) + git.reset().setMode(ResetType.SOFT) .setRef("HEAD~1").call(); //$NON-NLS-1$ } finally { // set ORIG_HEAD back to where we started because soft // reset moved it - repo.writeOrigHead(orig_headId); + repo.writeOrigHead(orig_head); } } @@ -795,8 +808,12 @@ if (!line.trim().startsWith("#")) //$NON-NLS-1$ result.append(line).append("\n"); //$NON-NLS-1$ } - if (!commitMessage.endsWith("\n")) //$NON-NLS-1$ - result.deleteCharAt(result.length() - 1); + if (!commitMessage.endsWith("\n")) { //$NON-NLS-1$ + int bufferSize = result.length(); + if (bufferSize > 0 && result.charAt(bufferSize - 1) == '\n') { + result.deleteCharAt(bufferSize - 1); + } + } return result.toString(); } @@ -915,6 +932,7 @@ try { DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree); dco.setFailOnConflict(false); + dco.setProgressMonitor(monitor); boolean needsDeleteFiles = dco.checkout(); if (needsDeleteFiles) { List fileList = dco.getToBeDeleted(); @@ -980,6 +998,9 @@ try { raw = IO.readFully(authorScriptFile); } catch (FileNotFoundException notFound) { + if (authorScriptFile.exists()) { + throw notFound; + } return null; } return parseAuthor(raw); @@ -1042,8 +1063,8 @@ private void popSteps(int numSteps) throws IOException { if (numSteps == 0) return; - List todoLines = new LinkedList(); - List poppedLines = new LinkedList(); + List todoLines = new LinkedList<>(); + List poppedLines = new LinkedList<>(); for (RebaseTodoLine line : repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), true)) { @@ -1069,11 +1090,12 @@ Ref head = getHead(); - String headName = getHeadName(head); ObjectId headId = head.getObjectId(); - if (headId == null) + if (headId == null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); + } + String headName = getHeadName(head); RevCommit headCommit = walk.lookupCommit(headId); RevCommit upstream = walk.lookupCommit(upstreamCommit.getId()); @@ -1107,7 +1129,7 @@ } rebaseState.createFile(QUIET, ""); //$NON-NLS-1$ - ArrayList toDoSteps = new ArrayList(); + ArrayList toDoSteps = new ArrayList<>(); toDoSteps.add(new RebaseTodoLine("# Created by EGit: rebasing " + headId.name() //$NON-NLS-1$ + " onto " + upstreamCommit.name())); //$NON-NLS-1$ // determine the commits to be applied @@ -1143,7 +1165,7 @@ LogCommand cmd = git.log().addRange(upstreamCommit, headCommit); commitsToUse = cmd.call(); } - List cherryPickList = new ArrayList(); + List cherryPickList = new ArrayList<>(); for (RevCommit commit : commitsToUse) { if (preserveMerges || commit.getParentCount() == 1) cherryPickList.add(commit); @@ -1184,15 +1206,19 @@ private static String getHeadName(Ref head) { String headName; - if (head.isSymbolic()) + if (head.isSymbolic()) { headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + } else { + ObjectId headId = head.getObjectId(); + // the callers are checking this already + assert headId != null; + headName = headId.getName(); + } return headName; } private Ref getHead() throws IOException, RefNotFoundException { - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); if (head == null || head.getObjectId() == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); @@ -1204,12 +1230,14 @@ } /** - * checks if we can fast-forward and returns the new head if it is possible + * Check if we can fast-forward and returns the new head if it is possible * * @param newCommit + * a {@link org.eclipse.jgit.revwalk.RevCommit} object to check + * if we can fast-forward to. * @return the new head, or null - * @throws IOException - * @throws GitAPIException + * @throws java.io.IOException + * @throws org.eclipse.jgit.api.errors.GitAPIException */ public RevCommit tryFastForward(RevCommit newCommit) throws IOException, GitAPIException { @@ -1238,6 +1266,7 @@ CheckoutCommand co = new CheckoutCommand(repo); try { + co.setProgressMonitor(monitor); co.setName(newCommit.name()).call(); if (headName.startsWith(Constants.R_HEADS)) { RefUpdate rup = repo.updateRef(headName); @@ -1380,6 +1409,7 @@ DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(), repo.lockDirCache(), commit.getTree()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); try { dco.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { @@ -1412,6 +1442,8 @@ /** + * Set upstream {@code RevCommit} + * * @param upstream * the upstream commit * @return {@code this} @@ -1423,6 +1455,8 @@ } /** + * Set the upstream commit + * * @param upstream * id of the upstream commit * @return {@code this} @@ -1440,10 +1474,12 @@ } /** + * Set the upstream branch + * * @param upstream - * the upstream branch + * the name of the upstream branch * @return {@code this} - * @throws RefNotFoundException + * @throws org.eclipse.jgit.api.errors.RefNotFoundException */ public RebaseCommand setUpstream(String upstream) throws RefNotFoundException { @@ -1478,6 +1514,8 @@ } /** + * Set the operation to execute during rebase + * * @param operation * the operation to perform * @return {@code this} @@ -1488,6 +1526,8 @@ } /** + * Set progress monitor + * * @param monitor * a progress monitor * @return this instance @@ -1501,15 +1541,18 @@ } /** - * Enables interactive rebase + * Enable interactive rebase *

* Does not stop after initialization of interactive rebase. This is * equivalent to - * {@link RebaseCommand#runInteractively(InteractiveHandler, boolean) + * {@link org.eclipse.jgit.api.RebaseCommand#runInteractively(InteractiveHandler, boolean) * runInteractively(handler, false)}; *

* * @param handler + * the + * {@link org.eclipse.jgit.api.RebaseCommand.InteractiveHandler} + * to use * @return this */ public RebaseCommand runInteractively(InteractiveHandler handler) { @@ -1517,14 +1560,17 @@ } /** - * Enables interactive rebase + * Enable interactive rebase *

* If stopAfterRebaseInteractiveInitialization is {@code true} the rebase * stops after initialization of interactive rebase returning - * {@link RebaseResult#INTERACTIVE_PREPARED_RESULT} + * {@link org.eclipse.jgit.api.RebaseResult#INTERACTIVE_PREPARED_RESULT} *

* * @param handler + * the + * {@link org.eclipse.jgit.api.RebaseCommand.InteractiveHandler} + * to use * @param stopAfterRebaseInteractiveInitialization * if {@code true} the rebase stops after initialization * @return this instance @@ -1538,6 +1584,8 @@ } /** + * Set the MergeStrategy. + * * @param strategy * The merge strategy to use during this rebase operation. * @return {@code this} @@ -1549,9 +1597,11 @@ } /** + * Whether to preserve merges during rebase + * * @param preserve - * True to re-create merges during rebase. Defaults to false, a - * flattening rebase. + * {@code true} to re-create merges during rebase. Defaults to + * {@code false}, a flattening rebase. * @return {@code this} * @since 3.5 */ @@ -1586,7 +1636,7 @@ if (raw.length == 0) return null; - Map keyValueMap = new HashMap(); + Map keyValueMap = new HashMap<>(); for (int p = 0; p < raw.length;) { int end = RawParseUtils.nextLF(raw, p); if (end == p) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,12 +45,11 @@ import java.util.List; import java.util.Map; -import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; /** - * The result of a {@link RebaseCommand} execution + * The result of a {@link org.eclipse.jgit.api.RebaseCommand} execution */ public class RebaseResult { /** @@ -279,6 +278,8 @@ } /** + * Get the status + * * @return the overall status */ public Status getStatus() { @@ -286,33 +287,46 @@ } /** - * @return the current commit if status is {@link Status#STOPPED}, otherwise - * null + * Get the current commit if status is + * {@link org.eclipse.jgit.api.RebaseResult.Status#STOPPED}, otherwise + * null + * + * @return the current commit if status is + * {@link org.eclipse.jgit.api.RebaseResult.Status#STOPPED}, + * otherwise null */ public RevCommit getCurrentCommit() { return currentCommit; } /** + * Get the list of paths causing this rebase to fail + * * @return the list of paths causing this rebase to fail (see - * {@link ResolveMerger#getFailingPaths()} for details) if status is - * {@link Status#FAILED}, otherwise null + * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} + * for details) if status is + * {@link org.eclipse.jgit.api.RebaseResult.Status#FAILED}, + * otherwise null */ public Map getFailingPaths() { return failingPaths; } /** - * @return the list of conflicts if status is {@link Status#CONFLICTS} + * Get the list of conflicts + * + * @return the list of conflicts if status is + * {@link org.eclipse.jgit.api.RebaseResult.Status#CONFLICTS} */ public List getConflicts() { return conflicts; } /** - * @return the list of uncommitted changes if status is - * {@link Status#UNCOMMITTED_CHANGES} + *

Getter for the field uncommittedChanges.

* + * @return the list of uncommitted changes if status is + * {@link org.eclipse.jgit.api.RebaseResult.Status#UNCOMMITTED_CHANGES} * @since 3.2 */ public List getUncommittedChanges() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,7 +67,10 @@ private String ref = Constants.HEAD; /** + * Constructor for ReflogCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public ReflogCommand(Repository repo) { super(repo); @@ -78,6 +81,7 @@ * value of HEAD will be used. * * @param ref + * the name of the {@code Ref} to log * @return {@code this} */ public ReflogCommand setRef(String ref) { @@ -87,11 +91,11 @@ } /** + * {@inheritDoc} + *

* Run the reflog command - * - * @throws GitAPIException - * @throws InvalidRefNameException */ + @Override public Collection call() throws GitAPIException, InvalidRefNameException { checkCallable(); @@ -108,4 +112,4 @@ } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +/** + * Used to add a new remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see Git + * documentation about Remote + * @since 4.2 + */ +public class RemoteAddCommand extends GitCommand { + + private String name; + + private URIish uri; + + /** + * Constructor for RemoteAddCommand. + * + * @param repo + * the {@link org.eclipse.jgit.lib.Repository} + */ + protected RemoteAddCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to add. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * The URL of the repository for the new remote. + * + * @param uri + * an URL for the remote + */ + public void setUri(URIish uri) { + this.uri = uri; + } + + /** + * {@inheritDoc} + *

+ * Executes the {@code remote add} command with all the options and + * parameters collected by the setter methods of this class. + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + + RefSpec refSpec = new RefSpec(); + refSpec = refSpec.setForceUpdate(true); + refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$ + Constants.R_REMOTES + name + "/*"); //$NON-NLS-1$ + remote.addFetchRefSpec(refSpec); + + remote.addURI(uri); + + remote.update(config); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.net.URISyntaxException; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteConfig; + +/** + * Used to obtain the list of remotes. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see Git + * documentation about Remote + * @since 4.2 + */ +public class RemoteListCommand extends GitCommand> { + + /** + *

+ * Constructor for RemoteListCommand. + *

+ * + * @param repo + * the {@link org.eclipse.jgit.lib.Repository} + */ + protected RemoteListCommand(Repository repo) { + super(repo); + } + + /** + * {@inheritDoc} + *

+ * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + */ + @Override + public List call() throws GitAPIException { + checkCallable(); + + try { + return RemoteConfig.getAllRemoteConfigs(repo.getConfig()); + } catch (URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RemoteConfig; + +/** + * Used to remove an existing remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see Git + * documentation about Remote + * @since 4.2 + */ +public class RemoteRemoveCommand extends GitCommand { + + private String name; + + /** + *

+ * Constructor for RemoteRemoveCommand. + *

+ * + * @param repo + * the {@link org.eclipse.jgit.lib.Repository} + */ + protected RemoteRemoveCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to remove. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * {@inheritDoc} + *

+ * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, name); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +/** + * Used to to change the URL of a remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see Git + * documentation about Remote + * @since 4.2 + */ +public class RemoteSetUrlCommand extends GitCommand { + + private String name; + + private URIish uri; + + private boolean push; + + /** + *

+ * Constructor for RemoteSetUrlCommand. + *

+ * + * @param repo + * the {@link org.eclipse.jgit.lib.Repository} + */ + protected RemoteSetUrlCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to change the URL for. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * The new URL for the remote. + * + * @param uri + * an URL for the remote + */ + public void setUri(URIish uri) { + this.uri = uri; + } + + /** + * Whether to change the push URL of the remote instead of the fetch URL. + * + * @param push + * true to set the push url, false to + * set the fetch url + */ + public void setPush(boolean push) { + this.push = push; + } + + /** + * {@inheritDoc} + *

+ * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + if (push) { + List uris = remote.getPushURIs(); + if (uris.size() > 1) { + throw new JGitInternalException( + "remote.newtest.pushurl has multiple values"); //$NON-NLS-1$ + } else if (uris.size() == 1) { + remote.removePushURI(uris.get(0)); + } + remote.addPushURI(uri); + } else { + List uris = remote.getURIs(); + if (uris.size() > 1) { + throw new JGitInternalException( + "remote.newtest.url has multiple values"); //$NON-NLS-1$ + } else if (uris.size() == 1) { + remote.removeURI(uris.get(0)); + } + remote.addURI(uri); + } + + remote.update(config); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,13 +46,9 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -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.notes.Note; import org.eclipse.jgit.notes.NoteMap; @@ -73,26 +69,34 @@ private String notesRef = Constants.R_NOTES_COMMITS; /** + *

+ * Constructor for RemoveNoteCommand. + *

+ * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoveNoteCommand(Repository repo) { super(repo); } + /** {@inheritDoc} */ + @Override public Note call() throws GitAPIException { checkCallable(); try (RevWalk walk = new RevWalk(repo); ObjectInserter inserter = repo.newObjectInserter()) { NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; - Ref ref = repo.getRef(notesRef); + Ref ref = repo.exactRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } map.set(id, null, inserter); - commitNoteMap(walk, map, notesCommit, inserter, + AddNoteCommand.commitNoteMap(repo, notesRef, walk, map, notesCommit, + inserter, "Notes removed by 'git notes remove'"); //$NON-NLS-1$ return map.getNote(id); } catch (IOException e) { @@ -104,6 +108,8 @@ * Sets the object id of object you want to remove a note * * @param id + * the {@link org.eclipse.jgit.revwalk.RevObject} to remove a + * note from. * @return {@code this} */ public RemoveNoteCommand setObjectId(RevObject id) { @@ -112,37 +118,14 @@ return this; } - private void commitNoteMap(RevWalk walk, NoteMap map, - RevCommit notesCommit, - ObjectInserter inserter, - String msg) - throws IOException { - // commit the note - CommitBuilder builder = new CommitBuilder(); - builder.setTreeId(map.writeTree(inserter)); - builder.setAuthor(new PersonIdent(repo)); - builder.setCommitter(builder.getAuthor()); - builder.setMessage(msg); - if (notesCommit != null) - builder.setParentIds(notesCommit); - ObjectId commit = inserter.insert(builder); - inserter.flush(); - RefUpdate refUpdate = repo.updateRef(notesRef); - if (notesCommit != null) - refUpdate.setExpectedOldObjectId(notesCommit); - else - refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); - refUpdate.setNewObjectId(commit); - refUpdate.update(walk); - } - /** + * Set the name of the Ref to remove a note from. + * * @param notesRef - * the ref to read notes from. Note, the default value of - * {@link Constants#R_NOTES_COMMITS} will be used if nothing is - * set + * the {@code Ref} to read notes from. Note, the default value of + * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be + * used if nothing is set * @return {@code this} - * * @see Constants#R_NOTES_COMMITS */ public RemoveNoteCommand setNotesRef(String notesRef) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.internal.JGitText; @@ -76,25 +77,19 @@ private String newName; /** + *

+ * Constructor for RenameBranchCommand. + *

+ * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected RenameBranchCommand(Repository repo) { super(repo); } - /** - * @throws RefNotFoundException - * if the old branch can not be found (branch with provided old - * name does not exist or old name resolves to a tag) - * @throws InvalidRefNameException - * if the provided new name is null or otherwise - * invalid - * @throws RefAlreadyExistsException - * if a branch with the new name already exists - * @throws DetachedHeadException - * if rename is tried without specifying the old name and HEAD - * is detached - */ + /** {@inheritDoc} */ + @Override public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException, RefAlreadyExistsException, DetachedHeadException { checkCallable(); @@ -106,11 +101,11 @@ try { String fullOldName; String fullNewName; - if (repo.getRef(newName) != null) + if (repo.findRef(newName) != null) throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().refAlreadyExists1, newName)); if (oldName != null) { - Ref ref = repo.getRef(oldName); + Ref ref = repo.findRef(oldName); if (ref == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, oldName)); @@ -121,6 +116,10 @@ fullOldName = ref.getName(); } else { fullOldName = repo.getFullBranch(); + if (fullOldName == null) { + throw new NoHeadException( + JGitText.get().invalidRepositoryStateNoHead); + } if (ObjectId.isId(fullOldName)) throw new DetachedHeadException(); } @@ -181,7 +180,7 @@ repoConfig.save(); } - Ref resultRef = repo.getRef(newName); + Ref resultRef = repo.findRef(newName); if (resultRef == null) throw new JGitInternalException( JGitText.get().renameBranchFailedUnknownReason); @@ -192,6 +191,8 @@ } /** + * Set the new name of the branch + * * @param newName * the new name * @return this instance @@ -203,6 +204,8 @@ } /** + * Set the old name of the branch + * * @param oldName * the name of the branch to rename; if not set, the currently * checked out branch (if any) will be renamed diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,7 +58,9 @@ import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -121,24 +123,32 @@ private ResetType mode; - private Collection filepaths = new LinkedList(); + private Collection filepaths = new LinkedList<>(); + + private boolean isReflogDisabled; + + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** + *

+ * Constructor for ResetCommand. + *

* * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public ResetCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code Reset} command. Each instance of this class should * only be used for one invocation of the command. Don't call this method * twice on an instance. - * - * @return the Ref after reset - * @throws GitAPIException */ + @Override public Ref call() throws GitAPIException, CheckoutConflictException { checkCallable(); @@ -171,7 +181,7 @@ // reset [commit] -- paths resetIndexForPaths(commitTree); setCallable(false); - return repo.getRef(Constants.HEAD); + return repo.exactRef(Constants.HEAD); } final Ref result; @@ -181,8 +191,12 @@ ru.setNewObjectId(commitId); String refName = Repository.shortenRefName(getRefOrHEAD()); - String message = refName + ": updating " + Constants.HEAD; //$NON-NLS-1$ - ru.setRefLogMessage(message, false); + if (isReflogDisabled) { + ru.disableRefLog(); + } else { + String message = refName + ": updating " + Constants.HEAD; //$NON-NLS-1$ + ru.setRefLogMessage(message, false); + } if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) throw new JGitInternalException(MessageFormat.format( JGitText.get().cannotLock, ru.getName())); @@ -190,10 +204,8 @@ ObjectId origHead = ru.getOldObjectId(); if (origHead != null) repo.writeOrigHead(origHead); - result = ru.getRef(); - } else { - result = repo.getRef(Constants.HEAD); } + result = repo.exactRef(Constants.HEAD); if (mode == null) mode = ResetType.MIXED; @@ -253,6 +265,8 @@ } /** + * Set the name of the Ref to reset to + * * @param ref * the ref to reset to, defaults to HEAD if not specified * @return this instance @@ -263,6 +277,8 @@ } /** + * Set the reset mode + * * @param mode * the mode of the reset command * @return this instance @@ -277,6 +293,8 @@ } /** + * Repository relative path of file or directory to reset + * * @param path * repository-relative path of file/directory to reset (with * / as separator) @@ -291,6 +309,30 @@ return this; } + /** + * Whether to disable reflog + * + * @param disable + * if {@code true} disables writing a reflog entry for this reset + * command + * @return this instance + * @since 4.5 + */ + public ResetCommand disableRefLog(boolean disable) { + this.isReflogDisabled = disable; + return this; + } + + /** + * Whether reflog is disabled + * + * @return {@code true} if writing reflog is disabled for this reset command + * @since 4.5 + */ + public boolean isReflogDisabled() { + return this.isReflogDisabled; + } + private String getRefOrHEAD() { if (ref != null) return ref; @@ -298,6 +340,24 @@ return Constants.HEAD; } + /** + * The progress monitor associated with the reset operation. By default, + * this is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} + * @return {@code this} + * @since 4.11 + */ + public ResetCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } + private void resetIndexForPaths(ObjectId commitTree) { DirCache dc = null; try (final TreeWalk tw = new TreeWalk(repo)) { @@ -382,6 +442,7 @@ DirCacheCheckout checkout = new DirCacheCheckout(repo, dc, commitTree); checkout.setFailOnConflict(false); + checkout.setProgressMonitor(monitor); try { checkout.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { @@ -408,4 +469,13 @@ repo.writeMergeCommitMsg(null); } + /** {@inheritDoc} */ + @SuppressWarnings("nls") + @Override + public String toString() { + return "ResetCommand [repo=" + repo + ", ref=" + ref + ", mode=" + mode + + ", isReflogDisabled=" + isReflogDisabled + ", filepaths=" + + filepaths + "]"; + } + } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,8 +61,10 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; @@ -85,11 +87,11 @@ * >Git documentation about revert */ public class RevertCommand extends GitCommand { - private List commits = new LinkedList(); + private List commits = new LinkedList<>(); private String ourCommitName = null; - private List revertedRefs = new LinkedList(); + private List revertedRefs = new LinkedList<>(); private MergeResult failingResult; @@ -97,29 +99,29 @@ private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** + *

+ * Constructor for RevertCommand. + *

+ * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected RevertCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code revert} command with all the options and parameters * collected by the setter methods (e.g. {@link #include(Ref)} of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. - * - * @return on success the {@link RevCommit} pointed to by the new HEAD is - * returned. If a failure occurred during revert null - * is returned. The list of successfully reverted {@link Ref}'s can - * be obtained by calling {@link #getRevertedRefs()} - * @throws GitAPIException - * @throws WrongRepositoryStateException - * @throws ConcurrentRefUpdateException - * @throws UnmergedPathsException - * @throws NoMessageException */ + @Override public RevCommit call() throws NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, GitAPIException { @@ -129,7 +131,7 @@ try (RevWalk revWalk = new RevWalk(repo)) { // get the head commit - Ref headRef = repo.getRef(Constants.HEAD); + Ref headRef = repo.exactRef(Constants.HEAD); if (headRef == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); @@ -180,6 +182,7 @@ headCommit.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); try (Git git = new Git(getRepository())) { newHead = git.commit().setMessage(newMessage) @@ -226,9 +229,10 @@ } /** + * Include a {@code Ref} to a commit to be reverted + * * @param commit - * a reference to a commit which is reverted into the current - * head + * a reference to a commit to be reverted into the current head * @return {@code this} */ public RevertCommand include(Ref commit) { @@ -238,8 +242,10 @@ } /** + * Include a commit to be reverted + * * @param commit - * the Id of a commit which is reverted into the current head + * the Id of a commit to be reverted into the current head * @return {@code this} */ public RevertCommand include(AnyObjectId commit) { @@ -247,8 +253,10 @@ } /** + * Include a commit to be reverted + * * @param name - * a name given to the commit + * name of a {@code Ref} referring to the commit * @param commit * the Id of a commit which is reverted into the current head * @return {@code this} @@ -259,6 +267,8 @@ } /** + * Set the name to be used in the "OURS" place for conflict markers + * * @param ourCommitName * the name that should be used in the "OURS" place for conflict * markers @@ -279,16 +289,20 @@ } /** - * @return the list of successfully reverted {@link Ref}'s. Never - * null but maybe an empty list if no commit was - * successfully cherry-picked + * Get the list of successfully reverted {@link org.eclipse.jgit.lib.Ref}'s. + * + * @return the list of successfully reverted + * {@link org.eclipse.jgit.lib.Ref}'s. Never null but + * maybe an empty list if no commit was successfully cherry-picked */ public List getRevertedRefs() { return revertedRefs; } /** - * @return the result of the merge failure, null if no merge + * Get the result of a merge failure + * + * @return the result of a merge failure, null if no merge * failure occurred during the revert */ public MergeResult getFailingResult() { @@ -296,6 +310,8 @@ } /** + * Get unmerged paths + * * @return the unmerged paths, will be null if no merge conflicts */ public List getUnmergedPaths() { @@ -303,8 +319,10 @@ } /** + * Set the merge strategy to use for this revert command + * * @param strategy - * The merge strategy to use during this revert command. + * The merge strategy to use for this revert command. * @return {@code this} * @since 3.4 */ @@ -312,4 +330,22 @@ this.strategy = strategy; return this; } + + /** + * The progress monitor associated with the revert operation. By default, + * this is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} + * @return {@code this} + * @since 4.11 + */ + public RevertCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,10 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; @@ -53,6 +55,7 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -69,7 +72,7 @@ * class should only be used for one invocation of the command (means: one call * to {@link #call()}). *

- * Examples (git is a {@link Git} instance): + * Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Remove file "test.txt" from both index and working directory: * @@ -94,15 +97,19 @@ private boolean cached = false; /** + * Constructor for RmCommand. * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public RmCommand(Repository repo) { super(repo); - filepatterns = new LinkedList(); + filepatterns = new LinkedList<>(); } /** + * Add file name pattern of files to be removed + * * @param filepattern * repository-relative path of file to remove (with * / as separator) @@ -118,8 +125,9 @@ * Only remove the specified files from the index. * * @param cached - * true if files should only be removed from index, false if - * files should also be deleted from the working directory + * {@code true} if files should only be removed from index, + * {@code false} if files should also be deleted from the working + * directory * @return {@code this} * @since 2.2 */ @@ -130,12 +138,13 @@ } /** + * {@inheritDoc} + *

* Executes the {@code Rm} command. Each instance of this class should only * be used for one invocation of the command. Don't call this method twice * on an instance. - * - * @return the DirCache after Rm */ + @Override public DirCache call() throws GitAPIException, NoFilepatternException { @@ -144,6 +153,7 @@ checkCallable(); DirCache dc = null; + List actuallyDeletedFiles = new ArrayList<>(); try (final TreeWalk tw = new TreeWalk(repo)) { dc = repo.lockDirCache(); DirCacheBuilder builder = dc.builder(); @@ -156,11 +166,14 @@ if (!cached) { final FileMode mode = tw.getFileMode(0); if (mode.getObjectType() == Constants.OBJ_BLOB) { + String relativePath = tw.getPathString(); final File path = new File(repo.getWorkTree(), - tw.getPathString()); + relativePath); // Deleting a blob is simply a matter of removing // the file or symlink named by the tree entry. - delete(path); + if (delete(path)) { + actuallyDeletedFiles.add(relativePath); + } } } } @@ -170,16 +183,28 @@ throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); } finally { - if (dc != null) - dc.unlock(); + try { + if (dc != null) { + dc.unlock(); + } + } finally { + if (!actuallyDeletedFiles.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent(null, + actuallyDeletedFiles)); + } + } } return dc; } - private void delete(File p) { - while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) + private boolean delete(File p) { + boolean deleted = false; + while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) { + deleted = true; p = p.getParentFile(); + } + return deleted; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,18 +68,23 @@ private String notesRef = Constants.R_NOTES_COMMITS; /** + * Constructor for ShowNoteCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected ShowNoteCommand(Repository repo) { super(repo); } + /** {@inheritDoc} */ + @Override public Note call() throws GitAPIException { checkCallable(); NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; try (RevWalk walk = new RevWalk(repo)) { - Ref ref = repo.getRef(notesRef); + Ref ref = repo.exactRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); @@ -95,6 +100,8 @@ * Sets the object id of object you want a note on * * @param id + * the {@link org.eclipse.jgit.revwalk.RevObject} to show notes + * for. * @return {@code this} */ public ShowNoteCommand setObjectId(RevObject id) { @@ -104,12 +111,13 @@ } /** + * Set the {@code Ref} to read notes from. + * * @param notesRef * the ref to read notes from. Note, the default value of - * {@link Constants#R_NOTES_COMMITS} will be used if nothing is - * set + * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be + * used if nothing is set * @return {@code this} - * * @see Constants#R_NOTES_COMMITS */ public ShowNoteCommand setNotesRef(String notesRef) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012, GitHub Inc. + * Copyright (C) 2012, 2017 GitHub Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -42,8 +42,13 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; + import java.io.IOException; import java.text.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; @@ -54,11 +59,14 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CheckoutConflictException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; @@ -80,7 +88,6 @@ * * @see Git documentation about Stash - * * @since 2.0 */ public class StashApplyCommand extends GitCommand { @@ -101,6 +108,8 @@ * Create command to apply the changes of a stashed commit * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} to apply the stash + * to */ public StashApplyCommand(final Repository repo) { super(repo); @@ -113,6 +122,7 @@ * unspecified * * @param stashRef + * name of the stash {@code Ref} to apply * @return {@code this} */ public StashApplyCommand setStashRef(final String stashRef) { @@ -121,7 +131,10 @@ } /** + * Whether to ignore the repository state when applying the stash + * * @param willIgnoreRepositoryState + * whether to ignore the repository state when applying the stash * @return {@code this} * @since 3.2 */ @@ -146,15 +159,11 @@ } /** + * {@inheritDoc} + *

* Apply the changes in a stashed commit to the working directory and index - * - * @return id of stashed commit that was applied TODO: Does anyone depend on - * this, or could we make it more like Merge/CherryPick/Revert? - * @throws GitAPIException - * @throws WrongRepositoryStateException - * @throws NoHeadException - * @throws StashApplyFailureException */ + @Override public ObjectId call() throws GitAPIException, WrongRepositoryStateException, NoHeadException, StashApplyFailureException { @@ -195,7 +204,13 @@ "stash" }); //$NON-NLS-1$ merger.setBase(stashHeadCommit); merger.setWorkingTreeIterator(new FileTreeIterator(repo)); - if (merger.merge(headCommit, stashCommit)) { + boolean mergeSucceeded = merger.merge(headCommit, stashCommit); + List modifiedByMerge = merger.getModifiedFiles(); + if (!modifiedByMerge.isEmpty()) { + repo.fireEvent( + new WorkingTreeModifiedEvent(modifiedByMerge, null)); + } + if (mergeSucceeded) { DirCache dc = repo.lockDirCache(); DirCacheCheckout dco = new DirCacheCheckout(repo, headTree, dc, merger.getResultTreeId()); @@ -221,23 +236,28 @@ ResolveMerger untrackedMerger = (ResolveMerger) strategy .newMerger(repo, true); untrackedMerger.setCommitNames(new String[] { - "stashed HEAD", "HEAD", "untracked files" }); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ - untrackedMerger.setBase(stashHeadCommit); + "null", "HEAD", "untracked files" }); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + // There is no common base for HEAD & untracked files + // because the commit for untracked files has no parent. If + // we use stashHeadCommit as common base (as in the other + // merges) we potentially report conflicts for files + // which are not even member of untracked files commit + untrackedMerger.setBase(null); boolean ok = untrackedMerger.merge(headCommit, untrackedCommit); - if (ok) + if (ok) { try { RevTree untrackedTree = revWalk - .parseTree(untrackedMerger - .getResultTreeId()); + .parseTree(untrackedCommit); resetUntracked(untrackedTree); } catch (CheckoutConflictException e) { throw new StashApplyFailureException( - JGitText.get().stashApplyConflict); + JGitText.get().stashApplyConflict, e); } - else + } else { throw new StashApplyFailureException( JGitText.get().stashApplyConflict); + } } } else { throw new StashApplyFailureException( @@ -253,6 +273,8 @@ } /** + * Whether to restore the index state + * * @param applyIndex * true (default) if the command should restore the index state */ @@ -261,6 +283,8 @@ } /** + * Set the MergeStrategy to use. + * * @param strategy * The merge strategy to use in order to merge during this * command execution. @@ -273,6 +297,8 @@ } /** + * Whether the command should restore untracked files + * * @param applyUntracked * true (default) if the command should restore untracked files * @since 3.4 @@ -321,6 +347,7 @@ private void resetUntracked(RevTree tree) throws CheckoutConflictException, IOException { + Set actuallyModifiedPaths = new HashSet<>(); // TODO maybe NameConflictTreeWalk ? try (TreeWalk walk = new TreeWalk(repo)) { walk.addTree(tree); @@ -336,6 +363,8 @@ // Not in commit, don't create untracked continue; + final EolStreamType eolStreamType = walk + .getEolStreamType(CHECKOUT_OP); final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath()); entry.setFileMode(cIter.getEntryFileMode()); entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset()); @@ -350,14 +379,23 @@ } } - checkoutPath(entry, reader); + checkoutPath(entry, reader, + new CheckoutMetadata(eolStreamType, null)); + actuallyModifiedPaths.add(entry.getPathString()); + } + } finally { + if (!actuallyModifiedPaths.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent( + actuallyModifiedPaths, null)); } } } - private void checkoutPath(DirCacheEntry entry, ObjectReader reader) { + private void checkoutPath(DirCacheEntry entry, ObjectReader reader, + CheckoutMetadata checkoutMetadata) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader); + DirCacheCheckout.checkoutEntry(repo, entry, reader, true, + checkoutMetadata); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,6 +62,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; @@ -114,6 +115,7 @@ * Create a command to stash changes in the working directory and index * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public StashCreateCommand(Repository repo) { super(repo); @@ -127,6 +129,7 @@ * id, and short commit message when used. * * @param message + * the stash message * @return {@code this} */ public StashCreateCommand setIndexMessage(String message) { @@ -141,6 +144,7 @@ * id, and short commit message when used. * * @param message + * the working directory message * @return {@code this} */ public StashCreateCommand setWorkingDirectoryMessage(String message) { @@ -152,6 +156,8 @@ * Set the person to use as the author and committer in the commits made * * @param person + * the {@link org.eclipse.jgit.lib.PersonIdent} of the person who + * creates the stash. * @return {@code this} */ public StashCreateCommand setPerson(PersonIdent person) { @@ -160,12 +166,13 @@ } /** - * Set the reference to update with the stashed commit id - * If null, no reference is updated + * Set the reference to update with the stashed commit id If null, no + * reference is updated *

- * This value defaults to {@link Constants#R_STASH} + * This value defaults to {@link org.eclipse.jgit.lib.Constants#R_STASH} * * @param ref + * the name of the {@code Ref} to update * @return {@code this} */ public StashCreateCommand setRef(String ref) { @@ -177,6 +184,7 @@ * Whether to include untracked files in the stash. * * @param includeUntracked + * whether to include untracked files in the stash * @return {@code this} * @since 3.4 */ @@ -206,11 +214,12 @@ String refLogMessage) throws IOException { if (ref == null) return; - Ref currentRef = repo.getRef(ref); + Ref currentRef = repo.findRef(ref); RefUpdate refUpdate = repo.updateRef(ref); refUpdate.setNewObjectId(commitId); refUpdate.setRefLogIdent(refLogIdent); refUpdate.setRefLogMessage(refLogMessage, false); + refUpdate.setForceRefLog(true); if (currentRef != null) refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); else @@ -220,7 +229,7 @@ private Ref getHead() throws GitAPIException { try { - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); if (head == null || head.getObjectId() == null) throw new NoHeadException(JGitText.get().headRequiredToStash); return head; @@ -230,27 +239,30 @@ } /** + * {@inheritDoc} + *

* Stash the contents on the working directory and index in separate commits * and reset to the current HEAD commit. - * - * @return stashed commit or null if no changes to stash - * @throws GitAPIException */ + @Override public RevCommit call() throws GitAPIException { checkCallable(); + List deletedFiles = new ArrayList<>(); Ref head = getHead(); try (ObjectReader reader = repo.newObjectReader()) { RevCommit headCommit = parseCommit(reader, head.getObjectId()); DirCache cache = repo.lockDirCache(); ObjectId commitId; try (ObjectInserter inserter = repo.newObjectInserter(); - TreeWalk treeWalk = new TreeWalk(reader)) { + TreeWalk treeWalk = new TreeWalk(repo, reader)) { treeWalk.setRecursive(true); treeWalk.addTree(headCommit.getTree()); treeWalk.addTree(new DirCacheIterator(cache)); treeWalk.addTree(new FileTreeIterator(repo)); + treeWalk.getTree(2, FileTreeIterator.class) + .setDirCacheIterator(treeWalk, 1); treeWalk.setFilter(AndTreeFilter.create(new SkipWorkTreeFilter( 1), new IndexDiffFilter(1, 2))); @@ -259,9 +271,9 @@ return null; MutableObjectId id = new MutableObjectId(); - List wtEdits = new ArrayList(); - List wtDeletes = new ArrayList(); - List untracked = new ArrayList(); + List wtEdits = new ArrayList<>(); + List wtDeletes = new ArrayList<>(); + List untracked = new ArrayList<>(); boolean hasChanges = false; do { AbstractTreeIterator headIter = treeWalk.getTree(0, @@ -303,6 +315,7 @@ untracked.add(entry); else wtEdits.add(new PathEdit(entry) { + @Override public void apply(DirCacheEntry ent) { ent.copyMetaData(entry); } @@ -373,9 +386,11 @@ // Remove untracked files if (includeUntracked) { for (DirCacheEntry entry : untracked) { + String repoRelativePath = entry.getPathString(); File file = new File(repo.getWorkTree(), - entry.getPathString()); + repoRelativePath); FileUtils.delete(file); + deletedFiles.add(repoRelativePath); } } @@ -390,6 +405,11 @@ return parseCommit(reader, commitId); } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashFailed, e); + } finally { + if (!deletedFiles.isEmpty()) { + repo.fireEvent( + new WorkingTreeModifiedEvent(null, deletedFiles)); + } } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.List; @@ -55,6 +56,7 @@ import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.file.RefDirectory; import org.eclipse.jgit.internal.storage.file.ReflogWriter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; @@ -67,6 +69,9 @@ /** * Command class to delete a stashed commit reference + *

+ * Currently only supported on a traditional file repository using + * one-file-per-ref reflogs. * * @see Git documentation about Stash @@ -79,10 +84,17 @@ private boolean all; /** + * Constructor for StashDropCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public StashDropCommand(Repository repo) { super(repo); + if (!(repo.getRefDatabase() instanceof RefDirectory)) { + throw new UnsupportedOperationException( + JGitText.get().stashDropNotSupported); + } } /** @@ -92,6 +104,7 @@ * unspecified * * @param stashRef + * the 0-based index of the stash reference * @return {@code this} */ public StashDropCommand setStashRef(final int stashRef) { @@ -103,11 +116,12 @@ } /** - * Set wheter drop all stashed commits + * Set whether to drop all stashed commits * * @param all - * true to drop all stashed commits, false to drop only the - * stashed commit set via calling {@link #setStashRef(int)} + * {@code true} to drop all stashed commits, {@code false} to + * drop only the stashed commit set via calling + * {@link #setStashRef(int)} * @return {@code this} */ public StashDropCommand setAll(final boolean all) { @@ -117,7 +131,7 @@ private Ref getRef() throws GitAPIException { try { - return repo.getRef(R_STASH); + return repo.exactRef(R_STASH); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().cannotRead, R_STASH), e); @@ -164,12 +178,12 @@ } /** + * {@inheritDoc} + *

* Drop the configured entry from the stash reflog and return value of the * stash reference after the drop occurs - * - * @return commit id of stash reference or null if no more stashed changes - * @throws GitAPIException */ + @Override public ObjectId call() throws GitAPIException { checkCallable(); @@ -203,10 +217,11 @@ return null; } - ReflogWriter writer = new ReflogWriter(repo, true); + RefDirectory refdb = (RefDirectory) repo.getRefDatabase(); + ReflogWriter writer = new ReflogWriter(refdb, true); String stashLockRef = ReflogWriter.refLockFor(R_STASH); - File stashLockFile = writer.logFor(stashLockRef); - File stashFile = writer.logFor(R_STASH); + File stashLockFile = refdb.logFor(stashLockRef); + File stashFile = refdb.logFor(R_STASH); if (stashLockFile.exists()) throw new JGitInternalException(JGitText.get().stashDropFailed, new LockFailedException(stashFile)); @@ -220,12 +235,14 @@ entry.getWho(), entry.getComment()); entryId = entry.getNewId(); } - if (!stashLockFile.renameTo(stashFile)) { - FileUtils.delete(stashFile); - if (!stashLockFile.renameTo(stashFile)) + try { + FileUtils.rename(stashLockFile, stashFile, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().renameFileFailed, - stashLockFile.getPath(), stashFile.getPath())); + stashLockFile.getPath(), stashFile.getPath()), + e); } } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); @@ -233,7 +250,7 @@ updateRef(stashRef, entryId); try { - Ref newStashRef = repo.getRef(R_STASH); + Ref newStashRef = repo.exactRef(R_STASH); return newStashRef != null ? newStashRef.getObjectId() : null; } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,18 +70,20 @@ /** * Create a new stash list command * - * @param repo + * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ public StashListCommand(final Repository repo) { super(repo); } + /** {@inheritDoc} */ + @Override public Collection call() throws GitAPIException, InvalidRefNameException { checkCallable(); try { - if (repo.getRef(Constants.R_STASH) == null) + if (repo.exactRef(Constants.R_STASH) == null) return Collections.emptyList(); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( @@ -94,7 +96,7 @@ if (stashEntries.isEmpty()) return Collections.emptyList(); - final List stashCommits = new ArrayList( + final List stashCommits = new ArrayList<>( stashEntries.size()); try (RevWalk walk = new RevWalk(repo)) { for (ReflogEntry entry : stashEntries) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,9 +64,9 @@ * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * - * @see Git documentation about Status + * @see Git + * documentation about Status */ public class StatusCommand extends GitCommand { private WorkingTreeIterator workingTreeIt; @@ -76,14 +76,21 @@ private IgnoreSubmoduleMode ignoreSubmoduleMode = null; /** + * Constructor for StatusCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ protected StatusCommand(Repository repo) { super(repo); } /** + * Whether to ignore submodules + * * @param mode + * the + * {@link org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode} * @return {@code this} * @since 3.6 */ @@ -109,7 +116,7 @@ */ public StatusCommand addPath(String path) { if (paths == null) - paths = new LinkedList(); + paths = new LinkedList<>(); paths.add(path); return this; } @@ -126,14 +133,14 @@ } /** + * {@inheritDoc} + *

* Executes the {@code Status} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command. Don't call * this method twice on an instance. - * - * @return a {@link Status} object telling about each path where working - * tree, index or HEAD differ from each other. */ + @Override public Status call() throws GitAPIException, NoWorkTreeException { if (workingTreeIt == null) workingTreeIt = new FileTreeIterator(repo); @@ -156,8 +163,9 @@ } /** - * To set the {@link WorkingTreeIterator} which should be used. If this - * method is not called a standard {@link FileTreeIterator} is used. + * To set the {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} which + * should be used. If this method is not called a standard + * {@link org.eclipse.jgit.treewalk.FileTreeIterator} is used. * * @param workingTreeIt * a working tree iterator @@ -169,10 +177,11 @@ } /** - * To set the {@link ProgressMonitor} which contains callback methods to - * inform you about the progress of this command. + * To set the {@link org.eclipse.jgit.lib.ProgressMonitor} which contains + * callback methods to inform you about the progress of this command. * * @param progressMonitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return {@code this} * @since 3.1 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,7 +70,10 @@ private final boolean hasUncommittedChanges; /** + * Constructor for Status. + * * @param diff + * the {@link org.eclipse.jgit.lib.IndexDiff} having the status */ public Status(IndexDiff diff) { super(); @@ -86,16 +89,20 @@ } /** - * @return true if no differences exist between the working-tree, the index, - * and the current HEAD, false if differences do exist + * Whether the status is clean + * + * @return {@code true} if no differences exist between the working-tree, + * the index, and the current HEAD, {@code false} if differences do + * exist */ public boolean isClean() { return clean; } /** - * @return true if any tracked file is changed + * Whether there are uncommitted changes * + * @return {@code true} if any tracked file is changed * @since 3.2 */ public boolean hasUncommittedChanges() { @@ -103,14 +110,18 @@ } /** + * Get files added to the index + * * @return list of files added to the index, not in HEAD (e.g. what you get - * if you call 'git add ...' on a newly created file) + * if you call {@code git add ...} on a newly created file) */ public Set getAdded() { return Collections.unmodifiableSet(diff.getAdded()); } /** + * Get changed files from HEAD to index + * * @return list of files changed from HEAD to index (e.g. what you get if * you modify an existing file and call 'git add ...' on it) */ @@ -119,6 +130,8 @@ } /** + * Get removed files + * * @return list of files removed from index, but in HEAD (e.g. what you get * if you call 'git rm ...' on a existing file) */ @@ -127,6 +140,8 @@ } /** + * Get missing files + * * @return list of files in index, but not filesystem (e.g. what you get if * you call 'rm ...' on a existing file) */ @@ -135,6 +150,8 @@ } /** + * Get modified files relative to the index + * * @return list of files modified on disk relative to the index (e.g. what * you get if you modify an existing file without adding it to the * index) @@ -144,6 +161,8 @@ } /** + * Get untracked files + * * @return list of files that are not ignored, and not in the index. (e.g. * what you get if you create a new file without adding it to the * index) @@ -153,6 +172,8 @@ } /** + * Get untracked folders + * * @return set of directories that are not ignored, and not in the index. */ public Set getUntrackedFolders() { @@ -160,6 +181,8 @@ } /** + * Get conflicting files + * * @return list of files that are in conflict. (e.g what you get if you * modify file that was modified by someone else in the meantime) */ @@ -168,7 +191,10 @@ } /** - * @return a map from conflicting path to its {@link StageState}. + * Get StageState of conflicting files + * + * @return a map from conflicting path to its + * {@link org.eclipse.jgit.lib.IndexDiff.StageState}. * @since 3.0 */ public Map getConflictingStageState() { @@ -176,6 +202,8 @@ } /** + * Get ignored files which are not in the index + * * @return set of files and folders that are ignored and not in the index. */ public Set getIgnoredNotInIndex() { @@ -183,13 +211,15 @@ } /** + * Get uncommitted changes, i.e. all files changed in the index or working + * tree + * * @return set of files and folders that are known to the repo and changed * either in the index or in the working tree. - * * @since 3.2 */ public Set getUncommittedChanges() { - Set uncommittedChanges = new HashSet(); + Set uncommittedChanges = new HashSet<>(); uncommittedChanges.addAll(diff.getAdded()); uncommittedChanges.addAll(diff.getChanged()); uncommittedChanges.addAll(diff.getRemoved()); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.submodule.SubmoduleValidator; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -69,8 +70,8 @@ * .gitmodules file and the repository config file, and also add the submodule * and .gitmodules file to the index. * - * @see Git documentation about submodules */ public class SubmoduleAddCommand extends @@ -83,7 +84,10 @@ private ProgressMonitor monitor; /** + * Constructor for SubmoduleAddCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleAddCommand(final Repository repo) { super(repo); @@ -105,6 +109,7 @@ * Set URI to clone submodule from * * @param uri + * a {@link java.lang.String} object. * @return this command */ public SubmoduleAddCommand setURI(final String uri) { @@ -118,6 +123,7 @@ * * @see NullProgressMonitor * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return this command */ public SubmoduleAddCommand setProgressMonitor(final ProgressMonitor monitor) { @@ -129,24 +135,26 @@ * Is the configured already a submodule in the index? * * @return true if submodule exists in index, false otherwise - * @throws IOException + * @throws java.io.IOException */ protected boolean submoduleExists() throws IOException { TreeFilter filter = PathFilter.create(path); - return SubmoduleWalk.forIndex(repo).setFilter(filter).next(); + try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { + return w.setFilter(filter).next(); + } } /** + * {@inheritDoc} + *

* Executes the {@code SubmoduleAddCommand} * * The {@code Repository} instance returned by this command needs to be * closed by the caller to free resources held by the {@code Repository} * instance. It is recommended to call this method as soon as you don't need * a reference to this {@code Repository} instance anymore. - * - * @return the newly created {@link Repository} - * @throws GitAPIException */ + @Override public Repository call() throws GitAPIException { checkCallable(); if (path == null || path.length() == 0) @@ -155,6 +163,14 @@ throw new IllegalArgumentException(JGitText.get().uriNotConfigured); try { + SubmoduleValidator.assertValidSubmoduleName(path); + SubmoduleValidator.assertValidSubmodulePath(path); + SubmoduleValidator.assertValidSubmoduleUri(uri); + } catch (SubmoduleValidator.SubmoduleValidationException e) { + throw new IllegalArgumentException(e.getMessage()); + } + + try { if (submoduleExists()) throw new JGitInternalException(MessageFormat.format( JGitText.get().submoduleExists, path)); @@ -178,7 +194,11 @@ clone.setURI(resolvedUri); if (monitor != null) clone.setProgressMonitor(monitor); - Repository subRepo = clone.call().getRepository(); + Repository subRepo = null; + try (Git git = clone.call()) { + subRepo = git.getRepository(); + subRepo.incrementOpen(); + } // Save submodule URL to parent repository's config StoredConfig config = repo.getConfig(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2017, Two Sigma Open Source + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.eclipse.jgit.util.FileUtils.RECURSIVE; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.FileUtils; + +/** + * A class used to execute a submodule deinit command. + *

+ * This will remove the module(s) from the working tree, but won't affect + * .git/modules. + * + * @since 4.10 + * @see Git documentation about submodules + */ +public class SubmoduleDeinitCommand + extends GitCommand> { + + private final Collection paths; + + private boolean force; + + /** + * Constructor of SubmoduleDeinitCommand + * + * @param repo + */ + public SubmoduleDeinitCommand(Repository repo) { + super(repo); + paths = new ArrayList<>(); + } + + /** + * {@inheritDoc} + *

+ * + * @return the set of repositories successfully deinitialized. + * @throws NoSuchSubmoduleException + * if any of the submodules which we might want to deinitialize + * don't exist + */ + @Override + public Collection call() throws GitAPIException { + checkCallable(); + try { + if (paths.isEmpty()) { + return Collections.emptyList(); + } + for (String path : paths) { + if (!submoduleExists(path)) { + throw new NoSuchSubmoduleException(path); + } + } + List results = new ArrayList<>(paths.size()); + try (RevWalk revWalk = new RevWalk(repo); + SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { + generator.setFilter(PathFilterGroup.createFromStrings(paths)); + StoredConfig config = repo.getConfig(); + while (generator.next()) { + String path = generator.getPath(); + String name = generator.getModuleName(); + SubmoduleDeinitStatus status = checkDirty(revWalk, path); + switch (status) { + case SUCCESS: + deinit(path); + break; + case ALREADY_DEINITIALIZED: + break; + case DIRTY: + if (force) { + deinit(path); + status = SubmoduleDeinitStatus.FORCED; + } + break; + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().unexpectedSubmoduleStatus, + status)); + } + + config.unsetSection( + ConfigConstants.CONFIG_SUBMODULE_SECTION, name); + results.add(new SubmoduleDeinitResult(path, status)); + } + } + return results; + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + + /** + * Recursively delete the *contents* of path, but leave path as an empty + * directory + * + * @param path + * the path to clean + * @throws IOException + */ + private void deinit(String path) throws IOException { + File dir = new File(repo.getWorkTree(), path); + if (!dir.isDirectory()) { + throw new JGitInternalException(MessageFormat.format( + JGitText.get().expectedDirectoryNotSubmodule, path)); + } + final File[] ls = dir.listFiles(); + if (ls != null) { + for (int i = 0; i < ls.length; i++) { + FileUtils.delete(ls[i], RECURSIVE); + } + } + } + + /** + * Check if a submodule is dirty. A submodule is dirty if there are local + * changes to the submodule relative to its HEAD, including untracked files. + * It is also dirty if the HEAD of the submodule does not match the value in + * the parent repo's index or HEAD. + * + * @param revWalk + * @param path + * @return status of the command + * @throws GitAPIException + * @throws IOException + */ + private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path) + throws GitAPIException, IOException { + Ref head = repo.exactRef("HEAD"); //$NON-NLS-1$ + if (head == null) { + throw new NoHeadException( + JGitText.get().invalidRepositoryStateNoHead); + } + RevCommit headCommit = revWalk.parseCommit(head.getObjectId()); + RevTree tree = headCommit.getTree(); + + ObjectId submoduleHead; + try (SubmoduleWalk w = SubmoduleWalk.forPath(repo, tree, path)) { + submoduleHead = w.getHead(); + if (submoduleHead == null) { + // The submodule is not checked out. + return SubmoduleDeinitStatus.ALREADY_DEINITIALIZED; + } + if (!submoduleHead.equals(w.getObjectId())) { + // The submodule's current HEAD doesn't match the value in the + // outer repo's HEAD. + return SubmoduleDeinitStatus.DIRTY; + } + } + + try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { + if (!w.next()) { + // The submodule does not exist in the index (shouldn't happen + // since we check this earlier) + return SubmoduleDeinitStatus.DIRTY; + } + if (!submoduleHead.equals(w.getObjectId())) { + // The submodule's current HEAD doesn't match the value in the + // outer repo's index. + return SubmoduleDeinitStatus.DIRTY; + } + + Repository submoduleRepo = w.getRepository(); + + Status status = Git.wrap(submoduleRepo).status().call(); + return status.isClean() ? SubmoduleDeinitStatus.SUCCESS + : SubmoduleDeinitStatus.DIRTY; + } + } + + /** + * Check if this path is a submodule by checking the index, which is what + * git submodule deinit checks. + * + * @param path + * path of the submodule + * + * @return {@code true} if path exists and is a submodule in index, + * {@code false} otherwise + * @throws IOException + */ + private boolean submoduleExists(String path) throws IOException { + TreeFilter filter = PathFilter.create(path); + try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { + return w.setFilter(filter).next(); + } + } + + /** + * Add repository-relative submodule path to deinitialize + * + * @param path + * (with / as separator) + * @return this command + */ + public SubmoduleDeinitCommand addPath(String path) { + paths.add(path); + return this; + } + + /** + * If {@code true}, call() will deinitialize modules with local changes; + * else it will refuse to do so. + * + * @param force + * @return {@code this} + */ + public SubmoduleDeinitCommand setForce(boolean force) { + this.force = force; + return this; + } + + /** + * The user tried to deinitialize a submodule that doesn't exist in the + * index. + */ + public static class NoSuchSubmoduleException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * Constructor of NoSuchSubmoduleException + * + * @param path + * path of non-existing submodule + */ + public NoSuchSubmoduleException(String path) { + super(MessageFormat.format(JGitText.get().noSuchSubmodule, path)); + } + } + + /** + * The effect of a submodule deinit command for a given path + */ + public enum SubmoduleDeinitStatus { + /** + * The submodule was not initialized in the first place + */ + ALREADY_DEINITIALIZED, + /** + * The submodule was deinitialized + */ + SUCCESS, + /** + * The submodule had local changes, but was deinitialized successfully + */ + FORCED, + /** + * The submodule had local changes and force was false + */ + DIRTY, + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017, Two Sigma Open Source + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +/** + * The result of a submodule deinit command for a particular path + * + * @since 4.10 + */ +public class SubmoduleDeinitResult { + private String path; + + private SubmoduleDeinitCommand.SubmoduleDeinitStatus status; + + /** + * Constructor for SubmoduleDeinitResult + * + * @param path + * path of the submodule + * @param status + */ + public SubmoduleDeinitResult(String path, + SubmoduleDeinitCommand.SubmoduleDeinitStatus status) { + this.path = path; + this.status = status; + } + + /** + * Get the path of the submodule + * + * @return path of the submodule + */ + public String getPath() { + return path; + } + + /** + * Set the path of the submodule + * + * @param path + * path of the submodule + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Get the status of the command + * + * @return the status of the command + */ + public SubmoduleDeinitCommand.SubmoduleDeinitStatus getStatus() { + return status; + } + + /** + * Set the status of the command + * + * @param status + * the status of the command + */ + public void setStatus(SubmoduleDeinitCommand.SubmoduleDeinitStatus status) { + this.status = status; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,8 +63,8 @@ * .gitmodules file to a repository's config file for each submodule not * currently present in the repository's config file. * - * @see Git documentation about submodules */ public class SubmoduleInitCommand extends GitCommand> { @@ -72,11 +72,14 @@ private final Collection paths; /** + * Constructor for SubmoduleInitCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleInitCommand(final Repository repo) { super(repo); - paths = new ArrayList(); + paths = new ArrayList<>(); } /** @@ -91,31 +94,33 @@ return this; } + /** {@inheritDoc} */ + @Override public Collection call() throws GitAPIException { checkCallable(); - try { - SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); StoredConfig config = repo.getConfig(); - List initialized = new ArrayList(); + List initialized = new ArrayList<>(); while (generator.next()) { // Ignore entry if URL is already present in config file if (generator.getConfigUrl() != null) continue; String path = generator.getPath(); + String name = generator.getModuleName(); // Copy 'url' and 'update' fields from .gitmodules to config // file String url = generator.getRemoteUrl(); String update = generator.getModulesUpdate(); if (url != null) config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, - path, ConfigConstants.CONFIG_KEY_URL, url); + name, ConfigConstants.CONFIG_KEY_URL, url); if (update != null) config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, - path, ConfigConstants.CONFIG_KEY_UPDATE, update); + name, ConfigConstants.CONFIG_KEY_UPDATE, update); if (url != null || update != null) initialized.add(path); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,16 +54,16 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.submodule.SubmoduleStatus; import org.eclipse.jgit.submodule.SubmoduleStatusType; +import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a submodule status command. * - * @see Git documentation about submodules */ public class SubmoduleStatusCommand extends @@ -72,11 +72,14 @@ private final Collection paths; /** + * Constructor for SubmoduleStatusCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleStatusCommand(final Repository repo) { super(repo); - paths = new ArrayList(); + paths = new ArrayList<>(); } /** @@ -91,14 +94,15 @@ return this; } + /** {@inheritDoc} */ + @Override public Map call() throws GitAPIException { checkCallable(); - try { - SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); - Map statuses = new HashMap(); + Map statuses = new HashMap<>(); while (generator.next()) { SubmoduleStatus status = getStatus(generator); statuses.put(status.getPath(), status); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,8 +65,8 @@ * This will set the remote URL in a submodule's repository to the current value * in the .gitmodules file. * - * @see Git documentation about submodules */ public class SubmoduleSyncCommand extends GitCommand> { @@ -74,11 +74,14 @@ private final Collection paths; /** + * Constructor for SubmoduleSyncCommand. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleSyncCommand(final Repository repo) { super(repo); - paths = new ArrayList(); + paths = new ArrayList<>(); } /** @@ -97,25 +100,27 @@ * Get branch that HEAD currently points to * * @param subRepo + * a {@link org.eclipse.jgit.lib.Repository} object. * @return shortened branch name, null on failures - * @throws IOException + * @throws java.io.IOException */ protected String getHeadBranch(final Repository subRepo) throws IOException { - Ref head = subRepo.getRef(Constants.HEAD); + Ref head = subRepo.exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) return Repository.shortenRefName(head.getLeaf().getName()); else return null; } + /** {@inheritDoc} */ + @Override public Map call() throws GitAPIException { checkCallable(); - try { - SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); - Map synced = new HashMap(); + Map synced = new HashMap<>(); StoredConfig config = repo.getConfig(); while (generator.next()) { String remoteUrl = generator.getRemoteUrl(); @@ -162,4 +167,4 @@ throw new JGitInternalException(e.getMessage(), e); } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2011, GitHub Inc. + * Copyright (C) 2016, Laurent Delaigue * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -88,12 +89,23 @@ private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private CloneCommand.Callback callback; + + private FetchCommand.Callback fetchCallback; + + private boolean fetch = false; + /** + *

+ * Constructor for SubmoduleUpdateCommand. + *

+ * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleUpdateCommand(final Repository repo) { super(repo); - paths = new ArrayList(); + paths = new ArrayList<>(); } /** @@ -102,6 +114,7 @@ * * @see NullProgressMonitor * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return this command */ public SubmoduleUpdateCommand setProgressMonitor( @@ -111,6 +124,20 @@ } /** + * Whether to fetch the submodules before we update them. By default, this + * is set to false + * + * @param fetch + * whether to fetch the submodules before we update them + * @return this command + * @since 4.9 + */ + public SubmoduleUpdateCommand setFetch(final boolean fetch) { + this.fetch = fetch; + return this; + } + + /** * Add repository-relative submodule path to initialize * * @param path @@ -123,19 +150,11 @@ } /** - * Execute the SubmoduleUpdateCommand command. + * {@inheritDoc} * - * @return a collection of updated submodule paths - * @throws ConcurrentRefUpdateException - * @throws CheckoutConflictException - * @throws InvalidMergeHeadsException - * @throws InvalidConfigurationException - * @throws NoHeadException - * @throws NoMessageException - * @throws RefNotFoundException - * @throws WrongRepositoryStateException - * @throws GitAPIException + * Execute the SubmoduleUpdateCommand command. */ + @Override public Collection call() throws InvalidConfigurationException, NoHeadException, ConcurrentRefUpdateException, CheckoutConflictException, InvalidMergeHeadsException, @@ -146,7 +165,7 @@ try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); - List updated = new ArrayList(); + List updated = new ArrayList<>(); while (generator.next()) { // Skip submodules not registered in .gitmodules file if (generator.getModulesPath() == null) @@ -157,8 +176,11 @@ continue; Repository submoduleRepo = generator.getRepository(); - // Clone repository is not present + // Clone repository if not present if (submoduleRepo == null) { + if (callback != null) { + callback.cloningSubmodule(generator.getPath()); + } CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setURI(url); @@ -168,6 +190,16 @@ if (monitor != null) clone.setProgressMonitor(monitor); submoduleRepo = clone.call().getRepository(); + } else if (this.fetch) { + if (fetchCallback != null) { + fetchCallback.fetchingSubmodule(generator.getPath()); + } + FetchCommand fetchCommand = Git.wrap(submoduleRepo).fetch(); + if (monitor != null) { + fetchCommand.setProgressMonitor(monitor); + } + configure(fetchCommand); + fetchCommand.call(); } try (RevWalk walk = new RevWalk(submoduleRepo)) { @@ -178,11 +210,13 @@ if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) { MergeCommand merge = new MergeCommand(submoduleRepo); merge.include(commit); + merge.setProgressMonitor(monitor); merge.setStrategy(strategy); merge.call(); } else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) { RebaseCommand rebase = new RebaseCommand(submoduleRepo); rebase.setUpstream(commit); + rebase.setProgressMonitor(monitor); rebase.setStrategy(strategy); rebase.call(); } else { @@ -192,11 +226,16 @@ submoduleRepo, submoduleRepo.lockDirCache(), commit.getTree()); co.setFailOnConflict(true); + co.setProgressMonitor(monitor); co.checkout(); RefUpdate refUpdate = submoduleRepo.updateRef( Constants.HEAD, true); refUpdate.setNewObjectId(commit); refUpdate.forceUpdate(); + if (callback != null) { + callback.checkingOut(commit, + generator.getPath()); + } } } finally { submoduleRepo.close(); @@ -212,6 +251,8 @@ } /** + * Setter for the field strategy. + * * @param strategy * The merge strategy to use during this update operation. * @return {@code this} @@ -221,4 +262,31 @@ this.strategy = strategy; return this; } + + /** + * Set status callback for submodule clone operation. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) { + this.callback = callback; + return this; + } + + /** + * Set status callback for submodule fetch operation. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.9 + */ + public SubmoduleUpdateCommand setFetchCallback( + FetchCommand.Callback callback) { + this.fetchCallback = callback; + return this; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,7 +68,7 @@ /** * Create/update an annotated tag object or a simple unannotated tag *

- * Examples (git is a {@link Git} instance): + * Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Create a new tag for the current commit: * @@ -104,23 +104,25 @@ private boolean annotated = true; /** - * @param repo + *

Constructor for TagCommand.

+ * + * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ protected TagCommand(Repository repo) { super(repo); } /** + * {@inheritDoc} + *

* Executes the {@code tag} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) * - * @return a {@link Ref} a ref pointing to a tag - * @throws NoHeadException - * when called on a git repo without a HEAD reference * @since 2.0 */ + @Override public Ref call() throws GitAPIException, ConcurrentRefUpdateException, InvalidTagNameException, NoHeadException { checkCallable(); @@ -184,7 +186,7 @@ switch (updateResult) { case NEW: case FORCED: - return repo.getRef(refName); + return repo.exactRef(refName); case LOCK_FAILURE: throw new ConcurrentRefUpdateException( JGitText.get().couldNotLockHEAD, tagRef.getRef(), @@ -225,6 +227,8 @@ } /** + * Set the tag name. + * * @param name * the tag name used for the {@code tag} * @return {@code this} @@ -236,6 +240,8 @@ } /** + * Get the tag name. + * * @return the tag name used for the tag */ public String getName() { @@ -243,6 +249,8 @@ } /** + * Get the tag message. + * * @return the tag message used for the tag */ public String getMessage() { @@ -250,6 +258,8 @@ } /** + * Set the tag message. + * * @param message * the tag message used for the {@code tag} * @return {@code this} @@ -261,6 +271,8 @@ } /** + * Whether this tag is signed + * * @return whether the tag is signed */ public boolean isSigned() { @@ -272,6 +284,7 @@ * corresponds to the parameter -s on the command line. * * @param signed + * a boolean. * @return {@code this} */ public TagCommand setSigned(boolean signed) { @@ -284,6 +297,7 @@ * created from the info in the repository. * * @param tagger + * a {@link org.eclipse.jgit.lib.PersonIdent} object. * @return {@code this} */ public TagCommand setTagger(PersonIdent tagger) { @@ -292,6 +306,8 @@ } /** + * Get the tagger who created the tag. + * * @return the tagger of the tag */ public PersonIdent getTagger() { @@ -299,6 +315,8 @@ } /** + * Get the tag's object id + * * @return the object id of the tag */ public RevObject getObjectId() { @@ -310,6 +328,7 @@ * pointed to from HEAD will be used. * * @param id + * a {@link org.eclipse.jgit.revwalk.RevObject} object. * @return {@code this} */ public TagCommand setObjectId(RevObject id) { @@ -318,6 +337,8 @@ } /** + * Whether this is a forced update + * * @return is this a force update */ public boolean isForceUpdate() { @@ -329,6 +350,7 @@ * corresponds to the parameter -f on the command line. * * @param forceUpdate + * whether this is a forced update * @return {@code this} */ public TagCommand setForceUpdate(boolean forceUpdate) { @@ -337,7 +359,10 @@ } /** + * Configure this tag to be created as an annotated tag + * * @param annotated + * whether this shall be an annotated tag * @return {@code this} * @since 3.0 */ @@ -347,6 +372,8 @@ } /** + * Whether this will create an annotated command + * * @return true if this command will create an annotated tag (default is * true) * @since 3.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,11 +47,12 @@ import org.eclipse.jgit.transport.Transport; /** - * Base class for commands that use a {@link Transport} during execution. + * Base class for commands that use a + * {@link org.eclipse.jgit.transport.Transport} during execution. *

* This class provides standard configuration of a transport for options such as - * a {@link CredentialsProvider}, a timeout, and a - * {@link TransportConfigCallback}. + * a {@link org.eclipse.jgit.transport.CredentialsProvider}, a timeout, and a + * {@link org.eclipse.jgit.api.TransportConfigCallback}. * * @param * @param @@ -75,15 +76,21 @@ protected TransportConfigCallback transportConfigCallback; /** - * @param repo + *

Constructor for TransportCommand.

+ * + * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ protected TransportCommand(final Repository repo) { super(repo); + setCredentialsProvider(CredentialsProvider.getDefault()); } /** + * Set the credentialsProvider. + * * @param credentialsProvider - * the {@link CredentialsProvider} to use + * the {@link org.eclipse.jgit.transport.CredentialsProvider} to + * use * @return {@code this} */ public C setCredentialsProvider( @@ -93,8 +100,10 @@ } /** + * Set timeout. + * * @param timeout - * the timeout used for the transport step + * the timeout (in seconds) used for the transport step * @return {@code this} */ public C setTimeout(int timeout) { @@ -103,12 +112,15 @@ } /** + * Set the TransportConfigCallback. + * * @param transportConfigCallback * if set, the callback will be invoked after the - * {@link Transport} has created, but before the - * {@link Transport} is used. The callback can use this - * opportunity to set additional type-specific configuration on - * the {@link Transport} instance. + * {@link org.eclipse.jgit.transport.Transport} has created, but + * before the {@link org.eclipse.jgit.transport.Transport} is + * used. The callback can use this opportunity to set additional + * type-specific configuration on the + * {@link org.eclipse.jgit.transport.Transport} instance. * @return {@code this} */ public C setTransportConfigCallback( @@ -117,7 +129,11 @@ return self(); } - /** @return {@code this} */ + /** + * Return this command cast to {@code C} + * + * @return {@code this} cast to {@code C} + */ @SuppressWarnings("unchecked") protected final C self() { return (C) this; @@ -128,6 +144,7 @@ * callback * * @param transport + * a {@link org.eclipse.jgit.transport.Transport} object. * @return {@code this} */ protected C configure(final Transport transport) { @@ -144,6 +161,7 @@ * {@code this} command * * @param childCommand + * a {@link org.eclipse.jgit.api.TransportCommand} object. * @return {@code this} */ protected C configure(final TransportCommand childCommand) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,7 +64,9 @@ /** * Add any additional transport-specific configuration required. + * * @param transport + * a {@link org.eclipse.jgit.transport.Transport} object. */ public void configure(Transport transport); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,13 +47,17 @@ *

* According to the man page, an attribute can have the following states: *

    - *
  • Set - represented by {@link State#SET}
  • - *
  • Unset - represented by {@link State#UNSET}
  • - *
  • Set to a value - represented by {@link State#CUSTOM}
  • - *
  • Unspecified - null is used instead of an instance of this - * class
  • + *
  • Set - represented by + * {@link org.eclipse.jgit.attributes.Attribute.State#SET}
  • + *
  • Unset - represented by + * {@link org.eclipse.jgit.attributes.Attribute.State#UNSET}
  • + *
  • Set to a value - represented by + * {@link org.eclipse.jgit.attributes.Attribute.State#CUSTOM}
  • + *
  • Unspecified - used to revert an attribute . This is crucial in order to + * mark an attribute as unspecified in the attributes map and thus preventing + * following (with lower priority) nodes from setting the attribute to a value + * at all
  • *
- *

* * @since 3.7 */ @@ -61,6 +65,7 @@ /** * The attribute value state + * see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html */ public static enum State { /** the attribute is set */ @@ -69,6 +74,13 @@ /** the attribute is unset */ UNSET, + /** + * the attribute appears as if it would not be defined at all + * + * @since 4.2 + */ + UNSPECIFIED, + /** the attribute is set to a custom value */ CUSTOM } @@ -83,10 +95,11 @@ * @param key * the attribute key. Should not be null. * @param state - * the attribute state. It should be either {@link State#SET} or - * {@link State#UNSET}. In order to create a custom value - * attribute prefer the use of {@link #Attribute(String, String)} - * constructor. + * the attribute state. It should be either + * {@link org.eclipse.jgit.attributes.Attribute.State#SET} or + * {@link org.eclipse.jgit.attributes.Attribute.State#UNSET}. In + * order to create a custom value attribute prefer the use of + * {@link #Attribute(String, String)} constructor. */ public Attribute(String key, State state) { this(key, state, null); @@ -117,6 +130,7 @@ this(key, State.CUSTOM, value); } + /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) @@ -137,6 +151,8 @@ } /** + * Get key + * * @return the attribute key (never returns null) */ public String getKey() { @@ -144,7 +160,7 @@ } /** - * Returns the state. + * Return the state. * * @return the state (never returns null) */ @@ -153,12 +169,15 @@ } /** + * Get value + * * @return the attribute value (may be null) */ public String getValue() { return value; } + /** {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; @@ -169,6 +188,7 @@ return result; } + /** {@inheritDoc} */ @Override public String toString() { switch (state) { @@ -176,9 +196,11 @@ return key; case UNSET: return "-" + key; //$NON-NLS-1$ + case UNSPECIFIED: + return "!" + key; //$NON-NLS-1$ case CUSTOM: default: return key + "=" + value; //$NON-NLS-1$ } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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.attributes; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; + +/** + * The attributes handler knows how to retrieve, parse and merge attributes from + * the various gitattributes files. Furthermore it collects and expands macro + * expressions. The method {@link #getAttributes()} yields the ready processed + * attributes for the current path represented by the + * {@link org.eclipse.jgit.treewalk.TreeWalk} + *

+ * The implementation is based on the specifications in + * http://git-scm.com/docs/gitattributes + * + * @since 4.3 + */ +public class AttributesHandler { + private static final String MACRO_PREFIX = "[attr]"; //$NON-NLS-1$ + + private static final String BINARY_RULE_KEY = "binary"; //$NON-NLS-1$ + + /** + * This is the default binary rule that is present in any git folder + * [attr]binary -diff -merge -text + */ + private static final List BINARY_RULE_ATTRIBUTES = new AttributesRule( + MACRO_PREFIX + BINARY_RULE_KEY, "-diff -merge -text") //$NON-NLS-1$ + .getAttributes(); + + private final TreeWalk treeWalk; + + private final AttributesNode globalNode; + + private final AttributesNode infoNode; + + private final Map> expansions = new HashMap<>(); + + /** + * Create an {@link org.eclipse.jgit.attributes.AttributesHandler} with + * default rules as well as merged rules from global, info and worktree root + * attributes + * + * @param treeWalk + * a {@link org.eclipse.jgit.treewalk.TreeWalk} + * @throws java.io.IOException + */ + public AttributesHandler(TreeWalk treeWalk) throws IOException { + this.treeWalk = treeWalk; + AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider(); + this.globalNode = attributesNodeProvider != null + ? attributesNodeProvider.getGlobalAttributesNode() : null; + this.infoNode = attributesNodeProvider != null + ? attributesNodeProvider.getInfoAttributesNode() : null; + + AttributesNode rootNode = attributesNode(treeWalk, + rootOf( + treeWalk.getTree(WorkingTreeIterator.class)), + rootOf( + treeWalk.getTree(DirCacheIterator.class)), + rootOf(treeWalk + .getTree(CanonicalTreeParser.class))); + + expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES); + for (AttributesNode node : new AttributesNode[] { globalNode, rootNode, + infoNode }) { + if (node == null) { + continue; + } + for (AttributesRule rule : node.getRules()) { + if (rule.getPattern().startsWith(MACRO_PREFIX)) { + expansions.put(rule.getPattern() + .substring(MACRO_PREFIX.length()).trim(), + rule.getAttributes()); + } + } + } + } + + /** + * See {@link org.eclipse.jgit.treewalk.TreeWalk#getAttributes()} + * + * @return the {@link org.eclipse.jgit.attributes.Attributes} for the + * current path represented by the + * {@link org.eclipse.jgit.treewalk.TreeWalk} + * @throws java.io.IOException + */ + public Attributes getAttributes() throws IOException { + String entryPath = treeWalk.getPathString(); + boolean isDirectory = (treeWalk.getFileMode() == FileMode.TREE); + Attributes attributes = new Attributes(); + + // Gets the info attributes + mergeInfoAttributes(entryPath, isDirectory, attributes); + + // Gets the attributes located on the current entry path + mergePerDirectoryEntryAttributes(entryPath, entryPath.lastIndexOf('/'), + isDirectory, + treeWalk.getTree(WorkingTreeIterator.class), + treeWalk.getTree(DirCacheIterator.class), + treeWalk.getTree(CanonicalTreeParser.class), + attributes); + + // Gets the attributes located in the global attribute file + mergeGlobalAttributes(entryPath, isDirectory, attributes); + + // now after all attributes are collected - in the correct hierarchy + // order - remove all unspecified entries (the ! marker) + for (Attribute a : attributes.getAll()) { + if (a.getState() == State.UNSPECIFIED) + attributes.remove(a.getKey()); + } + + return attributes; + } + + /** + * Merges the matching GLOBAL attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + private void mergeGlobalAttributes(String entryPath, boolean isDirectory, + Attributes result) { + mergeAttributes(globalNode, entryPath, isDirectory, result); + } + + /** + * Merges the matching INFO attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + private void mergeInfoAttributes(String entryPath, boolean isDirectory, + Attributes result) { + mergeAttributes(infoNode, entryPath, isDirectory, result); + } + + /** + * Merges the matching working directory attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param nameRoot + * index of the '/' preceeding the current level, or -1 if none + * @param isDirectory + * true if the target item is a directory. + * @param workingTreeIterator + * @param dirCacheIterator + * @param otherTree + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + * @throws IOException + */ + private void mergePerDirectoryEntryAttributes(String entryPath, + int nameRoot, boolean isDirectory, + @Nullable WorkingTreeIterator workingTreeIterator, + @Nullable DirCacheIterator dirCacheIterator, + @Nullable CanonicalTreeParser otherTree, Attributes result) + throws IOException { + // Prevents infinite recurrence + if (workingTreeIterator != null || dirCacheIterator != null + || otherTree != null) { + AttributesNode attributesNode = attributesNode( + treeWalk, workingTreeIterator, dirCacheIterator, otherTree); + if (attributesNode != null) { + mergeAttributes(attributesNode, + entryPath.substring(nameRoot + 1), isDirectory, + result); + } + mergePerDirectoryEntryAttributes(entryPath, + entryPath.lastIndexOf('/', nameRoot - 1), isDirectory, + parentOf(workingTreeIterator), parentOf(dirCacheIterator), + parentOf(otherTree), result); + } + } + + /** + * Merges the matching node attributes for an entry path. + * + * @param node + * the node to scan for matches to entryPath + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + protected void mergeAttributes(@Nullable AttributesNode node, + String entryPath, + boolean isDirectory, Attributes result) { + if (node == null) + return; + List rules = node.getRules(); + // Parse rules in the reverse order that they were read since the last + // entry should be used + ListIterator ruleIterator = rules + .listIterator(rules.size()); + while (ruleIterator.hasPrevious()) { + AttributesRule rule = ruleIterator.previous(); + if (rule.isMatch(entryPath, isDirectory)) { + ListIterator attributeIte = rule.getAttributes() + .listIterator(rule.getAttributes().size()); + // Parses the attributes in the reverse order that they were + // read since the last entry should be used + while (attributeIte.hasPrevious()) { + expandMacro(attributeIte.previous(), result); + } + } + } + } + + /** + * Expand a macro + * + * @param attr + * a {@link org.eclipse.jgit.attributes.Attribute} + * @param result + * contains the (recursive) expanded and merged macro attributes + * including the attribute iself + */ + protected void expandMacro(Attribute attr, Attributes result) { + // loop detection = exists check + if (result.containsKey(attr.getKey())) + return; + + // also add macro to result set, same does native git + result.put(attr); + + List expansion = expansions.get(attr.getKey()); + if (expansion == null) { + return; + } + switch (attr.getState()) { + case UNSET: { + for (Attribute e : expansion) { + switch (e.getState()) { + case SET: + expandMacro(new Attribute(e.getKey(), State.UNSET), result); + break; + case UNSET: + expandMacro(new Attribute(e.getKey(), State.SET), result); + break; + case UNSPECIFIED: + expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED), + result); + break; + case CUSTOM: + default: + expandMacro(e, result); + } + } + break; + } + case CUSTOM: { + for (Attribute e : expansion) { + switch (e.getState()) { + case SET: + case UNSET: + case UNSPECIFIED: + expandMacro(e, result); + break; + case CUSTOM: + default: + expandMacro(new Attribute(e.getKey(), attr.getValue()), + result); + } + } + break; + } + case UNSPECIFIED: { + for (Attribute e : expansion) { + expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED), + result); + } + break; + } + case SET: + default: + for (Attribute e : expansion) { + expandMacro(e, result); + } + break; + } + } + + /** + * Get the {@link AttributesNode} for the current entry. + *

+ * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + *

+ * + * @param treeWalk + * @param workingTreeIterator + * @param dirCacheIterator + * @param otherTree + * @return a {@link AttributesNode} of the current entry, + * {@link NullPointerException} otherwise. + * @throws IOException + * It raises an {@link IOException} if a problem appears while + * parsing one on the attributes file. + */ + private static AttributesNode attributesNode(TreeWalk treeWalk, + @Nullable WorkingTreeIterator workingTreeIterator, + @Nullable DirCacheIterator dirCacheIterator, + @Nullable CanonicalTreeParser otherTree) throws IOException { + AttributesNode attributesNode = null; + switch (treeWalk.getOperationType()) { + case CHECKIN_OP: + if (workingTreeIterator != null) { + attributesNode = workingTreeIterator.getEntryAttributesNode(); + } + if (attributesNode == null && dirCacheIterator != null) { + attributesNode = dirCacheIterator + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && otherTree != null) { + attributesNode = otherTree + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + break; + case CHECKOUT_OP: + if (otherTree != null) { + attributesNode = otherTree + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && dirCacheIterator != null) { + attributesNode = dirCacheIterator + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && workingTreeIterator != null) { + attributesNode = workingTreeIterator.getEntryAttributesNode(); + } + break; + default: + throw new IllegalStateException( + "The only supported operation types are:" //$NON-NLS-1$ + + OperationType.CHECKIN_OP + "," //$NON-NLS-1$ + + OperationType.CHECKOUT_OP); + } + + return attributesNode; + } + + private static T parentOf(@Nullable T node) { + if(node==null) return null; + @SuppressWarnings("unchecked") + Class type = (Class) node.getClass(); + AbstractTreeIterator parent = node.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + return null; + } + + private static T rootOf( + @Nullable T node) { + if(node==null) return null; + AbstractTreeIterator t=node; + while (t!= null && t.parent != null) { + t= t.parent; + } + @SuppressWarnings("unchecked") + Class type = (Class) node.getClass(); + if (type.isInstance(t)) { + return type.cast(t); + } + return null; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015, Ivan Motsch , + * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr) + * + * 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.attributes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.lib.Constants; + +/** + * Represents a set of attributes for a path + * + * @since 4.2 + */ +public final class Attributes { + private final Map map = new LinkedHashMap<>(); + + /** + * Creates a new instance + * + * @param attributes + * a {@link org.eclipse.jgit.attributes.Attribute} + */ + public Attributes(Attribute... attributes) { + if (attributes != null) { + for (Attribute a : attributes) { + put(a); + } + } + } + + /** + * Whether the set of attributes is empty + * + * @return true if the set does not contain any attributes + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Get the attribute with the given key + * + * @param key + * a {@link java.lang.String} object. + * @return the attribute or null + */ + public Attribute get(String key) { + return map.get(key); + } + + /** + * Get all attributes + * + * @return all attributes + */ + public Collection getAll() { + return new ArrayList<>(map.values()); + } + + /** + * Put an attribute + * + * @param a + * an {@link org.eclipse.jgit.attributes.Attribute} + */ + public void put(Attribute a) { + map.put(a.getKey(), a); + } + + /** + * Remove attribute with given key + * + * @param key + * an attribute name + */ + public void remove(String key) { + map.remove(key); + } + + /** + * Whether there is an attribute with this key + * + * @param key + * key of an attribute + * @return true if the {@link org.eclipse.jgit.attributes.Attributes} + * contains this key + */ + public boolean containsKey(String key) { + return map.containsKey(key); + } + + /** + * Return the state. + * + * @param key + * key of an attribute + * @return the state (never returns null) + */ + public Attribute.State getState(String key) { + Attribute a = map.get(key); + return a != null ? a.getState() : Attribute.State.UNSPECIFIED; + } + + /** + * Whether the attribute is set + * + * @param key + * a {@link java.lang.String} object. + * @return true if the key is + * {@link org.eclipse.jgit.attributes.Attribute.State#SET}, false in + * all other cases + */ + public boolean isSet(String key) { + return (getState(key) == State.SET); + } + + /** + * Whether the attribute is unset + * + * @param key + * a {@link java.lang.String} object. + * @return true if the key is + * {@link org.eclipse.jgit.attributes.Attribute.State#UNSET}, false + * in all other cases + */ + public boolean isUnset(String key) { + return (getState(key) == State.UNSET); + } + + /** + * Whether the attribute with the given key is unspecified + * + * @param key + * a {@link java.lang.String} object. + * @return true if the key is + * {@link org.eclipse.jgit.attributes.Attribute.State#UNSPECIFIED}, + * false in all other cases + */ + public boolean isUnspecified(String key) { + return (getState(key) == State.UNSPECIFIED); + } + + /** + * Is this a custom attribute + * + * @param key + * a {@link java.lang.String} object. + * @return true if the key is + * {@link org.eclipse.jgit.attributes.Attribute.State#CUSTOM}, false + * in all other cases see {@link #getValue(String)} for the value of + * the key + */ + public boolean isCustom(String key) { + return (getState(key) == State.CUSTOM); + } + + /** + * Get attribute value + * + * @param key + * an attribute key + * @return the attribute value (may be null) + */ + public String getValue(String key) { + Attribute a = map.get(key); + return a != null ? a.getValue() : null; + } + + /** + * Test if the given attributes implies to handle the related entry as a + * binary file (i.e. if the entry has an -merge or a merge=binary attribute) + * or if it can be content merged. + * + * @return true if the entry can be content merged, + * false otherwise + * @since 4.9 + */ + public boolean canBeContentMerged() { + if (isUnset(Constants.ATTR_MERGE)) { + return false; + } else if (isCustom(Constants.ATTR_MERGE) + && getValue(Constants.ATTR_MERGE) + .equals(Constants.ATTR_BUILTIN_BINARY_MERGER)) { + return false; + } + return true; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append("["); //$NON-NLS-1$ + buf.append(" "); //$NON-NLS-1$ + for (Attribute a : map.values()) { + buf.append(a.toString()); + buf.append(" "); //$NON-NLS-1$ + } + buf.append("]"); //$NON-NLS-1$ + return buf.toString(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return map.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Attributes)) + return false; + Attributes other = (Attributes) obj; + return this.map.equals(other.map); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ListIterator; -import java.util.Map; import org.eclipse.jgit.lib.Constants; @@ -65,9 +63,11 @@ /** The rules that have been parsed into this node. */ private final List rules; - /** Create an empty ignore node with no rules. */ + /** + * Create an empty ignore node with no rules. + */ public AttributesNode() { - rules = new ArrayList(); + rules = new ArrayList<>(); } /** @@ -75,7 +75,7 @@ * * @param rules * list of rules. - **/ + */ public AttributesNode(List rules) { this.rules = rules; } @@ -86,7 +86,7 @@ * @param in * input stream holding the standard ignore format. The caller is * responsible for closing the stream. - * @throws IOException + * @throws java.io.IOException * Error thrown when reading an ignore file. */ public void parse(InputStream in) throws IOException { @@ -118,44 +118,13 @@ return new BufferedReader(new InputStreamReader(in, Constants.CHARSET)); } - /** @return list of all ignore rules held by this node. */ - public List getRules() { - return Collections.unmodifiableList(rules); - } - /** - * Returns the matching attributes for an entry path. + * Getter for the field rules. * - * @param entryPath - * the path to test. The path must be relative to this attribute - * node's own repository path, and in repository path format - * (uses '/' and not '\'). - * @param isDirectory - * true if the target item is a directory. - * @param attributes - * Map that will hold the attributes matching this entry path. If - * it is not empty, this method will NOT override any - * existing entry. + * @return list of all ignore rules held by this node */ - public void getAttributes(String entryPath, boolean isDirectory, - Map attributes) { - // Parse rules in the reverse order that they were read since the last - // entry should be used - ListIterator ruleIterator = rules.listIterator(rules - .size()); - while (ruleIterator.hasPrevious()) { - AttributesRule rule = ruleIterator.previous(); - if (rule.isMatch(entryPath, isDirectory)) { - ListIterator attributeIte = rule.getAttributes() - .listIterator(rule.getAttributes().size()); - // Parses the attributes in the reverse order that they were - // read since the last entry should be used - while (attributeIte.hasPrevious()) { - Attribute attr = attributeIte.previous(); - if (!attributes.containsKey(attr.getKey())) - attributes.put(attr.getKey(), attr); - } - } - } + public List getRules() { + return Collections.unmodifiableList(rules); } + } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * 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.attributes; + +import java.io.IOException; + +import org.eclipse.jgit.lib.CoreConfig; + +/** + * An interface used to retrieve the global and info + * {@link org.eclipse.jgit.attributes.AttributesNode}s. + * + * @since 4.2 + */ +public interface AttributesNodeProvider { + + /** + * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} that + * holds the information located in $GIT_DIR/info/attributes file. + * + * @return the {@link org.eclipse.jgit.attributes.AttributesNode} that holds + * the information located in $GIT_DIR/info/attributes file. + * @throws java.io.IOException + * if an error is raised while parsing the attributes file + */ + public AttributesNode getInfoAttributesNode() throws IOException; + + /** + * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} that + * holds the information located in the global gitattributes file. + * + * @return the {@link org.eclipse.jgit.attributes.AttributesNode} that holds + * the information located in the global gitattributes file. + * @throws java.io.IOException + * java.io.IOException if an error is raised while parsing the + * attributes file + * @see CoreConfig#getAttributesFile() + */ + public AttributesNode getGlobalAttributesNode() throws IOException; + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * 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.attributes; + +/** + * Interface for classes which provide git attributes + * + * @since 4.2 + */ +public interface AttributesProvider { + /** + * Get attributes + * + * @return the currently active attributes + */ + public Attributes getAttributes(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Red Hat Inc. + * Copyright (C) 2010, 2017 Red Hat Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -57,7 +57,7 @@ /** * A single attributes rule corresponding to one line in a .gitattributes file. * - * Inspiration from: {@link FastIgnoreRule} + * Inspiration from: {@link org.eclipse.jgit.ignore.FastIgnoreRule} * * @since 3.7 */ @@ -71,7 +71,7 @@ private static List parseAttributes(String attributesLine) { // the C implementation oddly enough allows \r between attributes too. - ArrayList result = new ArrayList(); + ArrayList result = new ArrayList<>(); for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) { attribute = attribute.trim(); if (attribute.length() == 0) @@ -84,6 +84,13 @@ continue; } + if (attribute.startsWith("!")) {//$NON-NLS-1$ + if (attribute.length() > 1) + result.add(new Attribute(attribute.substring(1), + State.UNSPECIFIED)); + continue; + } + final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$ if (equalsIndex == -1) result.add(new Attribute(attribute, State.SET)); @@ -102,10 +109,11 @@ private final String pattern; private final List attributes; - private boolean nameOnly; - private boolean dirOnly; + private final boolean nameOnly; + + private final boolean dirOnly; - private IMatcher matcher; + private final IMatcher matcher; /** * Create a new attribute rule with the given pattern. Assumes that the @@ -121,43 +129,50 @@ */ public AttributesRule(String pattern, String attributes) { this.attributes = parseAttributes(attributes); - nameOnly = false; - dirOnly = false; if (pattern.endsWith("/")) { //$NON-NLS-1$ pattern = pattern.substring(0, pattern.length() - 1); dirOnly = true; + } else { + dirOnly = false; } - boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$ + int slashIndex = pattern.indexOf('/'); - if (!hasSlash) + if (slashIndex < 0) { nameOnly = true; - else if (!pattern.startsWith("/")) { //$NON-NLS-1$ + } else if (slashIndex == 0) { + nameOnly = false; + } else { + nameOnly = false; // Contains "/" but does not start with one // Adding / to the start should not interfere with matching pattern = "/" + pattern; //$NON-NLS-1$ } + IMatcher candidateMatcher = NO_MATCH; try { - matcher = PathMatcher.createPathMatcher(pattern, + candidateMatcher = PathMatcher.createPathMatcher(pattern, Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); } catch (InvalidPatternException e) { - matcher = NO_MATCH; + // ignore: invalid patterns are silently ignored } - + this.matcher = candidateMatcher; this.pattern = pattern; } /** - * @return True if the pattern should match directories only + * Whether to match directories only + * + * @return {@code true} if the pattern should match directories only + * @since 4.3 */ - public boolean dirOnly() { + public boolean isDirOnly() { return dirOnly; } /** - * Returns the attributes. + * Return the attributes. * * @return an unmodifiable list of attributes (never returns * null) @@ -167,6 +182,8 @@ } /** + * Whether the pattern is only a file name and not a path + * * @return true if the pattern is just a file name and not a * path */ @@ -175,6 +192,8 @@ } /** + * Get the pattern + * * @return The blob pattern to be used as a matcher (never returns * null) */ @@ -197,7 +216,20 @@ return false; if (relativeTarget.length() == 0) return false; - boolean match = matcher.matches(relativeTarget, isDirectory); + boolean match = matcher.matches(relativeTarget, isDirectory, true); return match; } -} \ No newline at end of file + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(pattern); + for (Attribute a : attributes) { + sb.append(" "); //$NON-NLS-1$ + sb.append(a); + } + return sb.toString(); + + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.lib.Repository; + +/** + * The factory responsible for creating instances of + * {@link org.eclipse.jgit.attributes.FilterCommand}. + * + * @since 4.6 + */ +public interface FilterCommandFactory { + /** + * Create a new {@link org.eclipse.jgit.attributes.FilterCommand}. + * + * @param db + * the repository this command should work on + * @param in + * the {@link java.io.InputStream} this command should read from + * @param out + * the {@link java.io.OutputStream} this command should write to + * @return the created {@link org.eclipse.jgit.attributes.FilterCommand} + * @throws java.io.IOException + * thrown when the command constructor throws an + * java.io.IOException + */ + public FilterCommand create(Repository db, InputStream in, + OutputStream out) throws IOException; + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An abstraction for JGit's builtin implementations for hooks and filters. + * Instead of spawning an external processes to start a filter/hook and to pump + * data from/to stdin/stdout these builtin commmands may be used. They are + * constructed by {@link org.eclipse.jgit.attributes.FilterCommandFactory}. + * + * @since 4.6 + */ +public abstract class FilterCommand { + /** + * The {@link InputStream} this command should read from + */ + protected InputStream in; + + /** + * The {@link OutputStream} this command should write to + */ + protected OutputStream out; + + /** + * Constructor for FilterCommand + *

+ * FilterCommand implementors are required to manage the in and out streams + * (close on success and/or exception). + * + * @param in + * The {@link java.io.InputStream} this command should read from + * @param out + * The {@link java.io.OutputStream} this command should write to + */ + public FilterCommand(InputStream in, OutputStream out) { + this.in = in; + this.out = out; + } + + /** + * Execute the command. The command is supposed to read data from + * {@link #in} and to write the result to {@link #out}. It returns the + * number of bytes it read from {@link #in}. It should be called in a loop + * until it returns -1 signaling that the {@link java.io.InputStream} is + * completely processed. + *

+ * On successful completion (return -1) or on Exception, the streams + * {@link #in} and {@link #out} are closed by the implementation. + * + * @return the number of bytes read from the {@link java.io.InputStream} or + * -1. -1 means that the {@link java.io.InputStream} is completely + * processed. + * @throws java.io.IOException + * when {@link java.io.IOException} occured while reading from + * {@link #in} or writing to {@link #out} + */ + public abstract int run() throws IOException; +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * 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.attributes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.lib.Repository; + +/** + * Registry for built-in filters + * + * @since 4.6 + */ +public class FilterCommandRegistry { + private static ConcurrentHashMap filterCommandRegistry = new ConcurrentHashMap<>(); + + /** + * Register a {@link org.eclipse.jgit.attributes.FilterCommandFactory} + * responsible for creating + * {@link org.eclipse.jgit.attributes.FilterCommand}s for a certain command + * name. If the factory f1 is registered for the name "jgit://builtin/x" + * then a call to getCommand("jgit://builtin/x", ...) will call + * f1(...) to create a new instance of + * {@link org.eclipse.jgit.attributes.FilterCommand} + * + * @param filterCommandName + * the command name for which this factory is registered + * @param factory + * the factory responsible for creating + * {@link org.eclipse.jgit.attributes.FilterCommand}s for the + * specified name + * @return the previous factory associated with commandName, or + * null if there was no mapping for commandName + */ + public static FilterCommandFactory register(String filterCommandName, + FilterCommandFactory factory) { + return filterCommandRegistry.put(filterCommandName, factory); + } + + /** + * Unregister the {@link org.eclipse.jgit.attributes.FilterCommandFactory} + * registered for the given command name + * + * @param filterCommandName + * the FilterCommandFactory's filter command name + * @return the previous factory associated with filterCommandName, + * or null if there was no mapping for commandName + */ + public static FilterCommandFactory unregister(String filterCommandName) { + return filterCommandRegistry.remove(filterCommandName); + } + + /** + * Check whether any + * {@link org.eclipse.jgit.attributes.FilterCommandFactory} is registered + * for a given command name + * + * @param filterCommandName + * the name for which the registry should be checked + * @return true if any factory was registered for the name + */ + public static boolean isRegistered(String filterCommandName) { + return filterCommandRegistry.containsKey(filterCommandName); + } + + /** + * Get registered filter commands + * + * @return Set of commandNames for which a + * {@link org.eclipse.jgit.attributes.FilterCommandFactory} is + * registered + */ + public static Set getRegisteredFilterCommands() { + return filterCommandRegistry.keySet(); + } + + /** + * Create a new {@link org.eclipse.jgit.attributes.FilterCommand} for the + * given name. A factory must be registered for the name in advance. + * + * @param filterCommandName + * The name for which a new + * {@link org.eclipse.jgit.attributes.FilterCommand} should be + * created + * @param db + * the repository this command should work on + * @param in + * the {@link java.io.InputStream} this + * {@link org.eclipse.jgit.attributes.FilterCommand} should read + * from + * @param out + * the {@link java.io.OutputStream} this + * {@link org.eclipse.jgit.attributes.FilterCommand} should write + * to + * @return the command if a command could be created or null if + * there was no factory registered for that name + * @throws java.io.IOException + */ + public static FilterCommand createFilterCommand(String filterCommandName, + Repository db, InputStream in, OutputStream out) + throws IOException { + FilterCommandFactory cf = filterCommandRegistry.get(filterCommandName); + return (cf == null) ? null : cf.create(db, in, out); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import java.util.Collection; import java.util.Collections; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.blame.Candidate.BlobCandidate; import org.eclipse.jgit.blame.Candidate.ReverseCandidate; import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit; @@ -87,8 +88,10 @@ *

* Applications that want more incremental update behavior may use either the * raw {@link #next()} streaming approach supported by this class, or construct - * a {@link BlameResult} using {@link BlameResult#create(BlameGenerator)} and - * incrementally construct the result with {@link BlameResult#computeNext()}. + * a {@link org.eclipse.jgit.blame.BlameResult} using + * {@link org.eclipse.jgit.blame.BlameResult#create(BlameGenerator)} and + * incrementally construct the result with + * {@link org.eclipse.jgit.blame.BlameResult#computeNext()}. *

* This class is not thread-safe. *

@@ -185,12 +188,20 @@ treeWalk.setRecursive(true); } - /** @return repository being scanned for revision history. */ + /** + * Get repository + * + * @return repository being scanned for revision history + */ public Repository getRepository() { return repository; } - /** @return path file path being processed. */ + /** + * Get result path + * + * @return path file path being processed + */ public String getResultPath() { return resultPath.getPath(); } @@ -199,6 +210,7 @@ * Difference algorithm to use when comparing revisions. * * @param algorithm + * a {@link org.eclipse.jgit.diff.DiffAlgorithm} * @return {@code this} */ public BlameGenerator setDiffAlgorithm(DiffAlgorithm algorithm) { @@ -210,6 +222,7 @@ * Text comparator to use when comparing revisions. * * @param comparator + * a {@link org.eclipse.jgit.diff.RawTextComparator} * @return {@code this} */ public BlameGenerator setTextComparator(RawTextComparator comparator) { @@ -238,11 +251,13 @@ } /** - * Obtain the RenameDetector if {@code setFollowFileRenames(true)}. + * Obtain the RenameDetector, allowing the application to configure its + * settings for rename score and breaking behavior. * - * @return the rename detector, allowing the application to configure its - * settings for rename score and breaking behavior. + * @return the rename detector, or {@code null} if + * {@code setFollowFileRenames(false)}. */ + @Nullable public RenameDetector getRenameDetector() { return renameDetector; } @@ -252,15 +267,15 @@ *

* Candidates should be pushed in history order from oldest-to-newest. * Applications should push the starting commit first, then the index - * revision (if the index is interesting), and finally the working tree - * copy (if the working tree is interesting). + * revision (if the index is interesting), and finally the working tree copy + * (if the working tree is interesting). * * @param description * description of the blob revision, such as "Working Tree". * @param contents * contents of the file. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameGenerator push(String description, byte[] contents) @@ -281,14 +296,15 @@ * @param contents * contents of the file. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameGenerator push(String description, RawText contents) throws IOException { if (description == null) description = JGitText.get().blameNotCommittedYet; - BlobCandidate c = new BlobCandidate(description, resultPath); + BlobCandidate c = new BlobCandidate(getRepository(), description, + resultPath); c.sourceText = contents; c.regionList = new Region(0, 0, contents.size()); remaining = contents.size(); @@ -309,7 +325,7 @@ * @param id * may be a commit or a blob. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameGenerator push(String description, AnyObjectId id) @@ -318,7 +334,8 @@ if (ldr.getType() == OBJ_BLOB) { if (description == null) description = JGitText.get().blameNotCommittedYet; - BlobCandidate c = new BlobCandidate(description, resultPath); + BlobCandidate c = new BlobCandidate(getRepository(), description, + resultPath); c.sourceBlob = id.toObjectId(); c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE)); c.regionList = new Region(0, 0, c.sourceText.size()); @@ -331,7 +348,7 @@ if (!find(commit, resultPath)) return this; - Candidate c = new Candidate(commit, resultPath); + Candidate c = new Candidate(getRepository(), commit, resultPath); c.sourceBlob = idBuf.toObjectId(); c.loadText(reader); c.regionList = new Region(0, 0, c.sourceText.size()); @@ -354,8 +371,8 @@ * each of these is a descendant commit that removed the line, typically * this occurs when the same deletion appears in multiple side branches such * as due to a cherry-pick. Applications relying on reverse should use - * {@link BlameResult} as it filters these duplicate sources and only - * remembers the first (oldest) deletion. + * {@link org.eclipse.jgit.blame.BlameResult} as it filters these duplicate + * sources and only remembers the first (oldest) deletion. * * @param start * oldest commit to traverse from. The result file will be loaded @@ -364,7 +381,7 @@ * most recent commit to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameGenerator reverse(AnyObjectId start, AnyObjectId end) @@ -386,8 +403,8 @@ * each of these is a descendant commit that removed the line, typically * this occurs when the same deletion appears in multiple side branches such * as due to a cherry-pick. Applications relying on reverse should use - * {@link BlameResult} as it filters these duplicate sources and only - * remembers the first (oldest) deletion. + * {@link org.eclipse.jgit.blame.BlameResult} as it filters these duplicate + * sources and only remembers the first (oldest) deletion. * * @param start * oldest commit to traverse from. The result file will be loaded @@ -396,7 +413,7 @@ * most recent commits to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameGenerator reverse(AnyObjectId start, @@ -415,7 +432,8 @@ // just pump the queue } - ReverseCandidate c = new ReverseCandidate(result, resultPath); + ReverseCandidate c = new ReverseCandidate(getRepository(), result, + resultPath); c.sourceBlob = idBuf.toObjectId(); c.loadText(reader); c.regionList = new Region(0, 0, c.sourceText.size()); @@ -440,7 +458,7 @@ * Execute the generator in a blocking fashion until all data is ready. * * @return the complete result. Null if no file exists for the given path. - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public BlameResult computeBlameResult() throws IOException { @@ -461,7 +479,7 @@ * and {@link #getResultStart()}, {@link #getResultEnd()} methods * can be used to inspect the region found. False if there are no * more regions to describe. - * @throws IOException + * @throws java.io.IOException * repository cannot be read. */ public boolean next() throws IOException { @@ -622,7 +640,8 @@ return false; } - Candidate next = n.create(parent, PathFilter.create(r.getOldPath())); + Candidate next = n.create(getRepository(), parent, + PathFilter.create(r.getOldPath())); next.sourceBlob = r.getOldId().toObjectId(); next.renameScore = r.getScore(); next.loadText(reader); @@ -638,7 +657,7 @@ private boolean splitBlameWithParent(Candidate n, RevCommit parent) throws IOException { - Candidate next = n.create(parent, n.sourcePath); + Candidate next = n.create(getRepository(), parent, n.sourcePath); next.sourceBlob = idBuf.toObjectId(); next.loadText(reader); return split(next, n); @@ -725,12 +744,12 @@ Candidate p; if (renames != null && renames[pIdx] != null) { - p = n.create(parent, + p = n.create(getRepository(), parent, PathFilter.create(renames[pIdx].getOldPath())); p.renameScore = renames[pIdx].getScore(); p.sourceBlob = renames[pIdx].getOldId().toObjectId(); } else if (ids != null && ids[pIdx] != null) { - p = n.create(parent, n.sourcePath); + p = n.create(getRepository(), parent, n.sourcePath); p.sourceBlob = ids[pIdx]; } else { continue; @@ -839,28 +858,47 @@ return outCandidate.sourceCommit; } - /** @return current author being blamed. */ + /** + * Get source author + * + * @return current author being blamed + */ public PersonIdent getSourceAuthor() { return outCandidate.getAuthor(); } - /** @return current committer being blamed. */ + /** + * Get source committer + * + * @return current committer being blamed + */ public PersonIdent getSourceCommitter() { RevCommit c = getSourceCommit(); return c != null ? c.getCommitterIdent() : null; } - /** @return path of the file being blamed. */ + /** + * Get source path + * + * @return path of the file being blamed + */ public String getSourcePath() { return outCandidate.sourcePath.getPath(); } - /** @return rename score if a rename occurred in {@link #getSourceCommit}. */ + /** + * Get rename score + * + * @return rename score if a rename occurred in {@link #getSourceCommit} + */ public int getRenameScore() { return outCandidate.renameScore; } /** + * Get first line of the source data that has been blamed for the current + * region + * * @return first line of the source data that has been blamed for the * current region. This is line number of where the region was added * during {@link #getSourceCommit()} in file @@ -871,6 +909,9 @@ } /** + * Get one past the range of the source data that has been blamed for the + * current region + * * @return one past the range of the source data that has been blamed for * the current region. This is line number of where the region was * added during {@link #getSourceCommit()} in file @@ -882,6 +923,9 @@ } /** + * Get first line of the result that {@link #getSourceCommit()} has been + * blamed for providing + * * @return first line of the result that {@link #getSourceCommit()} has been * blamed for providing. Line numbers use 0 based indexing. */ @@ -890,6 +934,9 @@ } /** + * Get one past the range of the result that {@link #getSourceCommit()} has + * been blamed for providing + * * @return one past the range of the result that {@link #getSourceCommit()} * has been blamed for providing. Line numbers use 0 based indexing. * Because a source cannot be blamed for an empty region of the @@ -902,6 +949,9 @@ } /** + * Get number of lines in the current region being blamed to + * {@link #getSourceCommit()} + * * @return number of lines in the current region being blamed to * {@link #getSourceCommit()}. This is always the value of the * expression {@code getResultEnd() - getResultStart()}, but also @@ -912,6 +962,9 @@ } /** + * Get complete contents of the source file blamed for the current output + * region + * * @return complete contents of the source file blamed for the current * output region. This is the contents of {@link #getSourcePath()} * within {@link #getSourceCommit()}. The source contents is @@ -923,13 +976,15 @@ } /** + * Get complete file contents of the result file blame is annotating + * * @return complete file contents of the result file blame is annotating. * This value is accessible only after being configured and only * immediately before the first call to {@link #next()}. Returns * null if the path does not exist. - * @throws IOException + * @throws java.io.IOException * repository cannot be read. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * {@link #next()} has already been invoked. */ public RawText getResultContents() throws IOException { @@ -937,6 +992,8 @@ } /** + * {@inheritDoc} + *

* Release the current blame session. * * @since 4.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -78,7 +78,7 @@ * the generator the result will consume records from. * @return the new result object. null if the generator cannot find the path * it starts from. - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public static BlameResult create(BlameGenerator gen) throws IOException { @@ -123,17 +123,27 @@ sourcePaths = new String[cnt]; } - /** @return path of the file this result annotates. */ + /** + * Get result path + * + * @return path of the file this result annotates + */ public String getResultPath() { return resultPath; } - /** @return contents of the result file, available for display. */ + /** + * Get result contents + * + * @return contents of the result file, available for display + */ public RawText getResultContents() { return resultContents; } - /** Throw away the {@link #getResultContents()}. */ + /** + * Throw away the {@link #getResultContents()}. + */ public void discardResultContents() { resultContents = null; } @@ -227,7 +237,7 @@ /** * Compute all pending information. * - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public void computeAll() throws IOException { @@ -252,7 +262,7 @@ * to determine how many lines of the result were computed. * * @return index that is now available. -1 if no more are available. - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public int computeNext() throws IOException { @@ -271,7 +281,11 @@ } } - /** @return length of the last segment found by {@link #computeNext()}. */ + /** + * Get last length + * + * @return length of the last segment found by {@link #computeNext()} + */ public int lastLength() { return lastLength; } @@ -283,7 +297,7 @@ * first index to examine (inclusive). * @param end * end index (exclusive). - * @throws IOException + * @throws java.io.IOException * the repository cannot be read. */ public void computeRange(int start, int end) throws IOException { @@ -322,6 +336,7 @@ } } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,10 +55,12 @@ import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.util.LfsFactory; /** * A source that may have supplied some (or all) of the result file. @@ -109,7 +111,11 @@ */ int renameScore; - Candidate(RevCommit commit, PathFilter path) { + /** repository used for LFS blob handling */ + private Repository sourceRepository; + + Candidate(Repository repo, RevCommit commit, PathFilter path) { + sourceRepository = repo; sourceCommit = commit; sourcePath = path; } @@ -150,12 +156,12 @@ return sourceCommit.getAuthorIdent(); } - Candidate create(RevCommit commit, PathFilter path) { - return new Candidate(commit, path); + Candidate create(Repository repo, RevCommit commit, PathFilter path) { + return new Candidate(repo, commit, path); } Candidate copy(RevCommit commit) { - Candidate r = create(commit, sourcePath); + Candidate r = create(sourceRepository, commit, sourcePath); r.sourceBlob = sourceBlob; r.sourceText = sourceText; r.regionList = regionList; @@ -164,7 +170,11 @@ } void loadText(ObjectReader reader) throws IOException { - ObjectLoader ldr = reader.open(sourceBlob, Constants.OBJ_BLOB); + ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(sourceRepository, + reader.open(sourceBlob, Constants.OBJ_BLOB), + LfsFactory.getAttributesForPath(sourceRepository, + sourcePath.getPath(), sourceCommit) + .get(Constants.ATTR_DIFF)); sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE)); } @@ -325,6 +335,7 @@ } } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -348,8 +359,9 @@ * children pointers, allowing reverse navigation of history. */ static final class ReverseCandidate extends Candidate { - ReverseCandidate(ReverseCommit commit, PathFilter path) { - super(commit, path); + ReverseCandidate(Repository repo, ReverseCommit commit, + PathFilter path) { + super(repo, commit, path); } @Override @@ -369,8 +381,8 @@ } @Override - Candidate create(RevCommit commit, PathFilter path) { - return new ReverseCandidate((ReverseCommit) commit, path); + Candidate create(Repository repo, RevCommit commit, PathFilter path) { + return new ReverseCandidate(repo, (ReverseCommit) commit, path); } @Override @@ -399,8 +411,8 @@ /** Author name to refer to this blob with. */ String description; - BlobCandidate(String name, PathFilter path) { - super(null, path); + BlobCandidate(Repository repo, String name, PathFilter path) { + super(repo, null, path); description = name; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java 2019-09-03 12:37:49.000000000 +0000 @@ -116,6 +116,7 @@ return head; } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder buf = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,6 +57,7 @@ super(repo); } + /** {@inheritDoc} */ @Override public ReverseCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -68,6 +69,7 @@ return c; } + /** {@inheritDoc} */ @Override protected RevCommit createCommit(AnyObjectId id) { return new ReverseCommit(id); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,13 +55,13 @@ import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectStream; -import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilter; /** - * Supplies the content of a file for {@link DiffFormatter}. + * Supplies the content of a file for + * {@link org.eclipse.jgit.diff.DiffFormatter}. *

* A content source is not thread-safe. Sources may contain state, including * information about the last ObjectLoader they returned. Callers must be @@ -83,8 +83,9 @@ /** * Construct a content source for a working directory. * - * If the iterator is a {@link FileTreeIterator} an optimized version is - * used that doesn't require seeking through a TreeWalk. + * If the iterator is a {@link org.eclipse.jgit.treewalk.FileTreeIterator} + * an optimized version is used that doesn't require seeking through a + * TreeWalk. * * @param iterator * the iterator to obtain source files through. @@ -102,7 +103,7 @@ * @param id * blob id of the file, if known. * @return the size in bytes. - * @throws IOException + * @throws java.io.IOException * the file cannot be accessed. */ public abstract long size(String path, ObjectId id) throws IOException; @@ -117,7 +118,7 @@ * @return a loader that can supply the content of the file. The loader must * be used before another loader can be obtained from this same * source. - * @throws IOException + * @throws java.io.IOException * the file cannot be accessed. */ public abstract ObjectLoader open(String path, ObjectId id) @@ -132,7 +133,11 @@ @Override public long size(String path, ObjectId id) throws IOException { - return reader.getObjectSize(id, Constants.OBJ_BLOB); + try { + return reader.getObjectSize(id, Constants.OBJ_BLOB); + } catch (MissingObjectException ignore) { + return 0; + } } @Override @@ -148,7 +153,7 @@ private String current; - private WorkingTreeIterator ptr; + WorkingTreeIterator ptr; WorkingTreeSource(WorkingTreeIterator iterator) { this.tw = new TreeWalk((ObjectReader) null); @@ -165,10 +170,11 @@ @Override public ObjectLoader open(String path, ObjectId id) throws IOException { seek(path); + long entrySize = ptr.getEntryContentLength(); return new ObjectLoader() { @Override public long getSize() { - return ptr.getEntryLength(); + return entrySize; } @Override @@ -179,7 +185,7 @@ @Override public ObjectStream openStream() throws MissingObjectException, IOException { - long contentLength = ptr.getEntryContentLength(); + long contentLength = entrySize; InputStream in = ptr.openEntryStream(); in = new BufferedInputStream(in); return new ObjectStream.Filter(getType(), contentLength, in); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,8 @@ package org.eclipse.jgit.diff; /** - * Compares two {@link Sequence}s to create an {@link EditList} of changes. + * Compares two {@link org.eclipse.jgit.diff.Sequence}s to create an + * {@link org.eclipse.jgit.diff.EditList} of changes. *

* An algorithm's {@code diff} method must be callable from concurrent threads * without data collisions. This permits some algorithms to use a singleton @@ -69,6 +70,8 @@ } /** + * Get diff algorithm + * * @param alg * the diff algorithm for which an implementation should be * returned @@ -88,18 +91,18 @@ /** * Compare two sequences and identify a list of edits between them. * - * @param - * type of sequence being compared. * @param cmp * the comparator supplying the element equivalence function. * @param a * the first (also known as old or pre-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'A' side: {@link Edit#getBeginA()}, {@link Edit#getEndA()}. + * 'A' side: {@link org.eclipse.jgit.diff.Edit#getBeginA()}, + * {@link org.eclipse.jgit.diff.Edit#getEndA()}. * @param b * the second (also known as new or post-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'B' side: {@link Edit#getBeginB()}, {@link Edit#getEndB()}. + * 'B' side: {@link org.eclipse.jgit.diff.Edit#getBeginB()}, + * {@link org.eclipse.jgit.diff.Edit#getEndB()}. * @return a modifiable edit list comparing the two sequences. If empty, the * sequences are identical according to {@code cmp}'s rules. The * result list is never null. @@ -117,27 +120,11 @@ if (region.getLengthA() == 1 && region.getLengthB() == 1) return EditList.singleton(region); - SubsequenceComparator cs = new SubsequenceComparator(cmp); + SubsequenceComparator cs = new SubsequenceComparator<>(cmp); Subsequence as = Subsequence.a(a, region); Subsequence bs = Subsequence.b(b, region); EditList e = Subsequence.toBase(diffNonCommon(cs, as, bs), as, bs); - - // The last insertion may need to be shifted later if it - // inserts elements that were previously reduced out as - // common at the end. - // - Edit last = e.get(e.size() - 1); - if (last.getType() == Edit.Type.INSERT) { - while (last.endB < b.size() - && cmp.equals(b, last.beginB, b, last.endB)) { - last.beginA++; - last.endA++; - last.beginB++; - last.endB++; - } - } - - return e; + return normalize(cmp, e, a, b); } case EMPTY: @@ -153,26 +140,128 @@ } /** + * Reorganize an {@link EditList} for better diff consistency. + *

+ * {@code DiffAlgorithms} may return {@link Edit.Type#INSERT} or + * {@link Edit.Type#DELETE} edits that can be "shifted". For + * example, the deleted section + *

+	 * -a
+	 * -b
+	 * -c
+	 *  a
+	 *  b
+	 *  c
+	 * 
+ * can be shifted down by 1, 2 or 3 locations. + *

+ * To avoid later merge issues, we shift such edits to a + * consistent location. {@code normalize} uses a simple strategy of + * shifting such edits to their latest possible location. + *

+ * This strategy may not always produce an aesthetically pleasing + * diff. For instance, it works well with + *

+	 *  function1 {
+	 *   ...
+	 *  }
+	 *
+	 * +function2 {
+	 * + ...
+	 * +}
+	 * +
+	 * function3 {
+	 * ...
+	 * }
+	 * 
+ * but less so for + *
+	 *  #
+	 *  # comment1
+	 *  #
+	 *  function1() {
+	 *  }
+	 *
+	 *  #
+	 * +# comment3
+	 * +#
+	 * +function3() {
+	 * +}
+	 * +
+	 * +#
+	 *  # comment2
+	 *  #
+	 *  function2() {
+	 *  }
+	 * 
+ * More + * sophisticated strategies are possible, say by calculating a + * suitable "aesthetic cost" for each possible position and using + * the lowest cost, but {@code normalize} just shifts edits + * to the end as much as possible. + * + * @param + * type of sequence being compared. + * @param cmp + * the comparator supplying the element equivalence function. + * @param e + * a modifiable edit list comparing the provided sequences. + * @param a + * the first (also known as old or pre-image) sequence. + * @param b + * the second (also known as new or post-image) sequence. + * @return a modifiable edit list with edit regions shifted to their + * latest possible location. The result list is never null. + * @since 4.7 + */ + private static EditList normalize( + SequenceComparator cmp, EditList e, S a, S b) { + Edit prev = null; + for (int i = e.size() - 1; i >= 0; i--) { + Edit cur = e.get(i); + Edit.Type curType = cur.getType(); + + int maxA = (prev == null) ? a.size() : prev.beginA; + int maxB = (prev == null) ? b.size() : prev.beginB; + + if (curType == Edit.Type.INSERT) { + while (cur.endA < maxA && cur.endB < maxB + && cmp.equals(b, cur.beginB, b, cur.endB)) { + cur.shift(1); + } + } else if (curType == Edit.Type.DELETE) { + while (cur.endA < maxA && cur.endB < maxB + && cmp.equals(a, cur.beginA, a, cur.endA)) { + cur.shift(1); + } + } + prev = cur; + } + return e; + } + + /** * Compare two sequences and identify a list of edits between them. * * This method should be invoked only after the two sequences have been * proven to have no common starting or ending elements. The expected * elimination of common starting and ending elements is automatically * performed by the {@link #diff(SequenceComparator, Sequence, Sequence)} - * method, which invokes this method using {@link Subsequence}s. + * method, which invokes this method using + * {@link org.eclipse.jgit.diff.Subsequence}s. * - * @param - * type of sequence being compared. * @param cmp * the comparator supplying the element equivalence function. * @param a * the first (also known as old or pre-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'A' side: {@link Edit#getBeginA()}, {@link Edit#getEndA()}. + * 'A' side: {@link org.eclipse.jgit.diff.Edit#getBeginA()}, + * {@link org.eclipse.jgit.diff.Edit#getEndA()}. * @param b * the second (also known as new or post-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'B' side: {@link Edit#getBeginB()}, {@link Edit#getEndB()}. + * 'B' side: {@link org.eclipse.jgit.diff.Edit#getBeginB()}, + * {@link org.eclipse.jgit.diff.Edit#getEndB()}. * @return a modifiable edit list comparing the two sequences. */ public abstract EditList diffNonCommon( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,14 +51,12 @@ import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.util.StringUtils; -/** Keeps track of diff related configuration options. */ +/** + * Keeps track of diff related configuration options. + */ public class DiffConfig { /** Key for {@link Config#get(SectionParser)}. */ - public static final Config.SectionParser KEY = new SectionParser() { - public DiffConfig parse(final Config cfg) { - return new DiffConfig(cfg); - } - }; + public static final Config.SectionParser KEY = DiffConfig::new; /** Permissible values for {@code diff.renames}. */ public static enum RenameDetectionType { @@ -87,22 +85,38 @@ ConfigConstants.CONFIG_KEY_RENAMELIMIT, 200); } - /** @return true if the prefix "a/" and "b/" should be suppressed. */ + /** + * If prefix should be suppressed + * + * @return true if the prefix "a/" and "b/" should be suppressed + */ public boolean isNoPrefix() { return noPrefix; } - /** @return true if rename detection is enabled by default. */ + /** + * If rename detection is enabled + * + * @return true if rename detection is enabled by default + */ public boolean isRenameDetectionEnabled() { return renameDetectionType != RenameDetectionType.FALSE; } - /** @return type of rename detection to perform. */ + /** + * Get the rename detection type + * + * @return type of rename detection to perform + */ public RenameDetectionType getRenameDetectionType() { return renameDetectionType; } - /** @return limit on number of paths to perform inexact rename detection. */ + /** + * Get the rename limit + * + * @return limit on number of paths to perform inexact rename detection + */ public int getRenameLimit() { return renameLimit; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,9 +48,11 @@ import java.util.Arrays; import java.util.List; +import org.eclipse.jgit.attributes.Attribute; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -58,7 +60,9 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilterMarker; -/** A value class representing a change to a file */ +/** + * A value class representing a change to a file + */ public class DiffEntry { /** Magical SHA1 used for file adds or deletes */ static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId @@ -107,9 +111,9 @@ * @param walk * the TreeWalk to walk through. Must have exactly two trees. * @return headers describing the changed files. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * When given TreeWalk doesn't have exactly two trees. */ public static List scan(TreeWalk walk) throws IOException { @@ -127,9 +131,9 @@ * @param includeTrees * include tree objects. * @return headers describing the changed files. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * when {@code includeTrees} is true and given TreeWalk is * recursive. Or when given TreeWalk doesn't have exactly two * trees @@ -155,9 +159,9 @@ * queried through {{@link #isMarked(int)} (with the index from * this passed array). * @return headers describing the changed files. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * when {@code includeTrees} is true and given TreeWalk is * recursive. Or when given TreeWalk doesn't have exactly two * trees @@ -179,7 +183,7 @@ else treeFilterMarker = null; - List r = new ArrayList(); + List r = new ArrayList<>(); MutableObjectId idBuf = new MutableObjectId(); while (walk.next()) { DiffEntry entry = new DiffEntry(); @@ -194,6 +198,11 @@ entry.newMode = walk.getFileMode(1); entry.newPath = entry.oldPath = walk.getPathString(); + if (walk.getAttributesNodeProvider() != null) { + entry.diffAttribute = walk.getAttributes() + .get(Constants.ATTR_DIFF); + } + if (treeFilterMarker != null) entry.treeFilterMarks = treeFilterMarker.getMarks(walk); @@ -280,6 +289,7 @@ del.newMode = FileMode.MISSING; del.newPath = DiffEntry.DEV_NULL; del.changeType = ChangeType.DELETE; + del.diffAttribute = entry.diffAttribute; DiffEntry add = new DiffEntry(); add.oldId = A_ZERO; @@ -290,6 +300,7 @@ add.newMode = entry.getNewMode(); add.newPath = entry.getNewPath(); add.changeType = ChangeType.ADD; + add.diffAttribute = entry.diffAttribute; return Arrays.asList(del, add); } @@ -304,6 +315,7 @@ r.newId = dst.newId; r.newMode = dst.newMode; r.newPath = dst.newPath; + r.diffAttribute = dst.diffAttribute; r.changeType = changeType; r.score = score; @@ -319,6 +331,13 @@ /** File name of the new (post-image). */ protected String newPath; + /** + * diff filter attribute + * + * @since 4.11 + */ + protected Attribute diffAttribute; + /** Old mode of the file, if described by the patch, else null. */ protected FileMode oldMode; @@ -392,12 +411,28 @@ return side == Side.OLD ? getOldPath() : getNewPath(); } - /** @return the old file mode, if described in the patch */ + /** + * @return the {@link Attribute} determining filters to be applied. + * @since 4.11 + */ + public Attribute getDiffAttribute() { + return diffAttribute; + } + + /** + * Get the old file mode + * + * @return the old file mode, if described in the patch + */ public FileMode getOldMode() { return oldMode; } - /** @return the new file mode, if described in the patch */ + /** + * Get the new file mode + * + * @return the new file mode, if described in the patch + */ public FileMode getNewMode() { return newMode; } @@ -413,15 +448,22 @@ return side == Side.OLD ? getOldMode() : getNewMode(); } - /** @return the type of change this patch makes on {@link #getNewPath()} */ + /** + * Get the change type + * + * @return the type of change this patch makes on {@link #getNewPath()} + */ public ChangeType getChangeType() { return changeType; } /** + * Get similarity score + * * @return similarity score between {@link #getOldPath()} and * {@link #getNewPath()} if {@link #getChangeType()} is - * {@link ChangeType#COPY} or {@link ChangeType#RENAME}. + * {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#COPY} or + * {@link org.eclipse.jgit.diff.DiffEntry.ChangeType#RENAME}. */ public int getScore() { return score; @@ -466,10 +508,9 @@ * * @param index * the index of the tree filter to check for (must be between 0 - * and {@link Integer#SIZE}). - * - * @return true, if the tree filter matched; false if not + * and {@link java.lang.Integer#SIZE}). * @since 2.3 + * @return a boolean. */ public boolean isMarked(int index) { return (treeFilterMarks & (1L << index)) != 0; @@ -498,6 +539,7 @@ return side == Side.OLD ? getOldId() : getNewId(); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -525,4 +567,4 @@ buf.append("]"); return buf.toString(); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,13 +66,14 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.AmbiguousObjectException; +import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -83,7 +84,6 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.FileHeader.PatchType; -import org.eclipse.jgit.patch.HunkHeader; import org.eclipse.jgit.revwalk.FollowFilter; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; @@ -98,8 +98,8 @@ import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.LfsFactory; import org.eclipse.jgit.util.QuotedString; -import org.eclipse.jgit.util.io.DisabledOutputStream; /** * Format a Git style patch script. @@ -112,15 +112,12 @@ /** Magic return content indicating it is empty or no content present. */ private static final byte[] EMPTY = new byte[] {}; - /** Magic return indicating the content is binary. */ - private static final byte[] BINARY = new byte[] {}; - private final OutputStream out; - private Repository db; - private ObjectReader reader; + private boolean closeReader; + private DiffConfig diffCfg; private int context = 3; @@ -145,6 +142,8 @@ private ContentSource.Pair source; + private Repository repository; + /** * Create a new formatter with a default level of context. * @@ -157,7 +156,11 @@ this.out = out; } - /** @return the stream we are outputting data to. */ + /** + * Get output stream + * + * @return the stream we are outputting data to + */ protected OutputStream getOutputStream() { return out; } @@ -172,28 +175,43 @@ * source repository holding referenced objects. */ public void setRepository(Repository repository) { - if (reader != null) - reader.close(); + this.repository = repository; + setReader(repository.newObjectReader(), repository.getConfig(), true); + } - db = repository; - reader = db.newObjectReader(); - diffCfg = db.getConfig().get(DiffConfig.KEY); + /** + * Set the repository the formatter can load object contents from. + * + * @param reader + * source reader holding referenced objects. Caller is responsible + * for closing the reader. + * @param cfg + * config specifying diff algorithm and rename detection options. + * @since 4.5 + */ + public void setReader(ObjectReader reader, Config cfg) { + setReader(reader, cfg, false); + } + + private void setReader(ObjectReader reader, Config cfg, boolean closeReader) { + close(); + this.closeReader = closeReader; + this.reader = reader; + this.diffCfg = cfg.get(DiffConfig.KEY); ContentSource cs = ContentSource.create(reader); source = new ContentSource.Pair(cs, cs); - DiffConfig dc = db.getConfig().get(DiffConfig.KEY); - if (dc.isNoPrefix()) { + if (diffCfg.isNoPrefix()) { setOldPrefix(""); //$NON-NLS-1$ setNewPrefix(""); //$NON-NLS-1$ } - setDetectRenames(dc.isRenameDetectionEnabled()); + setDetectRenames(diffCfg.isRenameDetectionEnabled()); - diffAlgorithm = DiffAlgorithm.getAlgorithm(db.getConfig().getEnum( + diffAlgorithm = DiffAlgorithm.getAlgorithm(cfg.getEnum( ConfigConstants.CONFIG_DIFF_SECTION, null, ConfigConstants.CONFIG_KEY_ALGORITHM, SupportedAlgorithm.HISTOGRAM)); - } /** @@ -312,7 +330,11 @@ return this.newPrefix; } - /** @return true if rename detection is enabled. */ + /** + * Get if rename detection is enabled + * + * @return true if rename detection is enabled + */ public boolean isDetectRenames() { return renameDetector != null; } @@ -330,13 +352,17 @@ */ public void setDetectRenames(boolean on) { if (on && renameDetector == null) { - assertHaveRepository(); - renameDetector = new RenameDetector(db); + assertHaveReader(); + renameDetector = new RenameDetector(reader, diffCfg); } else if (!on) renameDetector = null; } - /** @return the rename detector if rename detection is enabled. */ + /** + * Get rename detector + * + * @return the rename detector if rename detection is enabled + */ public RenameDetector getRenameDetector() { return renameDetector; } @@ -354,9 +380,10 @@ /** * Set the filter to produce only specific paths. * - * If the filter is an instance of {@link FollowFilter}, the filter path - * will be updated during successive scan or format invocations. The updated - * path can be obtained from {@link #getPathFilter()}. + * If the filter is an instance of + * {@link org.eclipse.jgit.revwalk.FollowFilter}, the filter path will be + * updated during successive scan or format invocations. The updated path + * can be obtained from {@link #getPathFilter()}. * * @param filter * the tree filter to apply. @@ -365,7 +392,11 @@ pathFilter = filter != null ? filter : TreeFilter.ALL; } - /** @return the current path filter. */ + /** + * Get path filter + * + * @return the current path filter + */ public TreeFilter getPathFilter() { return pathFilter; } @@ -373,7 +404,7 @@ /** * Flush the underlying output stream of this formatter. * - * @throws IOException + * @throws java.io.IOException * the stream's own flush method threw an exception. */ public void flush() throws IOException { @@ -381,14 +412,17 @@ } /** + * {@inheritDoc} + *

* Release the internal ObjectReader state. * * @since 4.0 */ @Override public void close() { - if (reader != null) + if (reader != null && closeReader) { reader.close(); + } } /** @@ -396,8 +430,8 @@ * * No output is created, instead only the file paths that are different are * returned. Callers may choose to format these paths themselves, or convert - * them into {@link FileHeader} instances with a complete edit list by - * calling {@link #toFileHeader(DiffEntry)}. + * them into {@link org.eclipse.jgit.patch.FileHeader} instances with a + * complete edit list by calling {@link #toFileHeader(DiffEntry)}. *

* Either side may be null to indicate that the tree has beed added or * removed. The diff will be computed against nothing. @@ -407,12 +441,12 @@ * @param b * the new (or updated) side or null * @return the paths that are different. - * @throws IOException + * @throws java.io.IOException * trees cannot be read or file contents cannot be read. */ public List scan(AnyObjectId a, AnyObjectId b) throws IOException { - assertHaveRepository(); + assertHaveReader(); try (RevWalk rw = new RevWalk(reader)) { RevTree aTree = a != null ? rw.parseTree(a) : null; @@ -426,8 +460,8 @@ * * No output is created, instead only the file paths that are different are * returned. Callers may choose to format these paths themselves, or convert - * them into {@link FileHeader} instances with a complete edit list by - * calling {@link #toFileHeader(DiffEntry)}. + * them into {@link org.eclipse.jgit.patch.FileHeader} instances with a + * complete edit list by calling {@link #toFileHeader(DiffEntry)}. *

* Either side may be null to indicate that the tree has beed added or * removed. The diff will be computed against nothing. @@ -437,11 +471,11 @@ * @param b * the new (or updated) side or null * @return the paths that are different. - * @throws IOException + * @throws java.io.IOException * trees cannot be read or file contents cannot be read. */ public List scan(RevTree a, RevTree b) throws IOException { - assertHaveRepository(); + assertHaveReader(); AbstractTreeIterator aIterator = makeIteratorFromTreeOrNull(a); AbstractTreeIterator bIterator = makeIteratorFromTreeOrNull(b); @@ -463,20 +497,20 @@ * * No output is created, instead only the file paths that are different are * returned. Callers may choose to format these paths themselves, or convert - * them into {@link FileHeader} instances with a complete edit list by - * calling {@link #toFileHeader(DiffEntry)}. + * them into {@link org.eclipse.jgit.patch.FileHeader} instances with a + * complete edit list by calling {@link #toFileHeader(DiffEntry)}. * * @param a * the old (or previous) side. * @param b * the new (or updated) side. * @return the paths that are different. - * @throws IOException + * @throws java.io.IOException * trees cannot be read or file contents cannot be read. */ public List scan(AbstractTreeIterator a, AbstractTreeIterator b) throws IOException { - assertHaveRepository(); + assertHaveReader(); TreeWalk walk = new TreeWalk(reader); walk.addTree(a); @@ -583,7 +617,7 @@ * the old (or previous) side or null * @param b * the new (or updated) side or null - * @throws IOException + * @throws java.io.IOException * trees cannot be read, file contents cannot be read, or the * patch cannot be output. */ @@ -605,7 +639,7 @@ * the old (or previous) side or null * @param b * the new (or updated) side or null - * @throws IOException + * @throws java.io.IOException * trees cannot be read, file contents cannot be read, or the * patch cannot be output. */ @@ -626,7 +660,7 @@ * the old (or previous) side or null * @param b * the new (or updated) side or null - * @throws IOException + * @throws java.io.IOException * trees cannot be read, file contents cannot be read, or the * patch cannot be output. */ @@ -642,7 +676,7 @@ * * @param entries * entries describing the affected files. - * @throws IOException + * @throws java.io.IOException * a file's content cannot be read, or the output stream cannot * be written to. */ @@ -656,7 +690,7 @@ * * @param ent * the entry to be formatted. - * @throws IOException + * @throws java.io.IOException * a file's content cannot be read, or the output stream cannot * be written to. */ @@ -666,7 +700,7 @@ } private static byte[] writeGitLinkText(AbbreviatedObjectId id) { - if (id.toObjectId().equals(ObjectId.zeroId())) { + if (ObjectId.zeroId().equals(id.toObjectId())) { return EMPTY; } return encodeASCII("Subproject commit " + id.name() //$NON-NLS-1$ @@ -674,7 +708,7 @@ } private String format(AbbreviatedObjectId id) { - if (id.isComplete() && db != null) { + if (id.isComplete() && reader != null) { try { id = reader.abbreviate(id.toObjectId(), abbreviationLength); } catch (IOException cannotAbbreviate) { @@ -699,11 +733,13 @@ * existing file header containing the header lines to copy. * @param a * text source for the pre-image version of the content. This - * must match the content of {@link FileHeader#getOldId()}. + * must match the content of + * {@link org.eclipse.jgit.patch.FileHeader#getOldId()}. * @param b * text source for the post-image version of the content. This - * must match the content of {@link FileHeader#getNewId()}. - * @throws IOException + * must match the content of + * {@link org.eclipse.jgit.patch.FileHeader#getNewId()}. + * @throws java.io.IOException * writing to the supplied stream failed. */ public void format(final FileHeader head, final RawText a, final RawText b) @@ -730,7 +766,7 @@ * the text A which was compared * @param b * the text B which was compared - * @throws IOException + * @throws java.io.IOException */ public void format(final EditList edits, final RawText a, final RawText b) throws IOException { @@ -778,7 +814,7 @@ * RawText for accessing raw data * @param line * the line number within text - * @throws IOException + * @throws java.io.IOException */ protected void writeContextLine(final RawText text, final int line) throws IOException { @@ -796,7 +832,7 @@ * RawText for accessing raw data * @param line * the line number within text - * @throws IOException + * @throws java.io.IOException */ protected void writeAddedLine(final RawText text, final int line) throws IOException { @@ -810,7 +846,7 @@ * RawText for accessing raw data * @param line * the line number within text - * @throws IOException + * @throws java.io.IOException */ protected void writeRemovedLine(final RawText text, final int line) throws IOException { @@ -828,7 +864,7 @@ * within second source * @param bEndLine * within second source - * @throws IOException + * @throws java.io.IOException */ protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException { @@ -881,7 +917,7 @@ * the text object to obtain the line from. * @param cur * line number to output. - * @throws IOException + * @throws java.io.IOException * the stream threw an exception while writing to it. */ protected void writeLine(final char prefix, final RawText text, @@ -892,24 +928,26 @@ } /** - * Creates a {@link FileHeader} representing the given {@link DiffEntry} + * Creates a {@link org.eclipse.jgit.patch.FileHeader} representing the + * given {@link org.eclipse.jgit.diff.DiffEntry} *

* This method does not use the OutputStream associated with this * DiffFormatter instance. It is therefore safe to instantiate this - * DiffFormatter instance with a {@link DisabledOutputStream} if this method - * is the only one that will be used. + * DiffFormatter instance with a + * {@link org.eclipse.jgit.util.io.DisabledOutputStream} if this method is + * the only one that will be used. * * @param ent * the DiffEntry to create the FileHeader for * @return a FileHeader representing the DiffEntry. The FileHeader's buffer * will contain only the header of the diff output. It will also - * contain one {@link HunkHeader}. - * @throws IOException + * contain one {@link org.eclipse.jgit.patch.HunkHeader}. + * @throws java.io.IOException * the stream threw an exception while writing to it, or one of * the blobs referenced by the DiffEntry could not be read. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * one of the blobs referenced by the DiffEntry is corrupt. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one of the blobs referenced by the DiffEntry is missing. */ public FileHeader toFileHeader(DiffEntry ent) throws IOException, @@ -938,47 +976,50 @@ // Content not changed (e.g. only mode, pure rename) editList = new EditList(); type = PatchType.UNIFIED; + res.header = new FileHeader(buf.toByteArray(), editList, type); + return res; + } - } else { - assertHaveRepository(); - - byte[] aRaw, bRaw; + assertHaveReader(); - if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) { - aRaw = writeGitLinkText(ent.getOldId()); - bRaw = writeGitLinkText(ent.getNewId()); - } else { + RawText aRaw = null; + RawText bRaw = null; + if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) { + aRaw = new RawText(writeGitLinkText(ent.getOldId())); + bRaw = new RawText(writeGitLinkText(ent.getNewId())); + } else { + try { aRaw = open(OLD, ent); bRaw = open(NEW, ent); - } - - if (aRaw == BINARY || bRaw == BINARY // - || RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) { + } catch (BinaryBlobException e) { + // Do nothing; we check for null below. formatOldNewPaths(buf, ent); buf.write(encodeASCII("Binary files differ\n")); //$NON-NLS-1$ editList = new EditList(); type = PatchType.BINARY; + res.header = new FileHeader(buf.toByteArray(), editList, type); + return res; + } + } - } else { - res.a = new RawText(aRaw); - res.b = new RawText(bRaw); - editList = diff(res.a, res.b); - type = PatchType.UNIFIED; - - switch (ent.getChangeType()) { - case RENAME: - case COPY: - if (!editList.isEmpty()) - formatOldNewPaths(buf, ent); - break; - - default: + res.a = aRaw; + res.b = bRaw; + editList = diff(res.a, res.b); + type = PatchType.UNIFIED; + + switch (ent.getChangeType()) { + case RENAME: + case COPY: + if (!editList.isEmpty()) formatOldNewPaths(buf, ent); - break; - } - } + break; + + default: + formatOldNewPaths(buf, ent); + break; } + res.header = new FileHeader(buf.toByteArray(), editList, type); return res; } @@ -987,18 +1028,19 @@ return diffAlgorithm.diff(comparator, a, b); } - private void assertHaveRepository() { - if (db == null) - throw new IllegalStateException(JGitText.get().repositoryIsRequired); + private void assertHaveReader() { + if (reader == null) { + throw new IllegalStateException(JGitText.get().readerIsRequired); + } } - private byte[] open(DiffEntry.Side side, DiffEntry entry) - throws IOException { + private RawText open(DiffEntry.Side side, DiffEntry entry) + throws IOException, BinaryBlobException { if (entry.getMode(side) == FileMode.MISSING) - return EMPTY; + return RawText.EMPTY_TEXT; if (entry.getMode(side).getObjectType() != Constants.OBJ_BLOB) - return EMPTY; + return RawText.EMPTY_TEXT; AbbreviatedObjectId id = entry.getId(side); if (!id.isComplete()) { @@ -1019,23 +1061,9 @@ throw new AmbiguousObjectException(id, ids); } - try { - ObjectLoader ldr = source.open(side, entry); - return ldr.getBytes(binaryFileThreshold); - - } catch (LargeObjectException.ExceedsLimit overLimit) { - return BINARY; - - } catch (LargeObjectException.ExceedsByteArrayLimit overLimit) { - return BINARY; - - } catch (LargeObjectException.OutOfMemory tooBig) { - return BINARY; - - } catch (LargeObjectException tooBig) { - tooBig.setObjectId(id.toObjectId()); - throw tooBig; - } + ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(repository, + source.open(side, entry), entry.getDiffAttribute()); + return RawText.load(ldr, binaryFileThreshold); } /** @@ -1044,12 +1072,12 @@ * @param o * The stream the formatter will write the first header line to * @param type - * The {@link ChangeType} + * The {@link org.eclipse.jgit.diff.DiffEntry.ChangeType} * @param oldPath * old path to the file * @param newPath * new path to the file - * @throws IOException + * @throws java.io.IOException * the stream threw an exception while writing to it. */ protected void formatGitDiffFirstHeaderLine(ByteArrayOutputStream o, @@ -1134,11 +1162,13 @@ } /** + * Format index line + * * @param o * the stream the formatter will write line data to * @param ent * the DiffEntry to create the FileHeader for - * @throws IOException + * @throws java.io.IOException * writing to the supplied stream failed. */ protected void formatIndexLine(OutputStream o, DiffEntry ent) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java 2019-09-03 12:37:49.000000000 +0000 @@ -118,7 +118,11 @@ endB = be; } - /** @return the type of this region */ + /** + * Get type + * + * @return the type of this region + */ public final Type getType() { if (beginA < endA) { if (beginB < endB) @@ -134,42 +138,86 @@ } } - /** @return true if the edit is empty (lengths of both a and b is zero). */ + /** + * Whether edit is empty + * + * @return {@code true} if the edit is empty (lengths of both a and b is + * zero) + */ public final boolean isEmpty() { return beginA == endA && beginB == endB; } - /** @return start point in sequence A. */ + /** + * Get start point in sequence A + * + * @return start point in sequence A + */ public final int getBeginA() { return beginA; } - /** @return end point in sequence A. */ + /** + * Get end point in sequence A + * + * @return end point in sequence A + */ public final int getEndA() { return endA; } - /** @return start point in sequence B. */ + /** + * Get start point in sequence B + * + * @return start point in sequence B + */ public final int getBeginB() { return beginB; } - /** @return end point in sequence B. */ + /** + * Get end point in sequence B + * + * @return end point in sequence B + */ public final int getEndB() { return endB; } - /** @return length of the region in A. */ + /** + * Get length of the region in A + * + * @return length of the region in A + */ public final int getLengthA() { return endA - beginA; } - /** @return length of the region in B. */ + /** + * Get length of the region in B + * + * @return return length of the region in B + */ public final int getLengthB() { return endB - beginB; } /** + * Move the edit region by the specified amount. + * + * @param amount + * the region is shifted by this amount, and can be positive or + * negative. + * @since 4.8 + */ + public final void shift(int amount) { + beginA += amount; + endA += amount; + beginB += amount; + endB += amount; + } + + /** * Construct a new edit representing the region before cut. * * @param cut @@ -195,17 +243,23 @@ return new Edit(cut.endA, endA, cut.endB, endB); } - /** Increase {@link #getEndA()} by 1. */ + /** + * Increase {@link #getEndA()} by 1. + */ public void extendA() { endA++; } - /** Increase {@link #getEndB()} by 1. */ + /** + * Increase {@link #getEndB()} by 1. + */ public void extendB() { endB++; } - /** Swap A and B, so the edit goes the other direction. */ + /** + * Swap A and B, so the edit goes the other direction. + */ public void swap() { final int sBegin = beginA; final int sEnd = endA; @@ -217,11 +271,13 @@ endB = sEnd; } + /** {@inheritDoc} */ @Override public int hashCode() { return beginA ^ endA; } + /** {@inheritDoc} */ @Override public boolean equals(final Object o) { if (o instanceof Edit) { @@ -232,6 +288,7 @@ return false; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ import java.util.ArrayList; -/** Specialized list of {@link Edit}s in a document. */ +/** + * Specialized list of {@link org.eclipse.jgit.diff.Edit}s in a document. + */ public class EditList extends ArrayList { private static final long serialVersionUID = 1L; @@ -62,7 +64,9 @@ return res; } - /** Create a new, empty edit list. */ + /** + * Create a new, empty edit list. + */ public EditList() { super(16); } @@ -78,6 +82,7 @@ super(capacity); } + /** {@inheritDoc} */ @Override public String toString() { return "EditList" + super.toString(); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,13 +44,16 @@ package org.eclipse.jgit.diff; /** - * Wrap another comparator for use with {@link HashedSequence}. + * Wrap another comparator for use with + * {@link org.eclipse.jgit.diff.HashedSequence}. *

* This comparator acts as a proxy for the real comparator, evaluating the * cached hash code before testing the underlying comparator's equality. - * Comparators of this type must be used with a {@link HashedSequence}. + * Comparators of this type must be used with a + * {@link org.eclipse.jgit.diff.HashedSequence}. *

- * To construct an instance of this type use {@link HashedSequencePair}. + * To construct an instance of this type use + * {@link org.eclipse.jgit.diff.HashedSequencePair}. * * @param * the base sequence type. @@ -63,6 +66,7 @@ this.cmp = cmp; } + /** {@inheritDoc} */ @Override public boolean equals(HashedSequence a, int ai, // HashedSequence b, int bi) { @@ -70,6 +74,7 @@ && cmp.equals(a.base, ai, b.base, bi); } + /** {@inheritDoc} */ @Override public int hash(HashedSequence seq, int ptr) { return seq.hashes[ptr]; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,13 +44,15 @@ package org.eclipse.jgit.diff; /** - * Wraps a {@link Sequence} to assign hash codes to elements. + * Wraps a {@link org.eclipse.jgit.diff.Sequence} to assign hash codes to + * elements. *

* This sequence acts as a proxy for the real sequence, caching element hash * codes so they don't need to be recomputed each time. Sequences of this type - * must be used with a {@link HashedSequenceComparator}. + * must be used with a {@link org.eclipse.jgit.diff.HashedSequenceComparator}. *

- * To construct an instance of this type use {@link HashedSequencePair}. + * To construct an instance of this type use + * {@link org.eclipse.jgit.diff.HashedSequencePair}. * * @param * the base sequence type. @@ -65,6 +67,7 @@ this.hashes = hashes; } + /** {@inheritDoc} */ @Override public int size() { return base.size(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,8 @@ package org.eclipse.jgit.diff; /** - * Wraps two {@link Sequence} instances to cache their element hash codes. + * Wraps two {@link org.eclipse.jgit.diff.Sequence} instances to cache their + * element hash codes. *

* This pair wraps two sequences that contain cached hash codes for the input * sequences. @@ -79,19 +80,31 @@ this.baseB = b; } - /** @return obtain a comparator that uses the cached hash codes. */ + /** + * Get comparator + * + * @return obtain a comparator that uses the cached hash codes + */ public HashedSequenceComparator getComparator() { - return new HashedSequenceComparator(cmp); + return new HashedSequenceComparator<>(cmp); } - /** @return wrapper around A that includes cached hash codes. */ + /** + * Get A + * + * @return wrapper around A that includes cached hash codes + */ public HashedSequence getA() { if (cachedA == null) cachedA = wrap(baseA); return cachedA; } - /** @return wrapper around B that includes cached hash codes. */ + /** + * Get B + * + * @return wrapper around B that includes cached hash codes + */ public HashedSequence getB() { if (cachedB == null) cachedB = wrap(baseB); @@ -103,6 +116,6 @@ final int[] hashes = new int[end]; for (int ptr = 0; ptr < end; ptr++) hashes[ptr] = cmp.hash(base, ptr); - return new HashedSequence(base, hashes); + return new HashedSequence<>(base, hashes); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java 2019-09-03 12:37:49.000000000 +0000 @@ -85,8 +85,9 @@ * So long as {@link #setMaxChainLength(int)} is a small constant (such as 64), * the algorithm runs in O(N * D) time, where N is the sum of the input lengths * and D is the number of edits in the resulting EditList. If the supplied - * {@link SequenceComparator} has a good hash function, this implementation - * typically out-performs {@link MyersDiff}, even though its theoretical running + * {@link org.eclipse.jgit.diff.SequenceComparator} has a good hash function, + * this implementation typically out-performs + * {@link org.eclipse.jgit.diff.MyersDiff}, even though its theoretical running * time is the same. *

* This implementation has an internal limitation that prevents it from handling @@ -94,7 +95,7 @@ */ public class HistogramDiff extends LowLevelDiffAlgorithm { /** Algorithm to use when there are too many element occurrences. */ - private DiffAlgorithm fallback = MyersDiff.INSTANCE; + DiffAlgorithm fallback = MyersDiff.INSTANCE; /** * Maximum number of positions to consider for a given element hash. @@ -103,7 +104,7 @@ * size is capped to ensure search is linear time at O(len_A + len_B) rather * than quadratic at O(len_A * len_B). */ - private int maxChainLength = 64; + int maxChainLength = 64; /** * Set the algorithm used when there are too many element occurrences. @@ -130,17 +131,19 @@ maxChainLength = maxLen; } + /** {@inheritDoc} */ + @Override public void diffNonCommon(EditList edits, HashedSequenceComparator cmp, HashedSequence a, HashedSequence b, Edit region) { - new State(edits, cmp, a, b).diffRegion(region); + new State<>(edits, cmp, a, b).diffRegion(region); } private class State { private final HashedSequenceComparator cmp; private final HashedSequence a; private final HashedSequence b; - private final List queue = new ArrayList(); + private final List queue = new ArrayList<>(); /** Result edits we have determined that must be made to convert a to b. */ final EditList edits; @@ -160,7 +163,7 @@ } private void diffReplace(Edit r) { - Edit lcs = new HistogramDiffIndex(maxChainLength, cmp, a, b, r) + Edit lcs = new HistogramDiffIndex<>(maxChainLength, cmp, a, b, r) .findLongestCommonSequence(); if (lcs != null) { // If we were given an edit, we can prove a result here. @@ -213,7 +216,7 @@ } private SubsequenceComparator> subcmp() { - return new SubsequenceComparator>(cmp); + return new SubsequenceComparator<>(cmp); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,12 +43,15 @@ package org.eclipse.jgit.diff; -/** Compares two sequences primarily based upon hash codes. */ +/** + * Compares two sequences primarily based upon hash codes. + */ public abstract class LowLevelDiffAlgorithm extends DiffAlgorithm { + /** {@inheritDoc} */ @Override public EditList diffNonCommon( SequenceComparator cmp, S a, S b) { - HashedSequencePair p = new HashedSequencePair(cmp, a, b); + HashedSequencePair p = new HashedSequencePair<>(cmp, a, b); HashedSequenceComparator hc = p.getComparator(); HashedSequence ha = p.getA(); HashedSequence hb = p.getB(); @@ -67,10 +70,9 @@ * proven to have no common starting or ending elements. The expected * elimination of common starting and ending elements is automatically * performed by the {@link #diff(SequenceComparator, Sequence, Sequence)} - * method, which invokes this method using {@link Subsequence}s. + * method, which invokes this method using + * {@link org.eclipse.jgit.diff.Subsequence}s. * - * @param - * type of sequence being compared. * @param edits * result list to append the region's edits onto. * @param cmp @@ -78,11 +80,13 @@ * @param a * the first (also known as old or pre-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'A' side: {@link Edit#getBeginA()}, {@link Edit#getEndA()}. + * 'A' side: {@link org.eclipse.jgit.diff.Edit#getBeginA()}, + * {@link org.eclipse.jgit.diff.Edit#getEndA()}. * @param b * the second (also known as new or post-image) sequence. Edits * returned by this algorithm will reference indexes using the - * 'B' side: {@link Edit#getBeginB()}, {@link Edit#getEndB()}. + * 'B' side: {@link org.eclipse.jgit.diff.Edit#getBeginB()}, + * {@link org.eclipse.jgit.diff.Edit#getEndB()}. * @param region * the region being compared within the two sequences. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java 2019-09-03 12:37:49.000000000 +0000 @@ -119,7 +119,7 @@ public void diffNonCommon(EditList edits, HashedSequenceComparator cmp, HashedSequence a, HashedSequence b, Edit region) { - new MyersDiff(edits, cmp, a, b, region); + new MyersDiff<>(edits, cmp, a, b, region); } }; @@ -460,6 +460,7 @@ } class ForwardEditPaths extends EditPaths { + @Override final int snake(int k, int x) { for (; x < endA && k + x < endB; x++) if (!cmp.equals(a, x, b, k + x)) @@ -467,18 +468,22 @@ return x; } + @Override final int getLeft(final int x) { return x; } + @Override final int getRight(final int x) { return x + 1; } + @Override final boolean isBetter(final int left, final int right) { return left > right; } + @Override final void adjustMinMaxK(final int k, final int x) { if (x >= endA || k + x >= endB) { if (k > backward.middleK) @@ -488,6 +493,7 @@ } } + @Override final boolean meets(int d, int k, int x, long snake) { if (k < backward.beginK || k > backward.endK) return false; @@ -502,6 +508,7 @@ } class BackwardEditPaths extends EditPaths { + @Override final int snake(int k, int x) { for (; x > beginA && k + x > beginB; x--) if (!cmp.equals(a, x - 1, b, k + x - 1)) @@ -509,18 +516,22 @@ return x; } + @Override final int getLeft(final int x) { return x - 1; } + @Override final int getRight(final int x) { return x; } + @Override final boolean isBetter(final int left, final int right) { return left < right; } + @Override final void adjustMinMaxK(final int k, final int x) { if (x <= beginA || k + x <= beginB) { if (k > forward.middleK) @@ -530,6 +541,7 @@ } } + @Override final boolean meets(int d, int k, int x, long snake) { if (k < forward.beginK || k > forward.endK) return false; @@ -545,7 +557,10 @@ } /** - * @param args two filenames specifying the contents to be diffed + * Main method + * + * @param args + * two filenames specifying the contents to be diffed */ public static void main(String[] args) { if (args.length != 2) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,7 +58,9 @@ private final MessageDigest digest; - /** Initialize a formatter to compute a patch id. */ + /** + * Initialize a formatter to compute a patch id. + */ public PatchIdDiffFormatter() { super(new DigestOutputStream(NullOutputStream.INSTANCE, Constants.newMessageDigest())); @@ -74,12 +76,14 @@ return ObjectId.fromRaw(digest.digest()); } + /** {@inheritDoc} */ @Override protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException { // The hunk header is not taken into account for patch id calculation } + /** {@inheritDoc} */ @Override protected void formatIndexLine(OutputStream o, DiffEntry ent) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.util.IntList; -/** Equivalence function for {@link RawText}. */ +/** + * Equivalence function for {@link org.eclipse.jgit.diff.RawText}. + */ public abstract class RawTextComparator extends SequenceComparator { /** No special treatment. */ public static final RawTextComparator DEFAULT = new RawTextComparator() { @@ -134,7 +136,9 @@ } }; - /** Ignores leading whitespace. */ + /** + * Ignore leading whitespace. + **/ public static final RawTextComparator WS_IGNORE_LEADING = new RawTextComparator() { @Override public boolean equals(RawText a, int ai, RawText b, int bi) { @@ -262,6 +266,7 @@ return hashRegion(seq.content, begin, end); } + /** {@inheritDoc} */ @Override public Edit reduceCommonStartEnd(RawText a, RawText b, Edit e) { // This is a faster exact match based form that tries to improve diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,11 +44,15 @@ package org.eclipse.jgit.diff; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.eclipse.jgit.errors.BinaryBlobException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IntList; import org.eclipse.jgit.util.RawParseUtils; @@ -66,11 +70,11 @@ * they are converting from "line number" to "element index". */ public class RawText extends Sequence { - /** A Rawtext of length 0 */ + /** A RawText of length 0 */ public static final RawText EMPTY_TEXT = new RawText(new byte[0]); /** Number of bytes to check for heuristics in {@link #isBinary(byte[])} */ - private static final int FIRST_FEW_BYTES = 8000; + static final int FIRST_FEW_BYTES = 8000; /** The file content for this sequence. */ protected final byte[] content; @@ -99,14 +103,24 @@ * * @param file * the text file. - * @throws IOException + * @throws java.io.IOException * if Exceptions occur while reading the file */ public RawText(File file) throws IOException { this(IO.readFully(file)); } + /** + * @return the raw, unprocessed content read. + * @since 4.11 + */ + public byte[] getRawContent() { + return content; + } + /** @return total number of items in the sequence. */ + /** {@inheritDoc} */ + @Override public int size() { // The line map is always 2 entries larger than the number of lines in // the file. Index 0 is padded out/unused. The last index is the total @@ -130,7 +144,7 @@ * @param i * index of the line to extract. Note this is 0-based, so line * number 1 is actually index 0. - * @throws IOException + * @throws java.io.IOException * the stream write operation failed. */ public void writeLine(final OutputStream out, final int i) @@ -239,7 +253,7 @@ * @param raw * input stream containing the raw file content. * @return true if raw is likely to be a binary file, false otherwise - * @throws IOException + * @throws java.io.IOException * if input stream could not be read */ public static boolean isBinary(InputStream raw) throws IOException { @@ -294,4 +308,68 @@ else return "\n"; //$NON-NLS-1$ } + + /** + * Read a blob object into RawText, or throw BinaryBlobException if the blob + * is binary. + * + * @param ldr + * the ObjectLoader for the blob + * @param threshold + * if the blob is larger than this size, it is always assumed to + * be binary. + * @since 4.10 + * @return the RawText representing the blob. + * @throws org.eclipse.jgit.errors.BinaryBlobException + * if the blob contains binary data. + * @throws java.io.IOException + * if the input could not be read. + */ + public static RawText load(ObjectLoader ldr, int threshold) throws IOException, BinaryBlobException { + long sz = ldr.getSize(); + + if (sz > threshold) { + throw new BinaryBlobException(); + } + + if (sz <= FIRST_FEW_BYTES) { + byte[] data = ldr.getCachedBytes(FIRST_FEW_BYTES); + if (isBinary(data)) { + throw new BinaryBlobException(); + } + return new RawText(data); + } + + byte[] head = new byte[FIRST_FEW_BYTES]; + try (InputStream stream = ldr.openStream()) { + int off = 0; + int left = head.length; + while (left > 0) { + int n = stream.read(head, off, left); + if (n < 0) { + throw new EOFException(); + } + left -= n; + + while (n > 0) { + if (head[off] == '\0') { + throw new BinaryBlobException(); + } + off++; + n--; + } + } + + byte data[]; + try { + data = new byte[(int)sz]; + } catch (OutOfMemoryError e) { + throw new LargeObjectException.OutOfMemory(e); + } + + System.arraycopy(head, 0, data, 0, head.length); + IO.readFully(stream, data, off, (int) (sz-off)); + return new RawText(data); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,11 +65,14 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; -/** Detect and resolve object renames. */ +/** + * Detect and resolve object renames. + */ public class RenameDetector { private static final int EXACT_RENAME_SCORE = 100; private static final Comparator DIFF_COMPARATOR = new Comparator() { + @Override public int compare(DiffEntry a, DiffEntry b) { int cmp = nameOf(a).compareTo(nameOf(b)); if (cmp == 0) @@ -155,6 +158,8 @@ } /** + * Get rename score + * * @return minimum score required to pair an add/delete as a rename. The * score ranges are within the bounds of (0, 100). */ @@ -172,7 +177,7 @@ * * @param score * new rename score, must be within [0, 100]. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the score was not within [0, 100]. */ public void setRenameScore(int score) { @@ -183,6 +188,8 @@ } /** + * Get break score + * * @return the similarity score required to keep modified file pairs * together. Any modify pairs that score below this will be broken * apart into separate add/deletes. Values less than or equal to @@ -194,6 +201,8 @@ } /** + * Set break score + * * @param breakScore * the similarity score required to keep modified file pairs * together. Any modify pairs that score below this will be @@ -205,7 +214,11 @@ this.breakScore = breakScore; } - /** @return limit on number of paths to perform inexact rename detection. */ + /** + * Get rename limit + * + * @return limit on number of paths to perform inexact rename detection + */ public int getRenameLimit() { return renameLimit; } @@ -219,7 +232,9 @@ * must be allocated, and 1,000,000 file compares may need to be performed. * * @param limit - * new file limit. + * new file limit. 0 means no limit; a negative number means no + * inexact rename detection will be performed, only exact rename + * detection. */ public void setRenameLimit(int limit) { renameLimit = limit; @@ -247,7 +262,7 @@ * * @param entriesToAdd * one or more entries to add. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if {@code getEntries} was already invoked. */ public void addAll(Collection entriesToAdd) { @@ -287,7 +302,7 @@ * * @param entry * to add. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if {@code getEntries} was already invoked. */ public void add(DiffEntry entry) { @@ -299,9 +314,9 @@ *

* This convenience function runs without a progress monitor. * - * @return an unmodifiable list of {@link DiffEntry}s representing all files - * that have been changed. - * @throws IOException + * @return an unmodifiable list of {@link org.eclipse.jgit.diff.DiffEntry}s + * representing all files that have been changed. + * @throws java.io.IOException * file contents cannot be read from the repository. */ public List compute() throws IOException { @@ -313,9 +328,9 @@ * * @param pm * report progress during the detection phases. - * @return an unmodifiable list of {@link DiffEntry}s representing all files - * that have been changed. - * @throws IOException + * @return an unmodifiable list of {@link org.eclipse.jgit.diff.DiffEntry}s + * representing all files that have been changed. + * @throws java.io.IOException * file contents cannot be read from the repository. */ public List compute(ProgressMonitor pm) throws IOException { @@ -336,9 +351,9 @@ * reader to obtain objects from the repository with. * @param pm * report progress during the detection phases. - * @return an unmodifiable list of {@link DiffEntry}s representing all files - * that have been changed. - * @throws IOException + * @return an unmodifiable list of {@link org.eclipse.jgit.diff.DiffEntry}s + * representing all files that have been changed. + * @throws java.io.IOException * file contents cannot be read from the repository. */ public List compute(ObjectReader reader, ProgressMonitor pm) @@ -354,9 +369,9 @@ * reader to obtain objects from the repository with. * @param pm * report progress during the detection phases. - * @return an unmodifiable list of {@link DiffEntry}s representing all files - * that have been changed. - * @throws IOException + * @return an unmodifiable list of {@link org.eclipse.jgit.diff.DiffEntry}s + * representing all files that have been changed. + * @throws java.io.IOException * file contents cannot be read from the repository. */ public List compute(ContentSource.Pair reader, ProgressMonitor pm) @@ -390,17 +405,19 @@ return Collections.unmodifiableList(entries); } - /** Reset this rename detector for another rename detection pass. */ + /** + * Reset this rename detector for another rename detection pass. + */ public void reset() { - entries = new ArrayList(); - deleted = new ArrayList(); - added = new ArrayList(); + entries = new ArrayList<>(); + deleted = new ArrayList<>(); + added = new ArrayList<>(); done = false; } private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm) throws IOException { - ArrayList newEntries = new ArrayList(entries.size()); + ArrayList newEntries = new ArrayList<>(entries.size()); pm.beginTask(JGitText.get().renamesBreakingModifies, entries.size()); @@ -427,8 +444,8 @@ } private void rejoinModifies(ProgressMonitor pm) { - HashMap nameMap = new HashMap(); - ArrayList newAdded = new ArrayList(added.size()); + HashMap nameMap = new HashMap<>(); + ArrayList newAdded = new ArrayList<>(added.size()); pm.beginTask(JGitText.get().renamesRejoiningModifies, added.size() + deleted.size()); @@ -455,7 +472,7 @@ } added = newAdded; - deleted = new ArrayList(nameMap.values()); + deleted = new ArrayList<>(nameMap.values()); } private int calculateModifyScore(ContentSource.Pair reader, DiffEntry d) @@ -507,8 +524,8 @@ HashMap deletedMap = populateMap(deleted, pm); HashMap addedMap = populateMap(added, pm); - ArrayList uniqueAdds = new ArrayList(added.size()); - ArrayList> nonUniqueAdds = new ArrayList>(); + ArrayList uniqueAdds = new ArrayList<>(added.size()); + ArrayList> nonUniqueAdds = new ArrayList<>(); for (Object o : addedMap.values()) { if (o instanceof DiffEntry) @@ -517,7 +534,7 @@ nonUniqueAdds.add((List) o); } - ArrayList left = new ArrayList(added.size()); + ArrayList left = new ArrayList<>(added.size()); for (DiffEntry a : uniqueAdds) { Object del = deletedMap.get(a.newId); @@ -626,7 +643,7 @@ } added = left; - deleted = new ArrayList(deletedMap.size()); + deleted = new ArrayList<>(deletedMap.size()); for (Object o : deletedMap.values()) { if (o instanceof DiffEntry) { DiffEntry e = (DiffEntry) o; @@ -676,11 +693,11 @@ @SuppressWarnings("unchecked") private HashMap populateMap( List diffEntries, ProgressMonitor pm) { - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); for (DiffEntry de : diffEntries) { Object old = map.put(id(de), de); if (old instanceof DiffEntry) { - ArrayList list = new ArrayList(2); + ArrayList list = new ArrayList<>(2); list.add((DiffEntry) old); list.add(de); map.put(id(de), list); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,8 @@ package org.eclipse.jgit.diff; /** - * Equivalence function for a {@link Sequence} compared by difference algorithm. + * Equivalence function for a {@link org.eclipse.jgit.diff.Sequence} compared by + * difference algorithm. *

* Difference algorithms can use a comparator to compare portions of two * sequences and discover the minimal edits required to transform from one diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,13 +52,19 @@ * Unlike a List, the members of the sequence are not directly obtainable. *

* Implementations of Sequence are primarily intended for use in content - * difference detection algorithms, to produce an {@link EditList} of - * {@link Edit} instances describing how two Sequence instances differ. + * difference detection algorithms, to produce an + * {@link org.eclipse.jgit.diff.EditList} of {@link org.eclipse.jgit.diff.Edit} + * instances describing how two Sequence instances differ. *

* To be compared against another Sequence of the same type, a supporting - * {@link SequenceComparator} must also be supplied. + * {@link org.eclipse.jgit.diff.SequenceComparator} must also be supplied. */ public abstract class Sequence { /** @return total number of items in the sequence. */ + /** + * Get size + * + * @return size + */ public abstract int size(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,8 +56,9 @@ * Index structure of lines/blocks in one file. *

* This structure can be used to compute an approximation of the similarity - * between two files. The index is used by {@link SimilarityRenameDetector} to - * compute scores between files. + * between two files. The index is used by + * {@link org.eclipse.jgit.diff.SimilarityRenameDetector} to compute scores + * between files. *

* To save space in memory, this index uses a space efficient encoding which * will not exceed 1 MiB per instance. The index starts out at a smaller size @@ -114,9 +115,9 @@ * @param obj * the object to hash * @return similarity index for this object - * @throws IOException + * @throws java.io.IOException * file contents cannot be read from the repository. - * @throws TableFullException + * @throws org.eclipse.jgit.diff.SimilarityIndex.TableFullException * object hashing overflowed the storage capacity of the * SimilarityIndex. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java 2019-09-03 12:37:49.000000000 +0000 @@ -136,7 +136,7 @@ 2 * srcs.size() * dsts.size()); int mNext = buildMatrix(pm); - out = new ArrayList(Math.min(mNext, dsts.size())); + out = new ArrayList<>(Math.min(mNext, dsts.size())); // Match rename pairs on a first come, first serve basis until // we have looked at everything that is above our minimum score. @@ -192,7 +192,7 @@ } private static List compactSrcList(List in) { - ArrayList r = new ArrayList(in.size()); + ArrayList r = new ArrayList<>(in.size()); for (DiffEntry e : in) { if (e.changeType == ChangeType.DELETE) r.add(e); @@ -201,7 +201,7 @@ } private static List compactDstList(List in) { - ArrayList r = new ArrayList(in.size()); + ArrayList r = new ArrayList<>(in.size()); for (DiffEntry e : in) { if (e != null) r.add(e); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,11 +44,13 @@ package org.eclipse.jgit.diff; /** - * Wrap another comparator for use with {@link Subsequence}. + * Wrap another comparator for use with + * {@link org.eclipse.jgit.diff.Subsequence}. *

* This comparator acts as a proxy for the real comparator, translating element * indexes on the fly by adding the subsequence's begin offset to them. - * Comparators of this type must be used with a {@link Subsequence}. + * Comparators of this type must be used with a + * {@link org.eclipse.jgit.diff.Subsequence}. * * @param * the base sequence type. @@ -67,11 +69,13 @@ this.cmp = cmp; } + /** {@inheritDoc} */ @Override public boolean equals(Subsequence a, int ai, Subsequence b, int bi) { return cmp.equals(a.base, ai + a.begin, b.base, bi + b.begin); } + /** {@inheritDoc} */ @Override public int hash(Subsequence seq, int ptr) { return cmp.hash(seq.base, ptr + seq.begin); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,11 +44,12 @@ package org.eclipse.jgit.diff; /** - * Wraps a {@link Sequence} to have a narrower range of elements. + * Wraps a {@link org.eclipse.jgit.diff.Sequence} to have a narrower range of + * elements. *

* This sequence acts as a proxy for the real sequence, translating element * indexes on the fly by adding {@code begin} to them. Sequences of this type - * must be used with a {@link SubsequenceComparator}. + * must be used with a {@link org.eclipse.jgit.diff.SubsequenceComparator}. * * @param * the base sequence type. @@ -57,8 +58,6 @@ /** * Construct a subsequence around the A region/base sequence. * - * @param - * the base sequence type. * @param a * the A sequence. * @param region @@ -66,14 +65,12 @@ * @return subsequence of {@code base} as described by A in {@code region}. */ public static Subsequence a(S a, Edit region) { - return new Subsequence(a, region.beginA, region.endA); + return new Subsequence<>(a, region.beginA, region.endA); } /** * Construct a subsequence around the B region/base sequence. * - * @param - * the base sequence type. * @param b * the B sequence. * @param region @@ -81,14 +78,12 @@ * @return subsequence of {@code base} as described by B in {@code region}. */ public static Subsequence b(S b, Edit region) { - return new Subsequence(b, region.beginB, region.endB); + return new Subsequence<>(b, region.beginB, region.endB); } /** * Adjust the Edit to reflect positions in the base sequence. * - * @param - * the base sequence type. * @param e * edit to adjust in-place. Prior to invocation the indexes are * in terms of the two subsequences; after invocation the indexes @@ -110,8 +105,6 @@ /** * Adjust the Edits to reflect positions in the base sequence. * - * @param - * the base sequence type. * @param edits * edits to adjust in-place. Prior to invocation the indexes are * in terms of the two subsequences; after invocation the indexes @@ -156,6 +149,7 @@ this.size = end - begin; } + /** {@inheritDoc} */ @Override public int size() { return size; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,13 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; +import static org.eclipse.jgit.util.Paths.compareSameName; + import java.io.IOException; +import org.eclipse.jgit.errors.DirCacheNameConflictException; + /** * Generic update/editing support for {@link DirCache}. *

@@ -86,6 +91,8 @@ } /** + * Get the {@code DirCache} + * * @return the cache we will update on {@link #finish()}. */ public DirCache getDirCache() { @@ -147,7 +154,8 @@ } /** - * Finish this builder and update the destination {@link DirCache}. + * Finish this builder and update the destination + * {@link org.eclipse.jgit.dircache.DirCache}. *

* When this method completes this builder instance is no longer usable by * the calling application. A new builder must be created to make additional @@ -168,6 +176,7 @@ * {@link #finish()}, and only after {@link #entries} is sorted. */ protected void replace() { + checkNameConflicts(); if (entryCnt < entries.length / 2) { final DirCacheEntry[] n = new DirCacheEntry[entryCnt]; System.arraycopy(entries, 0, n, 0, entryCnt); @@ -176,6 +185,76 @@ cache.replace(entries, entryCnt); } + private void checkNameConflicts() { + int end = entryCnt - 1; + for (int eIdx = 0; eIdx < end; eIdx++) { + DirCacheEntry e = entries[eIdx]; + if (e.getStage() != 0) { + continue; + } + + byte[] ePath = e.path; + int prefixLen = lastSlash(ePath) + 1; + + for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) { + DirCacheEntry n = entries[nIdx]; + if (n.getStage() != 0) { + continue; + } + + byte[] nPath = n.path; + if (!startsWith(ePath, nPath, prefixLen)) { + // Different prefix; this entry is in another directory. + break; + } + + int s = nextSlash(nPath, prefixLen); + int m = s < nPath.length ? TYPE_TREE : n.getRawMode(); + int cmp = compareSameName( + ePath, prefixLen, ePath.length, + nPath, prefixLen, s, m); + if (cmp < 0) { + break; + } else if (cmp == 0) { + throw new DirCacheNameConflictException( + e.getPathString(), + n.getPathString()); + } + } + } + } + + private static int lastSlash(byte[] path) { + for (int i = path.length - 1; i >= 0; i--) { + if (path[i] == '/') { + return i; + } + } + return -1; + } + + private static int nextSlash(byte[] b, int p) { + final int n = b.length; + for (; p < n; p++) { + if (b[p] == '/') { + return p; + } + } + return n; + } + + private static boolean startsWith(byte[] a, byte[] b, int n) { + if (b.length < n) { + return false; + } + for (n--; n >= 0; n--) { + if (a[n] != b[n]) { + return false; + } + } + return true; + } + /** * Finish, write, commit this change, and release the index lock. *

@@ -187,9 +266,9 @@ * @return true if the commit was successful and the file contains the new * data; false if the commit failed and the file remains with the * old data. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * the lock is not held. - * @throws IOException + * @throws java.io.IOException * the output file could not be created. The caller no longer * holds the lock. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,9 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.lib.FileMode.TYPE_MASK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + import java.io.IOException; import java.text.MessageFormat; import java.util.Arrays; @@ -51,12 +54,11 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; -import org.eclipse.jgit.treewalk.TreeWalk; /** - * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s. + * Updates a {@link org.eclipse.jgit.dircache.DirCache} by adding individual + * {@link org.eclipse.jgit.dircache.DirCacheEntry}s. *

* A builder always starts from a clean slate and appends in every single * DirCacheEntry which the final updated index must have to reflect @@ -97,13 +99,14 @@ * * @param newEntry * the new entry to add. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * If the FileMode of the entry was not set by the caller. */ public void add(final DirCacheEntry newEntry) { if (newEntry.getRawMode() == 0) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath - , newEntry.getPathString())); + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().fileModeNotSetForPath, + newEntry.getPathString())); beforeAdd(newEntry); fastAdd(newEntry); } @@ -159,34 +162,65 @@ * under pathPrefix. The ObjectId must be that of a * tree; the caller is responsible for dereferencing a tag or * commit (if necessary). - * @throws IOException + * @throws java.io.IOException * a tree cannot be read to iterate through its entries. */ - public void addTree(final byte[] pathPrefix, final int stage, - final ObjectReader reader, final AnyObjectId tree) throws IOException { - final TreeWalk tw = new TreeWalk(reader); - tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree - .toObjectId())); - tw.setRecursive(true); - if (tw.next()) { - final DirCacheEntry newEntry = toEntry(stage, tw); - beforeAdd(newEntry); - fastAdd(newEntry); - while (tw.next()) - fastAdd(toEntry(stage, tw)); + public void addTree(byte[] pathPrefix, int stage, ObjectReader reader, + AnyObjectId tree) throws IOException { + CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree); + while (!p.eof()) { + if (isTree(p)) { + p = enterTree(p, reader); + continue; + } + + DirCacheEntry first = toEntry(stage, p); + beforeAdd(first); + fastAdd(first); + p = p.next(); + break; + } + + // Rest of tree entries are correctly sorted; use fastAdd(). + while (!p.eof()) { + if (isTree(p)) { + p = enterTree(p, reader); + } else { + fastAdd(toEntry(stage, p)); + p = p.next(); + } } } - private DirCacheEntry toEntry(final int stage, final TreeWalk tw) { - final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage); - final AbstractTreeIterator i; + private static CanonicalTreeParser createTreeParser(byte[] pathPrefix, + ObjectReader reader, AnyObjectId tree) throws IOException { + return new CanonicalTreeParser(pathPrefix, reader, tree); + } + + private static boolean isTree(CanonicalTreeParser p) { + return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE; + } + + private static CanonicalTreeParser enterTree(CanonicalTreeParser p, + ObjectReader reader) throws IOException { + p = p.createSubtreeIterator(reader); + return p.eof() ? p.next() : p; + } + + private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) { + byte[] buf = i.getEntryPathBuffer(); + int len = i.getEntryPathLength(); + byte[] path = new byte[len]; + System.arraycopy(buf, 0, path, 0, len); - i = tw.getTree(0, AbstractTreeIterator.class); - e.setFileMode(tw.getFileMode(0)); + DirCacheEntry e = new DirCacheEntry(path, stage); + e.setFileMode(i.getEntryRawMode()); e.setObjectIdFromRaw(i.idBuffer(), i.idOffset()); return e; } + /** {@inheritDoc} */ + @Override public void finish() { if (!sorted) resort(); @@ -242,9 +276,9 @@ sorted = true; } - private static IllegalStateException bad(final DirCacheEntry a, - final String msg) { - return new IllegalStateException(msg + ": " + a.getStage() + " " //$NON-NLS-1$ //$NON-NLS-2$ - + a.getPathString()); + private static IllegalStateException bad(DirCacheEntry a, String msg) { + return new IllegalStateException(String.format( + "%s: %d %s", //$NON-NLS-1$ + msg, Integer.valueOf(a.getStage()), a.getPathString())); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,12 +54,14 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator; /** - * Iterate and update a {@link DirCache} as part of a TreeWalk. + * Iterate and update a {@link org.eclipse.jgit.dircache.DirCache} as part of a + * TreeWalk. *

- * Like {@link DirCacheIterator} this iterator allows a DirCache to be used in - * parallel with other sorts of iterators in a TreeWalk. However any entry which - * appears in the source DirCache and which is skipped by the TreeFilter is - * automatically copied into {@link DirCacheBuilder}, thus retaining it in the + * Like {@link org.eclipse.jgit.dircache.DirCacheIterator} this iterator allows + * a DirCache to be used in parallel with other sorts of iterators in a + * TreeWalk. However any entry which appears in the source DirCache and which is + * skipped by the TreeFilter is automatically copied into + * {@link org.eclipse.jgit.dircache.DirCacheBuilder}, thus retaining it in the * newly updated index. *

* This iterator is suitable for update processes, or even a simple delete @@ -105,6 +107,7 @@ builder = p.builder; } + /** {@inheritDoc} */ @Override public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { @@ -114,6 +117,7 @@ return new DirCacheBuildIterator(this, currentSubtree); } + /** {@inheritDoc} */ @Override public void skip() throws CorruptObjectException { if (currentSubtree != null) @@ -123,6 +127,7 @@ next(1); } + /** {@inheritDoc} */ @Override public void stopWalk() { final int cur = ptr; @@ -130,4 +135,10 @@ if (cur < cnt) builder.keep(cur, cnt - cur); } + + /** {@inheritDoc} */ + @Override + protected boolean needsStopWalk() { + return ptr < cache.getEntryCount(); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,29 +42,43 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.api.errors.FilterFailedException; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IndexWriteException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; @@ -76,22 +90,56 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IntList; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; -import org.eclipse.jgit.util.io.AutoCRLFOutputStream; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class handles checking out one or two trees merging with the index. */ public class DirCacheCheckout { + private static Logger LOG = LoggerFactory.getLogger(DirCacheCheckout.class); + + private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024; + + /** + * Metadata used in checkout process + * + * @since 4.3 + */ + public static class CheckoutMetadata { + /** git attributes */ + public final EolStreamType eolStreamType; + + /** filter command to apply */ + public final String smudgeFilterCommand; + + /** + * @param eolStreamType + * @param smudgeFilterCommand + */ + public CheckoutMetadata(EolStreamType eolStreamType, + String smudgeFilterCommand) { + this.eolStreamType = eolStreamType; + this.smudgeFilterCommand = smudgeFilterCommand; + } + + static CheckoutMetadata EMPTY = new CheckoutMetadata( + EolStreamType.DIRECT, null); + } + private Repository repo; - private HashMap updated = new HashMap(); + private HashMap updated = new HashMap<>(); - private ArrayList conflicts = new ArrayList(); + private ArrayList conflicts = new ArrayList<>(); - private ArrayList removed = new ArrayList(); + private ArrayList removed = new ArrayList<>(); private ObjectId mergeCommitTree; @@ -107,18 +155,26 @@ private boolean failOnConflict = true; - private ArrayList toBeDeleted = new ArrayList(); + private ArrayList toBeDeleted = new ArrayList<>(); private boolean emptyDirCache; + private boolean performingCheckout; + + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** - * @return a list of updated paths and objectIds + * Get list of updated paths and smudgeFilterCommands + * + * @return a list of updated paths and smudgeFilterCommands */ - public Map getUpdated() { + public Map getUpdated() { return updated; } /** + * Get a list of conflicts created by this checkout + * * @return a list of conflicts created by this checkout */ public List getConflicts() { @@ -126,19 +182,24 @@ } /** + * Get list of paths of files which couldn't be deleted during last call to + * {@link #checkout()} + * * @return a list of paths (relative to the start of the working tree) of * files which couldn't be deleted during last call to * {@link #checkout()} . {@link #checkout()} detected that these * files should be deleted but the deletion in the filesystem failed * (e.g. because a file was locked). To have a consistent state of * the working tree these files have to be deleted by the callers of - * {@link DirCacheCheckout}. + * {@link org.eclipse.jgit.dircache.DirCacheCheckout}. */ public List getToBeDeleted() { return toBeDeleted; } /** + * Get list of all files removed by this checkout + * * @return a list of all files removed by this checkout */ public List getRemoved() { @@ -159,7 +220,7 @@ * the id of the tree we want to fast-forward to * @param workingTree * an iterator over the repositories Working Tree - * @throws IOException + * @throws java.io.IOException */ public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc, ObjectId mergeCommitTree, WorkingTreeIterator workingTree) @@ -175,7 +236,8 @@ /** * Constructs a DirCacheCeckout for merging and checking out two trees (HEAD * and mergeCommitTree) and the index. As iterator over the working tree - * this constructor creates a standard {@link FileTreeIterator} + * this constructor creates a standard + * {@link org.eclipse.jgit.treewalk.FileTreeIterator} * * @param repo * the repository in which we do the checkout @@ -185,7 +247,7 @@ * the (already locked) Dircache for this repo * @param mergeCommitTree * the id of the tree we want to fast-forward to - * @throws IOException + * @throws java.io.IOException */ public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc, ObjectId mergeCommitTree) throws IOException { @@ -204,7 +266,7 @@ * the id of the tree we want to fast-forward to * @param workingTree * an iterator over the repositories Working Tree - * @throws IOException + * @throws java.io.IOException */ public DirCacheCheckout(Repository repo, DirCache dc, ObjectId mergeCommitTree, WorkingTreeIterator workingTree) @@ -215,7 +277,7 @@ /** * Constructs a DirCacheCeckout for checking out one tree, merging with the * index. As iterator over the working tree this constructor creates a - * standard {@link FileTreeIterator} + * standard {@link org.eclipse.jgit.treewalk.FileTreeIterator} * * @param repo * the repository in which we do the checkout @@ -223,7 +285,7 @@ * the (already locked) Dircache for this repo * @param mergeCommitTree * the id of the tree of the - * @throws IOException + * @throws java.io.IOException */ public DirCacheCheckout(Repository repo, DirCache dc, ObjectId mergeCommitTree) throws IOException { @@ -231,11 +293,23 @@ } /** + * Set a progress monitor which can be passed to built-in filter commands, + * providing progress information for long running tasks. + * + * @param monitor + * the {@link ProgressMonitor} + * @since 4.11 + */ + public void setProgressMonitor(ProgressMonitor monitor) { + this.monitor = monitor != null ? monitor : NullProgressMonitor.INSTANCE; + } + + /** * Scan head, index and merge tree. Used during normal checkout or merge * operations. * - * @throws CorruptObjectException - * @throws IOException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @throws java.io.IOException */ public void preScanTwoTrees() throws CorruptObjectException, IOException { removed.clear(); @@ -246,8 +320,9 @@ addTree(walk, headCommitTree); addTree(walk, mergeCommitTree); - walk.addTree(new DirCacheBuildIterator(builder)); + int dciPos = walk.addTree(new DirCacheBuildIterator(builder)); walk.addTree(workingTree); + workingTree.setDirCacheIterator(walk, dciPos); while (walk.next()) { processEntry(walk.getTree(0, CanonicalTreeParser.class), @@ -270,10 +345,10 @@ * Scan index and merge tree (no HEAD). Used e.g. for initial checkout when * there is no head yet. * - * @throws MissingObjectException - * @throws IncorrectObjectTypeException - * @throws CorruptObjectException - * @throws IOException + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @throws java.io.IOException */ public void prescanOneTree() throws MissingObjectException, IncorrectObjectTypeException, @@ -286,8 +361,9 @@ walk = new NameConflictTreeWalk(repo); addTree(walk, mergeCommitTree); - walk.addTree(new DirCacheBuildIterator(builder)); + int dciPos = walk.addTree(new DirCacheBuildIterator(builder)); walk.addTree(workingTree); + workingTree.setDirCacheIterator(walk, dciPos); while (walk.next()) { processEntry(walk.getTree(0, CanonicalTreeParser.class), @@ -318,8 +394,16 @@ // The index entry is missing if (f != null && !FileMode.TREE.equals(f.getEntryFileMode()) && !f.isEntryIgnored()) { - // don't overwrite an untracked and not ignored file - conflicts.add(walk.getPathString()); + if (failOnConflict) { + // don't overwrite an untracked and not ignored file + conflicts.add(walk.getPathString()); + } else { + // failOnConflict is false. Putting something to conflicts + // would mean we delete it. Instead we want the mergeCommit + // content to be checked out. + update(m.getEntryPathString(), m.getEntryObjectId(), + m.getEntryFileMode()); + } } else update(m.getEntryPathString(), m.getEntryObjectId(), m.getEntryFileMode()); @@ -354,6 +438,9 @@ if (f != null) { // There is a file/folder for that path in the working tree if (walk.isDirectoryFileConflict()) { + // We put it in conflicts. Even if failOnConflict is false + // this would cause the path to be deleted. Thats exactly what + // we want in this situation conflicts.add(walk.getPathString()); } else { // No file/folder conflict exists. All entries are files or @@ -379,29 +466,44 @@ } /** - * Execute this checkout + * Execute this checkout. A + * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} is fired if the + * working tree was modified; even if the checkout fails. * * @return false if this method could not delete all the files - * which should be deleted (e.g. because of of the files was + * which should be deleted (e.g. because one of the files was * locked). In this case {@link #getToBeDeleted()} lists the files * which should be tried to be deleted outside of this method. * Although false is returned the checkout was * successful and the working tree was updated for all other files. * true is returned when no such problem occurred - * - * @throws IOException + * @throws java.io.IOException */ public boolean checkout() throws IOException { try { return doCheckout(); + } catch (CanceledException ce) { + // should actually be propagated, but this would change a LOT of + // APIs + throw new IOException(ce); } finally { - dc.unlock(); + try { + dc.unlock(); + } finally { + if (performingCheckout) { + WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent( + getUpdated().keySet(), getRemoved()); + if (!event.isEmpty()) { + repo.fireEvent(event); + } + } + } } } private boolean doCheckout() throws CorruptObjectException, IOException, MissingObjectException, IncorrectObjectTypeException, - CheckoutConflictException, IndexWriteException { + CheckoutConflictException, IndexWriteException, CanceledException { toBeDeleted.clear(); try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) { if (headCommitTree != null) @@ -419,11 +521,17 @@ // update our index builder.finish(); + // init progress reporting + int numTotal = removed.size() + updated.size(); + monitor.beginTask(JGitText.get().checkingOutFiles, numTotal); + + performingCheckout = true; File file = null; String last = null; // when deleting files process them in the opposite order as they have // been reported. This ensures the files are deleted before we delete // their parent folders + IntList nonDeleted = new IntList(); for (int i = removed.size() - 1; i >= 0; i--) { String r = removed.get(i); file = new File(repo.getWorkTree(), r); @@ -433,22 +541,63 @@ // a submodule, in which case we shall not attempt // to delete it. A submodule is not empty, so it // is safe to check this after a failed delete. - if (!repo.getFS().isDirectory(file)) + if (!repo.getFS().isDirectory(file)) { + nonDeleted.add(i); toBeDeleted.add(r); + } } else { if (last != null && !isSamePrefix(r, last)) removeEmptyParents(new File(repo.getWorkTree(), last)); last = r; } + monitor.update(1); + if (monitor.isCancelled()) { + throw new CanceledException(MessageFormat.format( + JGitText.get().operationCanceled, + JGitText.get().checkingOutFiles)); + } } - if (file != null) + if (file != null) { removeEmptyParents(file); + } + removed = filterOut(removed, nonDeleted); + nonDeleted = null; + Iterator> toUpdate = updated + .entrySet().iterator(); + Map.Entry e = null; + try { + while (toUpdate.hasNext()) { + e = toUpdate.next(); + String path = e.getKey(); + CheckoutMetadata meta = e.getValue(); + DirCacheEntry entry = dc.getEntry(path); + if (FileMode.GITLINK.equals(entry.getRawMode())) { + checkoutGitlink(path, entry); + } else { + checkoutEntry(repo, entry, objectReader, false, meta); + } + e = null; - for (String path : updated.keySet()) { - DirCacheEntry entry = dc.getEntry(path); - if (!FileMode.GITLINK.equals(entry.getRawMode())) - checkoutEntry(repo, entry, objectReader); + monitor.update(1); + if (monitor.isCancelled()) { + throw new CanceledException(MessageFormat.format( + JGitText.get().operationCanceled, + JGitText.get().checkingOutFiles)); + } + } + } catch (Exception ex) { + // We didn't actually modify the current entry nor any that + // might follow. + if (e != null) { + toUpdate.remove(); + } + while (toUpdate.hasNext()) { + e = toUpdate.next(); + toUpdate.remove(); + } + throw ex; } + monitor.endTask(); // commit the index builder - a new index is persisted if (!builder.commit()) @@ -457,6 +606,44 @@ return toBeDeleted.size() == 0; } + private void checkoutGitlink(String path, DirCacheEntry entry) + throws IOException { + File gitlinkDir = new File(repo.getWorkTree(), path); + FileUtils.mkdirs(gitlinkDir, true); + FS fs = repo.getFS(); + entry.setLastModified(fs.lastModified(gitlinkDir)); + } + + private static ArrayList filterOut(ArrayList strings, + IntList indicesToRemove) { + int n = indicesToRemove.size(); + if (n == strings.size()) { + return new ArrayList<>(0); + } + switch (n) { + case 0: + return strings; + case 1: + strings.remove(indicesToRemove.get(0)); + return strings; + default: + int length = strings.size(); + ArrayList result = new ArrayList<>(length - n); + // Process indicesToRemove from the back; we know that it + // contains indices in descending order. + int j = n - 1; + int idx = indicesToRemove.get(j); + for (int i = 0; i < length; i++) { + if (i == idx) { + idx = (--j >= 0) ? indicesToRemove.get(j) : -1; + } else { + result.add(strings.get(i)); + } + } + return result; + } + } + private static boolean isSamePrefix(String a, String b) { int as = a.lastIndexOf('/'); int bs = b.lastIndexOf('/'); @@ -669,10 +856,23 @@ return; } - // if we have no file at all then there is nothing to do - if ((ffMask & 0x222) == 0 - && (f == null || FileMode.TREE.equals(f.getEntryFileMode()))) - return; + if ((ffMask & 0x222) == 0) { + // HEAD, MERGE and index don't contain a file (e.g. all contain a + // folder) + if (f == null || FileMode.TREE.equals(f.getEntryFileMode())) { + // the workingtree entry doesn't exist or also contains a folder + // -> no problem + return; + } else { + // the workingtree entry exists and is not a folder + if (!idEqual(h, m)) { + // Because HEAD and MERGE differ we will try to update the + // workingtree with a folder -> return a conflict + conflict(name, null, null, null); + } + return; + } + } if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) { // File/Directory conflict case #20 @@ -955,6 +1155,17 @@ } } + private static boolean idEqual(AbstractTreeIterator a, + AbstractTreeIterator b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return a.getEntryObjectId().equals(b.getEntryObjectId()); + } + /** * A conflict is detected - add the three different stages to the index * @param path the path of the conflicting entry @@ -996,9 +1207,13 @@ removed.add(path); } - private void update(String path, ObjectId mId, FileMode mode) { + private void update(String path, ObjectId mId, FileMode mode) + throws IOException { if (!FileMode.TREE.equals(mode)) { - updated.put(path, mId); + updated.put(path, new CheckoutMetadata( + walk.getEolStreamType(CHECKOUT_OP), + walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE))); + DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0); entry.setObjectId(mId); entry.setFileMode(mode); @@ -1008,10 +1223,12 @@ /** * If true, will scan first to see if it's possible to check - * out, otherwise throw {@link CheckoutConflictException}. If + * out, otherwise throw + * {@link org.eclipse.jgit.errors.CheckoutConflictException}. If * false, it will silently deal with the problem. * * @param failOnConflict + * a boolean. */ public void setFailOnConflict(boolean failOnConflict) { this.failOnConflict = failOnConflict; @@ -1054,8 +1271,10 @@ private boolean isModifiedSubtree_IndexWorkingtree(String path) throws CorruptObjectException, IOException { try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) { - tw.addTree(new DirCacheIterator(dc)); - tw.addTree(new FileTreeIterator(repo)); + int dciPos = tw.addTree(new DirCacheIterator(dc)); + FileTreeIterator fti = new FileTreeIterator(repo); + tw.addTree(fti); + fti.setDirCacheIterator(tw, dciPos); tw.setRecursive(true); tw.setFilter(PathFilter.create(path)); DirCacheIterator dcIt; @@ -1127,6 +1346,13 @@ * final filename. * *

+ * Note: if the entry path on local file system exists as a non-empty + * directory, and the target entry type is a link or file, the checkout will + * fail with {@link java.io.IOException} since existing non-empty directory + * cannot be renamed to file or link without deleting it recursively. + *

+ * + *

* TODO: this method works directly on File IO, we may need another * abstraction (like WorkingTreeIterator). This way we could tell e.g. * Eclipse that Files in the workspace got changed @@ -1138,11 +1364,56 @@ * the entry containing new mode and content * @param or * object reader to use for checkout - * @throws IOException + * @throws java.io.IOException * @since 3.6 */ public static void checkoutEntry(Repository repo, DirCacheEntry entry, ObjectReader or) throws IOException { + checkoutEntry(repo, entry, or, false, null); + } + + /** + * Updates the file in the working tree with content and mode from an entry + * in the index. The new content is first written to a new temporary file in + * the same directory as the real file. Then that new file is renamed to the + * final filename. + * + *

+ * Note: if the entry path on local file system exists as a file, it + * will be deleted and if it exists as a directory, it will be deleted + * recursively, independently if has any content. + *

+ * + *

+ * TODO: this method works directly on File IO, we may need another + * abstraction (like WorkingTreeIterator). This way we could tell e.g. + * Eclipse that Files in the workspace got changed + *

+ * + * @param repo + * repository managing the destination work tree. + * @param entry + * the entry containing new mode and content + * @param or + * object reader to use for checkout + * @param deleteRecursive + * true to recursively delete final path if it exists on the file + * system + * @param checkoutMetadata + * containing + *
    + *
  • smudgeFilterCommand to be run for smudging the entry to be + * checked out
  • + *
  • eolStreamType used for stream conversion
  • + *
+ * @throws java.io.IOException + * @since 4.2 + */ + public static void checkoutEntry(Repository repo, DirCacheEntry entry, + ObjectReader or, boolean deleteRecursive, + CheckoutMetadata checkoutMetadata) throws IOException { + if (checkoutMetadata == null) + checkoutMetadata = CheckoutMetadata.EMPTY; ObjectLoader ol = or.open(entry.getObjectId()); File f = new File(repo.getWorkTree(), entry.getPathString()); File parentDir = f.getParentFile(); @@ -1153,25 +1424,55 @@ && opt.getSymLinks() == SymLinks.TRUE) { byte[] bytes = ol.getBytes(); String target = RawParseUtils.decode(bytes); + if (deleteRecursive && f.isDirectory()) { + FileUtils.delete(f, FileUtils.RECURSIVE); + } fs.createSymLink(f, target); entry.setLength(bytes.length); entry.setLastModified(fs.lastModified(f)); return; } + String name = f.getName(); + if (name.length() > 200) { + name = name.substring(0, 200); + } File tmpFile = File.createTempFile( - "._" + f.getName(), null, parentDir); //$NON-NLS-1$ - OutputStream channel = new FileOutputStream(tmpFile); - if (opt.getAutoCRLF() == AutoCRLF.TRUE) - channel = new AutoCRLFOutputStream(channel); - try { - ol.copyTo(channel); - } finally { - channel.close(); + "._" + name, null, parentDir); //$NON-NLS-1$ + + EolStreamType nonNullEolStreamType; + if (checkoutMetadata.eolStreamType != null) { + nonNullEolStreamType = checkoutMetadata.eolStreamType; + } else if (opt.getAutoCRLF() == AutoCRLF.TRUE) { + nonNullEolStreamType = EolStreamType.AUTO_CRLF; + } else { + nonNullEolStreamType = EolStreamType.DIRECT; + } + try (OutputStream channel = EolStreamTypeUtil.wrapOutputStream( + new FileOutputStream(tmpFile), nonNullEolStreamType)) { + if (checkoutMetadata.smudgeFilterCommand != null) { + if (FilterCommandRegistry + .isRegistered(checkoutMetadata.smudgeFilterCommand)) { + runBuiltinFilterCommand(repo, checkoutMetadata, ol, + channel); + } else { + runExternalFilterCommand(repo, entry, checkoutMetadata, ol, + fs, channel); + } + } else { + ol.copyTo(channel); + } + } + // The entry needs to correspond to the on-disk filesize. If the content + // was filtered (either by autocrlf handling or smudge filters) ask the + // filesystem again for the length. Otherwise the objectloader knows the + // size + if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT + && checkoutMetadata.smudgeFilterCommand == null) { + entry.setLength(ol.getSize()); + } else { + entry.setLength(tmpFile.length()); } - entry.setLength(opt.getAutoCRLF() == AutoCRLF.TRUE ? // - tmpFile.length() // AutoCRLF wants on-disk-size - : (int) ol.getSize()); if (opt.isFileMode() && fs.supportsExecute()) { if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) { @@ -1183,13 +1484,87 @@ } } try { - FileUtils.rename(tmpFile, f); + if (deleteRecursive && f.isDirectory()) { + FileUtils.delete(f, FileUtils.RECURSIVE); + } + FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, tmpFile.getPath(), - f.getPath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + tmpFile.getPath(), f.getPath()), + e); + } finally { + if (tmpFile.exists()) { + FileUtils.delete(tmpFile); + } + } + entry.setLastModified(fs.lastModified(f)); + } + + // Run an external filter command + private static void runExternalFilterCommand(Repository repo, + DirCacheEntry entry, + CheckoutMetadata checkoutMetadata, ObjectLoader ol, FS fs, + OutputStream channel) throws IOException { + ProcessBuilder filterProcessBuilder = fs.runInShell( + checkoutMetadata.smudgeFilterCommand, new String[0]); + filterProcessBuilder.directory(repo.getWorkTree()); + filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, + repo.getDirectory().getAbsolutePath()); + ExecutionResult result; + int rc; + try { + // TODO: wire correctly with AUTOCRLF + result = fs.execute(filterProcessBuilder, ol.openStream()); + rc = result.getRc(); + if (rc == 0) { + result.getStdout().writeTo(channel, + NullProgressMonitor.INSTANCE); + } + } catch (IOException | InterruptedException e) { + throw new IOException(new FilterFailedException(e, + checkoutMetadata.smudgeFilterCommand, + entry.getPathString())); + } + if (rc != 0) { + throw new IOException(new FilterFailedException(rc, + checkoutMetadata.smudgeFilterCommand, + entry.getPathString(), + result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), + RawParseUtils.decode(result.getStderr() + .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + } + } + + // Run a builtin filter command + private static void runBuiltinFilterCommand(Repository repo, + CheckoutMetadata checkoutMetadata, ObjectLoader ol, + OutputStream channel) throws MissingObjectException, IOException { + boolean isMandatory = repo.getConfig().getBoolean( + ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_REQUIRED, false); + FilterCommand command = null; + try { + command = FilterCommandRegistry.createFilterCommand( + checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(), + channel); + } catch (IOException e) { + LOG.error(JGitText.get().failedToDetermineFilterDefinition, e); + if (!isMandatory) { + // In case an IOException occurred during creating of the + // command then proceed as if there would not have been a + // builtin filter (only if the filter is not mandatory). + ol.copyTo(channel); + } else { + throw e; + } + } + if (command != null) { + while (command.run() != -1) { + // loop as long as command.run() tells there is work to do + } } - entry.setLastModified(f.lastModified()); } @SuppressWarnings("deprecation") @@ -1202,24 +1577,6 @@ checkValidPathSegment(chk, i); } - /** - * Check if path is a valid path for a checked out file name or ref name. - * - * @param path - * @throws InvalidPathException - * if the path is invalid - * @since 3.3 - */ - static void checkValidPath(String path) throws InvalidPathException { - try { - SystemReader.getInstance().checkPath(path); - } catch (CorruptObjectException e) { - InvalidPathException p = new InvalidPathException(path); - p.initCause(e); - throw p; - } - } - private static void checkValidPathSegment(ObjectChecker chk, CanonicalTreeParser t) throws InvalidPathException { try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,10 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.dircache.DirCache.cmp; +import static org.eclipse.jgit.dircache.DirCacheTree.peq; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -53,30 +57,35 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.Paths; /** - * Updates a {@link DirCache} by supplying discrete edit commands. + * Updates a {@link org.eclipse.jgit.dircache.DirCache} by supplying discrete + * edit commands. *

- * An editor updates a DirCache by taking a list of {@link PathEdit} commands - * and executing them against the entries of the destination cache to produce a - * new cache. This edit style allows applications to insert a few commands and - * then have the editor compute the proper entry indexes necessary to perform an + * An editor updates a DirCache by taking a list of + * {@link org.eclipse.jgit.dircache.DirCacheEditor.PathEdit} commands and + * executing them against the entries of the destination cache to produce a new + * cache. This edit style allows applications to insert a few commands and then + * have the editor compute the proper entry indexes necessary to perform an * efficient in-order update of the index records. This can be easier to use - * than {@link DirCacheBuilder}. + * than {@link org.eclipse.jgit.dircache.DirCacheBuilder}. *

* * @see DirCacheBuilder */ public class DirCacheEditor extends BaseDirCacheEditor { private static final Comparator EDIT_CMP = new Comparator() { + @Override public int compare(final PathEdit o1, final PathEdit o2) { final byte[] a = o1.path; final byte[] b = o2.path; - return DirCache.cmp(a, a.length, b, b.length); + return cmp(a, a.length, b, b.length); } }; private final List edits; + private int editIdx; /** * Construct a new editor. @@ -89,7 +98,7 @@ */ protected DirCacheEditor(final DirCache dc, final int ecnt) { super(dc, ecnt); - edits = new ArrayList(); + edits = new ArrayList<>(); } /** @@ -106,6 +115,7 @@ edits.add(edit); } + /** {@inheritDoc} */ @Override public boolean commit() throws IOException { if (edits.isEmpty()) { @@ -117,6 +127,8 @@ return super.commit(); } + /** {@inheritDoc} */ + @Override public void finish() { if (!edits.isEmpty()) { applyEdits(); @@ -126,35 +138,44 @@ private void applyEdits() { Collections.sort(edits, EDIT_CMP); + editIdx = 0; final int maxIdx = cache.getEntryCount(); int lastIdx = 0; - for (final PathEdit e : edits) { - int eIdx = cache.findEntry(e.path, e.path.length); + while (editIdx < edits.size()) { + PathEdit e = edits.get(editIdx++); + int eIdx = cache.findEntry(lastIdx, e.path, e.path.length); final boolean missing = eIdx < 0; if (eIdx < 0) eIdx = -(eIdx + 1); final int cnt = Math.min(eIdx, maxIdx) - lastIdx; if (cnt > 0) fastKeep(lastIdx, cnt); - lastIdx = missing ? eIdx : cache.nextEntry(eIdx); - if (e instanceof DeletePath) + if (e instanceof DeletePath) { + lastIdx = missing ? eIdx : cache.nextEntry(eIdx); continue; + } if (e instanceof DeleteTree) { lastIdx = cache.nextEntry(e.path, e.path.length, eIdx); continue; } if (missing) { - final DirCacheEntry ent = new DirCacheEntry(e.path); + DirCacheEntry ent = new DirCacheEntry(e.path); e.apply(ent); - if (ent.getRawMode() == 0) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath - , ent.getPathString())); + if (ent.getRawMode() == 0) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().fileModeNotSetForPath, + ent.getPathString())); + } + lastIdx = e.replace + ? deleteOverlappingSubtree(ent, eIdx) + : eIdx; fastAdd(ent); } else { // Apply to all entries of the current path (different stages) + lastIdx = cache.nextEntry(eIdx); for (int i = eIdx; i < lastIdx; i++) { final DirCacheEntry ent = cache.getEntry(i); e.apply(ent); @@ -168,6 +189,102 @@ fastKeep(lastIdx, cnt); } + private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) { + byte[] entPath = ent.path; + int entLen = entPath.length; + + // Delete any file that was previously processed and overlaps + // the parent directory for the new entry. Since the editor + // always processes entries in path order, binary search back + // for the overlap for each parent directory. + for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) { + int i = findEntry(entPath, p); + if (i >= 0) { + // A file does overlap, delete the file from the array. + // No other parents can have overlaps as the file should + // have taken care of that itself. + int n = --entryCnt - i; + System.arraycopy(entries, i + 1, entries, i, n); + break; + } + + // If at least one other entry already exists in this parent + // directory there is no need to continue searching up the tree. + i = -(i + 1); + if (i < entryCnt && inDir(entries[i], entPath, p)) { + break; + } + } + + int maxEnt = cache.getEntryCount(); + if (eIdx >= maxEnt) { + return maxEnt; + } + + DirCacheEntry next = cache.getEntry(eIdx); + if (Paths.compare(next.path, 0, next.path.length, 0, + entPath, 0, entLen, TYPE_TREE) < 0) { + // Next DirCacheEntry sorts before new entry as tree. Defer a + // DeleteTree command to delete any entries if they exist. This + // case only happens for A, A.c, A/c type of conflicts (rare). + insertEdit(new DeleteTree(entPath)); + return eIdx; + } + + // Next entry may be contained by the entry-as-tree, skip if so. + while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) { + eIdx++; + } + return eIdx; + } + + private int findEntry(byte[] p, int pLen) { + int low = 0; + int high = entryCnt; + while (low < high) { + int mid = (low + high) >>> 1; + int cmp = cmp(p, pLen, entries[mid]); + if (cmp < 0) { + high = mid; + } else if (cmp == 0) { + while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) { + mid--; + } + return mid; + } else { + low = mid + 1; + } + } + return -(low + 1); + } + + private void insertEdit(DeleteTree d) { + for (int i = editIdx; i < edits.size(); i++) { + int cmp = EDIT_CMP.compare(d, edits.get(i)); + if (cmp < 0) { + edits.add(i, d); + return; + } else if (cmp == 0) { + return; + } + } + edits.add(d); + } + + private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) { + return e.path.length > pLen && e.path[pLen] == '/' + && peq(path, e.path, pLen); + } + + private static int pdir(byte[] path, int e) { + for (e--; e > 0; e--) { + if (path[e] == '/') { + return e; + } + } + return 0; + } + /** * Any index record update. *

@@ -179,6 +296,7 @@ */ public abstract static class PathEdit { final byte[] path; + boolean replace = true; /** * Create a new update command by path name. @@ -190,6 +308,10 @@ path = Constants.encode(entryPath); } + PathEdit(byte[] path) { + this.path = path; + } + /** * Create a new update command for an existing entry instance. * @@ -202,6 +324,22 @@ } /** + * Configure if a file can replace a directory (or vice versa). + *

+ * Default is {@code true} as this is usually the desired behavior. + * + * @param ok + * if true a file can replace a directory, or a directory can + * replace a file. + * @return {@code this} + * @since 4.2 + */ + public PathEdit setReplace(boolean ok) { + replace = ok; + return this; + } + + /** * Apply the update to a single cache entry matching the path. *

* After apply is invoked the entry is added to the output table, and @@ -212,6 +350,12 @@ * the path is a new path in the index. */ public abstract void apply(DirCacheEntry ent); + + @Override + public String toString() { + String p = DirCacheEntry.toString(path); + return getClass().getSimpleName() + '[' + p + ']'; + } } /** @@ -245,6 +389,7 @@ super(ent); } + @Override public void apply(final DirCacheEntry ent) { throw new UnsupportedOperationException(JGitText.get().noApplyInDelete); } @@ -272,12 +417,29 @@ * only the subtree's contents are matched by the command. * The special case "" (not "/"!) deletes all entries. */ - public DeleteTree(final String entryPath) { - super( - (entryPath.endsWith("/") || entryPath.length() == 0) ? entryPath //$NON-NLS-1$ - : entryPath + "/"); //$NON-NLS-1$ + public DeleteTree(String entryPath) { + super(entryPath.isEmpty() + || entryPath.charAt(entryPath.length() - 1) == '/' + ? entryPath + : entryPath + '/'); + } + + DeleteTree(byte[] path) { + super(appendSlash(path)); + } + + private static byte[] appendSlash(byte[] path) { + int n = path.length; + if (n > 0 && path[n - 1] != '/') { + byte[] r = new byte[n + 1]; + System.arraycopy(path, 0, r, 0, n); + r[n] = '/'; + return r; + } + return path; } + @Override public void apply(final DirCacheEntry ent) { throw new UnsupportedOperationException(JGitText.get().noApplyInDelete); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,9 +65,11 @@ import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.SystemReader; /** - * A single file (or stage of a file) in a {@link DirCache}. + * A single file (or stage of a file) in a + * {@link org.eclipse.jgit.dircache.DirCache}. *

* An entry represents exactly one stage of a file. If a file path is unmerged * then multiple DirCacheEntry instances may appear for the same path name. @@ -191,7 +193,7 @@ } try { - DirCacheCheckout.checkValidPath(toString(path)); + checkPath(path); } catch (InvalidPathException e) { CorruptObjectException p = new CorruptObjectException(e.getMessage()); @@ -220,7 +222,7 @@ * * @param newPath * name of the cache entry. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * If the path starts or ends with "/", or contains "//" either * "\0". These sequences are not permitted in a git tree object * or DirCache file. @@ -236,7 +238,7 @@ * name of the cache entry. * @param stage * the stage index of the new entry. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * If the path starts or ends with "/", or contains "//" either * "\0". These sequences are not permitted in a git tree object * or DirCache file. Or if {@code stage} is outside of the @@ -251,7 +253,7 @@ * * @param newPath * name of the cache entry, in the standard encoding. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * If the path starts or ends with "/", or contains "//" either * "\0". These sequences are not permitted in a git tree object * or DirCache file. @@ -263,27 +265,27 @@ /** * Create an empty entry at the specified stage. * - * @param newPath + * @param path * name of the cache entry, in the standard encoding. * @param stage * the stage index of the new entry. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * If the path starts or ends with "/", or contains "//" either * "\0". These sequences are not permitted in a git tree object * or DirCache file. Or if {@code stage} is outside of the * range 0..3, inclusive. */ @SuppressWarnings("boxing") - public DirCacheEntry(final byte[] newPath, final int stage) { - DirCacheCheckout.checkValidPath(toString(newPath)); + public DirCacheEntry(byte[] path, final int stage) { + checkPath(path); if (stage < 0 || 3 < stage) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidStageForPath, - stage, toString(newPath))); + stage, toString(path))); info = new byte[INFO_LEN]; infoOffset = 0; - path = newPath; + this.path = path; int flags = ((stage & 0x3) << 12); if (path.length < NAME_MASK) @@ -293,6 +295,23 @@ NB.encodeInt16(info, infoOffset + P_FLAGS, flags); } + /** + * Duplicate DirCacheEntry with same path and copied info. + *

+ * The same path buffer is reused (avoiding copying), however a new info + * buffer is created and its contents are copied. + * + * @param src + * entry to clone. + * @since 4.2 + */ + public DirCacheEntry(DirCacheEntry src) { + path = src.path; + info = new byte[INFO_LEN]; + infoOffset = 0; + System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN); + } + void write(final OutputStream os) throws IOException { final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; final int pathLen = path.length; @@ -360,8 +379,9 @@ /** * Check whether this entry has been smudged or not *

- * If a blob has length 0 we know his id see {@link Constants#EMPTY_BLOB_ID}. If an entry - * has length 0 and an ID different from the one for empty blob we know this + * If a blob has length 0 we know its id, see + * {@link org.eclipse.jgit.lib.Constants#EMPTY_BLOB_ID}. If an entry has + * length 0 and an ID different from the one for empty blob we know this * entry was smudged. * * @return true if the entry is smudged, false @@ -408,7 +428,9 @@ } /** - * @return true if this entry should be checked for changes + * Whether this entry should be checked for changes + * + * @return {@code true} if this entry should be checked for changes */ public boolean isUpdateNeeded() { return (inCoreFlags & UPDATE_NEEDED) != 0; @@ -418,6 +440,7 @@ * Set whether this entry must be checked for changes * * @param updateNeeded + * whether this entry must be checked for changes */ public void setUpdateNeeded(boolean updateNeeded) { if (updateNeeded) @@ -466,7 +489,7 @@ } /** - * Obtain the raw {@link FileMode} bits for this entry. + * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for this entry. * * @return mode bits for the entry. * @see FileMode#fromBits(int) @@ -476,7 +499,7 @@ } /** - * Obtain the {@link FileMode} for this entry. + * Obtain the {@link org.eclipse.jgit.lib.FileMode} for this entry. * * @return the file mode singleton for this entry. */ @@ -489,21 +512,26 @@ * * @param mode * the new mode constant. - * @throws IllegalArgumentException - * If {@code mode} is {@link FileMode#MISSING}, - * {@link FileMode#TREE}, or any other type code not permitted - * in a tree object. + * @throws java.lang.IllegalArgumentException + * If {@code mode} is + * {@link org.eclipse.jgit.lib.FileMode#MISSING}, + * {@link org.eclipse.jgit.lib.FileMode#TREE}, or any other type + * code not permitted in a tree object. */ public void setFileMode(final FileMode mode) { switch (mode.getBits() & FileMode.TYPE_MASK) { case FileMode.TYPE_MISSING: case FileMode.TYPE_TREE: - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidModeForPath - , mode, getPathString())); + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().invalidModeForPath, mode, getPathString())); } NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits()); } + void setFileMode(int mode) { + NB.encodeInt32(info, infoOffset + P_MODE, mode); + } + /** * Get the cached creation time of this file, in milliseconds. * @@ -607,7 +635,8 @@ * * @param id * new object identifier for the entry. May be - * {@link ObjectId#zeroId()} to remove the current identifier. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to remove the + * current identifier. */ public void setObjectId(final AnyObjectId id) { id.copyRawTo(idBuffer(), idOffset()); @@ -654,6 +683,8 @@ } /** + * {@inheritDoc} + *

* Use for debugging only ! */ @SuppressWarnings("nls") @@ -730,7 +761,17 @@ return 0; } - private static String toString(final byte[] path) { + private static void checkPath(byte[] path) { + try { + SystemReader.getInstance().checkPath(path); + } catch (CorruptObjectException e) { + InvalidPathException p = new InvalidPathException(toString(path)); + p.initCause(e); + throw p; + } + } + + static String toString(final byte[] path) { return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,7 +61,8 @@ import org.eclipse.jgit.util.RawParseUtils; /** - * Iterate a {@link DirCache} as part of a TreeWalk. + * Iterate a {@link org.eclipse.jgit.dircache.DirCache} as part of a + * TreeWalk. *

* This is an iterator to adapt a loaded DirCache instance (such as * read from an existing .git/index file) to the tree structure @@ -103,9 +104,6 @@ /** The subtree containing {@link #currentEntry} if this is first entry. */ protected DirCacheTree currentSubtree; - /** Holds an {@link AttributesNode} for the current entry */ - private AttributesNode attributesNode; - /** * Create a new iterator for an already loaded DirCache instance. *

@@ -137,6 +135,7 @@ parseEntry(); } + /** {@inheritDoc} */ @Override public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { @@ -146,6 +145,7 @@ return new DirCacheIterator(this, currentSubtree); } + /** {@inheritDoc} */ @Override public EmptyTreeIterator createEmptyTreeIterator() { final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)]; @@ -154,6 +154,7 @@ return new EmptyTreeIterator(this, n, pathLen + 1); } + /** {@inheritDoc} */ @Override public boolean hasId() { if (currentSubtree != null) @@ -161,6 +162,7 @@ return currentEntry != null; } + /** {@inheritDoc} */ @Override public byte[] idBuffer() { if (currentSubtree != null) @@ -170,6 +172,7 @@ return zeroid; } + /** {@inheritDoc} */ @Override public int idOffset() { if (currentSubtree != null) @@ -179,6 +182,7 @@ return 0; } + /** {@inheritDoc} */ @Override public void reset() { if (!first()) { @@ -191,16 +195,19 @@ } } + /** {@inheritDoc} */ @Override public boolean first() { return ptr == treeStart; } + /** {@inheritDoc} */ @Override public boolean eof() { return ptr == treeEnd; } + /** {@inheritDoc} */ @Override public void next(int delta) { while (--delta >= 0) { @@ -214,6 +221,7 @@ } } + /** {@inheritDoc} */ @Override public void back(int delta) { while (--delta >= 0) { @@ -285,12 +293,15 @@ } /** - * Retrieves the {@link AttributesNode} for the current entry. + * Retrieves the {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. * * @param reader - * {@link ObjectReader} used to parse the .gitattributes entry. - * @return {@link AttributesNode} for the current entry. - * @throws IOException + * {@link org.eclipse.jgit.lib.ObjectReader} used to parse the + * .gitattributes entry. + * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. + * @throws java.io.IOException * @since 3.7 */ public AttributesNode getEntryAttributesNode(ObjectReader reader) @@ -318,11 +329,8 @@ AttributesNode r = new AttributesNode(); ObjectLoader loader = reader.open(objectId); if (loader != null) { - InputStream in = loader.openStream(); - try { + try (InputStream in = loader.openStream()) { r.parse(in); - } finally { - in.close(); } } return r.getRules().isEmpty() ? null : r; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ package org.eclipse.jgit.dircache; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; @@ -63,6 +64,7 @@ import java.util.List; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IndexReadException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.events.IndexChangedEvent; @@ -70,19 +72,21 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.internal.storage.file.LockFile; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * Support for the Git dircache (aka index file). @@ -107,6 +111,7 @@ private static final byte[] NO_CHECKSUM = {}; static final Comparator ENT_CMP = new Comparator() { + @Override public int compare(final DirCacheEntry o1, final DirCacheEntry o2) { final int cr = cmp(o1, o2); if (cr != 0) @@ -145,6 +150,28 @@ } /** + * Create a new in memory index read from the contents of a tree. + * + * @param reader + * reader to access the tree objects from a repository. + * @param treeId + * tree to read. Must identify a tree, not a tree-ish. + * @return a new cache which has no backing store file, but contains the + * contents of {@code treeId}. + * @throws java.io.IOException + * one or more trees not available from the ObjectReader. + * @since 4.2 + */ + public static DirCache read(ObjectReader reader, AnyObjectId treeId) + throws IOException { + DirCache d = newInCore(); + DirCacheBuilder b = d.builder(); + b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId); + b.finish(); + return d; + } + + /** * Create a new in-core index representation and read an index from disk. *

* The new index will be read before it is returned to the caller. Read @@ -155,9 +182,9 @@ * repository containing the index to read * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ @@ -182,9 +209,9 @@ * certain file system operations. * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ @@ -210,10 +237,10 @@ * certain file system operations. * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read, or the lock * could not be obtained. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ @@ -253,10 +280,10 @@ * listener to be informed when DirCache is committed * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read, or the lock * could not be obtained. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. * @since 2.0 @@ -287,10 +314,10 @@ * listener to be informed when DirCache is committed * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read, or the lock * could not be obtained. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ @@ -318,9 +345,6 @@ /** Our active lock (if we hold it); null if we don't have it locked. */ private LockFile myLock; - /** file system abstraction **/ - private final FS fs; - /** Keep track of whether the index has changed or not */ private FileSnapshot snapshot; @@ -350,7 +374,6 @@ */ public DirCache(final File indexLocation, final FS fs) { liveFile = indexLocation; - this.fs = fs; clear(); } @@ -358,7 +381,8 @@ * Create a new builder to update this cache. *

* Callers should add all entries to the builder, then use - * {@link DirCacheBuilder#finish()} to update this instance. + * {@link org.eclipse.jgit.dircache.DirCacheBuilder#finish()} to update this + * instance. * * @return a new builder instance for this cache. */ @@ -370,7 +394,8 @@ * Create a new editor to recreate this cache. *

* Callers should add commands to the editor, then use - * {@link DirCacheEditor#finish()} to update this instance. + * {@link org.eclipse.jgit.dircache.DirCacheEditor#finish()} to update this + * instance. * * @return a new builder instance for this cache. */ @@ -391,10 +416,10 @@ * the last time we consulted it. A missing index file will be treated as * though it were present but had no file entries in it. * - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read. This * DirCache instance may not be populated correctly. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ @@ -417,6 +442,12 @@ } } } catch (FileNotFoundException fnfe) { + if (liveFile.exists()) { + // Panic: the index file exists but we can't read it + throw new IndexReadException( + MessageFormat.format(JGitText.get().cannotReadIndex, + liveFile.getAbsolutePath(), fnfe)); + } // Someone must have deleted it between our exists test // and actually opening the path. That's fine, its empty. // @@ -427,8 +458,10 @@ } /** - * @return true if the memory state differs from the index file - * @throws IOException + * Whether the memory state differs from the index file + * + * @return {@code true} if the memory state differs from the index file + * @throws java.io.IOException */ public boolean isOutdated() throws IOException { if (liveFile == null || !liveFile.exists()) @@ -436,7 +469,9 @@ return snapshot == null || snapshot.isModified(liveFile); } - /** Empty this index, removing all entries. */ + /** + * Empty this index, removing all entries. + */ public void clear() { snapshot = null; sortedEntries = NO_ENTRIES; @@ -572,14 +607,14 @@ * * @return true if the lock is now held by the caller; false if it is held * by someone else. - * @throws IOException + * @throws java.io.IOException * the output file could not be created. The caller does not * hold the lock. */ public boolean lock() throws IOException { if (liveFile == null) throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile); - final LockFile tmp = new LockFile(liveFile, fs); + final LockFile tmp = new LockFile(liveFile); if (tmp.lock()) { tmp.setNeedStatInformation(true); myLock = tmp; @@ -599,16 +634,16 @@ * Once written the lock is closed and must be either committed with * {@link #commit()} or rolled back with {@link #unlock()}. * - * @throws IOException + * @throws java.io.IOException * the output file could not be created. The caller no longer * holds the lock. */ public void write() throws IOException { final LockFile tmp = myLock; requireLocked(tmp); - try { - writeTo(liveFile.getParentFile(), - new SafeBufferedOutputStream(tmp.getOutputStream())); + try (OutputStream o = tmp.getOutputStream(); + OutputStream bo = new BufferedOutputStream(o)) { + writeTo(liveFile.getParentFile(), bo); } catch (IOException err) { tmp.unlock(); throw err; @@ -626,8 +661,12 @@ final DigestOutputStream dos = new DigestOutputStream(os, foot); boolean extended = false; - for (int i = 0; i < entryCnt; i++) - extended |= sortedEntries[i].isExtended(); + for (int i = 0; i < entryCnt; i++) { + if (sortedEntries[i].isExtended()) { + extended = true; + break; + } + } // Write the header. // @@ -697,7 +736,7 @@ * @return true if the commit was successful and the file contains the new * data; false if the commit failed and the file remains with the * old data. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * the lock is not held. */ public boolean commit() { @@ -768,8 +807,11 @@ * information. If < 0 the entry does not exist in the index. * @since 3.4 */ - public int findEntry(final byte[] p, final int pLen) { - int low = 0; + public int findEntry(byte[] p, int pLen) { + return findEntry(0, p, pLen); + } + + int findEntry(int low, byte[] p, int pLen) { int high = entryCnt; while (low < high) { int mid = (low + high) >>> 1; @@ -869,8 +911,8 @@ */ public DirCacheEntry[] getEntriesWithin(String path) { if (path.length() == 0) { - final DirCacheEntry[] r = new DirCacheEntry[sortedEntries.length]; - System.arraycopy(sortedEntries, 0, r, 0, sortedEntries.length); + DirCacheEntry[] r = new DirCacheEntry[entryCnt]; + System.arraycopy(sortedEntries, 0, r, 0, entryCnt); return r; } if (!path.endsWith("/")) //$NON-NLS-1$ @@ -921,13 +963,13 @@ * responsible for flushing the inserter before trying to use the * returned tree identity. * @return identity for the root tree. - * @throws UnmergedPathException + * @throws org.eclipse.jgit.errors.UnmergedPathException * one or more paths contain higher-order stages (stage > 0), * which cannot be stored in a tree object. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * one or more paths contain an invalid mode which should never * appear in a tree object. - * @throws IOException + * @throws java.io.IOException * an unexpected error occurred writing to the object store. */ public ObjectId writeTree(final ObjectInserter ow) @@ -961,8 +1003,9 @@ * @throws IOException */ private void updateSmudgedEntries() throws IOException { - List paths = new ArrayList(128); + List paths = new ArrayList<>(128); try (TreeWalk walk = new TreeWalk(repository)) { + walk.setOperationType(OperationType.CHECKIN_OP); for (int i = 0; i < entryCnt; i++) if (sortedEntries[i].isSmudged()) paths.add(sortedEntries[i].getPathString()); @@ -974,6 +1017,7 @@ FileTreeIterator fIter = new FileTreeIterator(repository); walk.addTree(iIter); walk.addTree(fIter); + fIter.setDirCacheIterator(walk, 0); walk.setRecursive(true); while (walk.next()) { iIter = walk.getTree(0, DirCacheIterator.class); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,13 +62,14 @@ import org.eclipse.jgit.util.RawParseUtils; /** - * Single tree record from the 'TREE' {@link DirCache} extension. + * Single tree record from the 'TREE' {@link org.eclipse.jgit.dircache.DirCache} + * extension. *

* A valid cache tree record contains the object id of a tree object and the - * total number of {@link DirCacheEntry} instances (counted recursively) from - * the DirCache contained within the tree. This information facilitates faster - * traversal of the index and quicker generation of tree objects prior to - * creating a new commit. + * total number of {@link org.eclipse.jgit.dircache.DirCacheEntry} instances + * (counted recursively) from the DirCache contained within the tree. This + * information facilitates faster traversal of the index and quicker generation + * of tree objects prior to creating a new commit. *

* An invalid cache tree record indicates a known subtree whose file entries * have changed in ways that cause the tree to no longer have a known object id. @@ -80,6 +81,7 @@ private static final DirCacheTree[] NO_CHILDREN = {}; private static final Comparator TREE_CMP = new Comparator() { + @Override public int compare(final DirCacheTree o1, final DirCacheTree o2) { final byte[] a = o1.encodedName; final byte[] b = o2.encodedName; @@ -103,7 +105,7 @@ private DirCacheTree parent; /** Name of this tree within its parent. */ - private byte[] encodedName; + byte[] encodedName; /** Number of {@link DirCacheEntry} records that belong to this tree. */ private int entrySpan; @@ -204,10 +206,11 @@ /** * Determine if this cache is currently valid. *

- * A valid cache tree knows how many {@link DirCacheEntry} instances from - * the parent {@link DirCache} reside within this tree (recursively - * enumerated). It also knows the object id of the tree, as the tree should - * be readily available from the repository's object database. + * A valid cache tree knows how many + * {@link org.eclipse.jgit.dircache.DirCacheEntry} instances from the parent + * {@link org.eclipse.jgit.dircache.DirCache} reside within this tree + * (recursively enumerated). It also knows the object id of the tree, as the + * tree should be readily available from the repository's object database. * * @return true if this tree is knows key details about itself; false if the * tree needs to be regenerated. @@ -249,7 +252,15 @@ return children[i]; } - ObjectId getObjectId() { + /** + * Get the tree's ObjectId. + *

+ * If {@link #isValid()} returns false this method will return null. + * + * @return ObjectId of this tree or null. + * @since 4.3 + */ + public ObjectId getObjectId() { return id; } @@ -554,6 +565,7 @@ return -1; } + /** {@inheritDoc} */ @Override public String toString() { return getNameString(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for InvalidPathException + * * @param path + * the invalid path */ public InvalidPathException(String path) { this(JGitText.get().invalidPath, path); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/AmbiguousObjectException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/AmbiguousObjectException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/AmbiguousObjectException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/AmbiguousObjectException.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,9 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.ObjectId; -/** An {@link AbbreviatedObjectId} cannot be extended. */ +/** + * An {@link org.eclipse.jgit.lib.AbbreviatedObjectId} cannot be extended. + */ public class AmbiguousObjectException extends IOException { private static final long serialVersionUID = 1L; @@ -76,12 +78,20 @@ this.candidates = candidates; } - /** @return the AbbreviatedObjectId that has more than one result. */ + /** + * Get the {@code AbbreviatedObjectId} that has more than one result + * + * @return the {@code AbbreviatedObjectId} that has more than one result + */ public AbbreviatedObjectId getAbbreviatedObjectId() { return missing; } - /** @return the matching candidates (or at least a subset of them). */ + /** + * Get the matching candidates (or at least a subset of them) + * + * @return the matching candidates (or at least a subset of them) + */ public Collection getCandidates() { return candidates; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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.errors; + +/** + * BinaryBlobException is used to signal that binary data was found + * in a context that requires text (eg. for generating textual diffs). + * + * @since 4.10 + */ +public class BinaryBlobException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Construct a BinaryBlobException. + */ + public BinaryBlobException() {} +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 Ericsson + * 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.errors; + +import java.io.IOException; + +/** + * Thrown when an operation was canceled + * + * @since 4.7 + */ +public class CancelledException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for CancelledException + * + * @param message + * error message + */ + public CancelledException(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,22 +56,39 @@ public class CheckoutConflictException extends IOException { private static final long serialVersionUID = 1L; + private final String[] conflicting; + /** * Construct a CheckoutConflictException for the specified file * * @param file + * relative path of a file */ public CheckoutConflictException(String file) { super(MessageFormat.format(JGitText.get().checkoutConflictWithFile, file)); + conflicting = new String[] { file }; } /** * Construct a CheckoutConflictException for the specified set of files * * @param files + * an array of relative file paths */ public CheckoutConflictException(String[] files) { super(MessageFormat.format(JGitText.get().checkoutConflictWithFiles, buildList(files))); + conflicting = files; + } + + /** + * Get the relative paths of the conflicting files + * + * @return the relative paths of the conflicting files (relative to the + * working directory root). + * @since 4.4 + */ + public String[] getConflictingFiles() { + return conflicting; } private static String buildList(String[] files) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * 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.errors; + +/** + * Thrown when an external command failed + * + * @since 4.5 + */ +public class CommandFailedException extends Exception { + + private static final long serialVersionUID = 1L; + + private int returnCode; + + /** + * Constructor for CommandFailedException + * + * @param returnCode + * return code returned by the command + * @param message + * error message + */ + public CommandFailedException(int returnCode, String message) { + super(message); + this.returnCode = returnCode; + } + + /** + * Constructor for CommandFailedException + * + * @param returnCode + * return code returned by the command + * @param message + * error message + * @param cause + * exception causing this exception + */ + public CommandFailedException(int returnCode, String message, + Throwable cause) { + super(message, cause); + this.returnCode = returnCode; + } + + /** + * Get return code returned by the command + * + * @return return code returned by the command + */ + public int getReturnCode() { + return returnCode; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.internal.JGitText; -/** An exception detailing multiple reasons for failure. */ +/** + * An exception detailing multiple reasons for failure. + */ public class CompoundException extends Exception { private static final long serialVersionUID = 1L; @@ -75,7 +77,7 @@ */ public CompoundException(final Collection why) { super(format(why)); - causeList = Collections.unmodifiableList(new ArrayList(why)); + causeList = Collections.unmodifiableList(new ArrayList<>(why)); } /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.errors; -/** Indicates a text string is not a valid Git style configuration. */ +/** + * Indicates a text string is not a valid Git style configuration. + */ public class ConfigInvalidException extends Exception { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,10 @@ import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; /** @@ -59,15 +61,37 @@ public class CorruptObjectException extends IOException { private static final long serialVersionUID = 1L; + private ObjectChecker.ErrorType errorType; + + /** + * Report a specific error condition discovered in an object. + * + * @param type + * type of error + * @param id + * identity of the bad object + * @param why + * description of the error. + * @since 4.2 + */ + public CorruptObjectException(ObjectChecker.ErrorType type, AnyObjectId id, + String why) { + super(MessageFormat.format(JGitText.get().objectIsCorrupt3, + type.getMessageId(), id.name(), why)); + this.errorType = type; + } + /** * Construct a CorruptObjectException for reporting a problem specified * object id * * @param id + * a {@link org.eclipse.jgit.lib.AnyObjectId} * @param why + * error message */ - public CorruptObjectException(final AnyObjectId id, final String why) { - this(id.toObjectId(), why); + public CorruptObjectException(AnyObjectId id, String why) { + super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); } /** @@ -75,9 +99,11 @@ * object id * * @param id + * a {@link org.eclipse.jgit.lib.ObjectId} * @param why + * error message */ - public CorruptObjectException(final ObjectId id, final String why) { + public CorruptObjectException(ObjectId id, String why) { super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); } @@ -86,8 +112,9 @@ * with a specific object id. * * @param why + * error message */ - public CorruptObjectException(final String why) { + public CorruptObjectException(String why) { super(why); } @@ -105,4 +132,16 @@ super(why); initCause(cause); } + + /** + * Specific error condition identified by + * {@link org.eclipse.jgit.lib.ObjectChecker}. + * + * @return error condition or null. + * @since 4.2 + */ + @Nullable + public ObjectChecker.ErrorType getErrorType() { + return errorType; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017, 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.errors; + +import org.eclipse.jgit.annotations.Nullable; + +/** + * Exception thrown when encounters a corrupt pack index file. + * + * @since 4.9 + */ +public class CorruptPackIndexException extends Exception { + private static final long serialVersionUID = 1L; + + /** The error type of a corrupt index file. */ + public enum ErrorType { + /** Offset does not match index in pack file. */ + MISMATCH_OFFSET, + /** CRC does not match CRC of the object data in pack file. */ + MISMATCH_CRC, + /** CRC is not present in index file. */ + MISSING_CRC, + /** Object in pack is not present in index file. */ + MISSING_OBJ, + /** Object in index file is not present in pack file. */ + UNKNOWN_OBJ, + } + + private ErrorType errorType; + + /** + * Report a specific error condition discovered in an index file. + * + * @param message + * the error message. + * @param errorType + * the error type of corruption. + */ + public CorruptPackIndexException(String message, ErrorType errorType) { + super(message); + this.errorType = errorType; + } + + /** + * Specific the reason of the corrupt index file. + * + * @return error condition or null. + */ + @Nullable + public ErrorType getErrorType() { + return errorType; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,8 +53,12 @@ private static final long serialVersionUID = 1L; /** + * Constructor for DiffInterruptedException + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} * @since 4.1 */ public DiffInterruptedException(String message, Throwable cause) { @@ -62,14 +66,19 @@ } /** + * Constructor for DiffInterruptedException + * * @param message + * error message * @since 4.1 */ public DiffInterruptedException(String message) { super(message); } - /** Indicates that the thread computing a diff was interrupted. */ + /** + * Indicates that the thread computing a diff was interrupted. + */ public DiffInterruptedException() { super(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * 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.errors; + +/** + * Thrown by DirCache code when entries overlap in impossible way. + * + * @since 4.2 + */ +public class DirCacheNameConflictException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + private final String path1; + private final String path2; + + /** + * Construct an exception for a specific path. + * + * @param path1 + * one path that conflicts. + * @param path2 + * another path that conflicts. + */ + public DirCacheNameConflictException(String path1, String path2) { + super(path1 + ' ' + path2); + this.path1 = path1; + this.path2 = path2; + } + + /** + * Get one of the paths that has a conflict + * + * @return one of the paths that has a conflict + */ + public String getPath1() { + return path1; + } + + /** + * Get another path that has a conflict + * + * @return another path that has a conflict + */ + public String getPath2() { + return path2; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/IllegalTodoFileModification.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/IllegalTodoFileModification.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/IllegalTodoFileModification.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/IllegalTodoFileModification.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,10 @@ private static final long serialVersionUID = 1L; /** + * Constructor for IllegalTodoFileModification + * * @param msg + * error message */ public IllegalTodoFileModification(final String msg) { super(msg); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015, Christian Halstrick 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.errors; + +import java.io.IOException; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Cannot read the index. This is a serious error that users need to be made + * aware of. + * + * @since 4.2 + */ +public class IndexReadException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Constructs an IndexReadException with the default message. + */ + public IndexReadException() { + super(JGitText.get().indexWriteException); + } + + /** + * Constructs an IndexReadException with the specified detail message. + * + * @param s + * message + */ + public IndexReadException(final String s) { + super(s); + } + + /** + * Constructs an IndexReadException with the specified detail message. + * + * @param s + * message + * @param cause + * root cause exception + */ + public IndexReadException(final String s, final Throwable cause) { + super(s); + initCause(cause); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,8 +69,10 @@ } /** - * @param id the invalid id. + * Constructor for InvalidObjectIdException * + * @param id + * the invalid id. * @since 4.1 */ public InvalidObjectIdException(String id) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,6 @@ /** * Thrown when a pattern passed in an argument was wrong. - * */ public class InvalidPatternException extends Exception { private static final long serialVersionUID = 1L; @@ -55,6 +54,8 @@ private final String pattern; /** + * Constructor for InvalidPatternException + * * @param message * explains what was wrong with the pattern. * @param pattern @@ -66,6 +67,25 @@ } /** + * Constructor for InvalidPatternException + * + * @param message + * explains what was wrong with the pattern. + * @param pattern + * the invalid pattern. + * @param cause + * the cause. + * @since 4.10 + */ + public InvalidPatternException(String message, String pattern, + Throwable cause) { + this(message, pattern); + initCause(cause); + } + + /** + * Get the invalid pattern + * * @return the invalid pattern. */ public String getPattern() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,18 +49,33 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; -/** An object is too big to load into memory as a single byte array. */ +/** + * An object is too big to load into memory as a single byte array. + */ public class LargeObjectException extends RuntimeException { private static final long serialVersionUID = 1L; private ObjectId objectId; - /** Create a large object exception, where the object isn't known. */ + /** + * Create a large object exception, where the object isn't known. + */ public LargeObjectException() { // Do nothing. } /** + * Create a large object exception, where the object isn't known. + * + * @param cause + * the cause + * @since 4.10 + */ + public LargeObjectException(Throwable cause) { + initCause(cause); + } + + /** * Create a large object exception, naming the object that is too big. * * @param id @@ -71,12 +86,20 @@ setObjectId(id); } - /** @return identity of the object that is too large; may be null. */ + /** + * Get identity of the object that is too large; may be null + * + * @return identity of the object that is too large; may be null + */ public ObjectId getObjectId() { return objectId; } - /** @return either the hex encoded name of the object, or 'unknown object'. */ + /** + * Get the hex encoded name of the object, or 'unknown object' + * + * @return either the hex encoded name of the object, or 'unknown object' + */ protected String getObjectName() { if (getObjectId() != null) return getObjectId().name(); @@ -94,6 +117,7 @@ objectId = id.copy(); } + /** {@inheritDoc} */ @Override public String getMessage() { return MessageFormat.format(JGitText.get().largeObjectException, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,12 +57,15 @@ private File file; /** + * Constructor for LockFailedException + * * @param file * file that could not be locked * @param message * exception message * @param cause - * cause, for later retrieval by {@link Throwable#getCause()} + * cause, for later retrieval by + * {@link java.lang.Throwable#getCause()} * @since 4.1 */ public LockFailedException(File file, String message, Throwable cause) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java 2019-09-03 12:37:49.000000000 +0000 @@ -100,7 +100,11 @@ missing = null; } - /** @return the ObjectId that was not found. */ + /** + * Get the ObjectId that was not found + * + * @return the ObjectId that was not found + */ public ObjectId getObjectId() { return missing; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,6 +58,8 @@ private static final long serialVersionUID = 1L; /** + * Constructor for NoClosingBracketException + * * @param indexOfOpeningBracket * the position of the [ character which has no ] character. * @param openingBracket diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java 2019-09-03 12:37:49.000000000 +0000 @@ -118,6 +118,8 @@ } /** + * Get the reason why no merge base could be found + * * @return the reason why no merge base could be found */ public MergeBaseFailureReason getReason() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017, Matthias Sohn + * 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.errors; + +import java.io.IOException; + +/** + * Thrown when a PackFile is found not to contain the pack signature defined by + * git. + * + * @since 4.5 + */ +public class NoPackSignatureException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Construct an exception. + * + * @param why + * description of the type of error. + */ + public NoPackSignatureException(final String why) { + super(why); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,15 +44,17 @@ package org.eclipse.jgit.errors; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Repository; /** - * Indicates a {@link Repository} has no working directory, and is thus bare. + * Indicates a {@link org.eclipse.jgit.lib.Repository} has no working directory, + * and is thus bare. */ public class NoWorkTreeException extends IllegalStateException { private static final long serialVersionUID = 1L; - /** Creates an exception indicating there is no work tree for a repository. */ + /** + * Creates an exception indicating there is no work tree for a repository. + */ public NoWorkTreeException() { super(JGitText.get().bareRepositoryNoWorkdirAndIndex); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,9 @@ import org.eclipse.jgit.internal.JGitText; -/** Thrown when a PackFile previously failed and is known to be unusable */ +/** + * Thrown when a PackFile previously failed and is known to be unusable + */ public class PackInvalidException extends IOException { private static final long serialVersionUID = 1L; @@ -58,9 +60,24 @@ * * @param path * path of the invalid pack file. + * @deprecated Use {@link #PackInvalidException(File, Throwable)}. */ + @Deprecated public PackInvalidException(final File path) { - this(path.getAbsolutePath()); + this(path, null); + } + + /** + * Construct a pack invalid error with cause. + * + * @param path + * path of the invalid pack file. + * @param cause + * cause of the pack file becoming invalid. + * @since 4.5.7 + */ + public PackInvalidException(final File path, Throwable cause) { + this(path.getAbsolutePath(), cause); } /** @@ -68,8 +85,23 @@ * * @param path * path of the invalid pack file. + * @deprecated Use {@link #PackInvalidException(String, Throwable)}. */ + @Deprecated public PackInvalidException(final String path) { - super(MessageFormat.format(JGitText.get().packFileInvalid, path)); + this(path, null); + } + + /** + * Construct a pack invalid error with cause. + * + * @param path + * path of the invalid pack file. + * @param cause + * cause of the pack file becoming invalid. + * @since 4.5.7 + */ + public PackInvalidException(final String path, Throwable cause) { + super(MessageFormat.format(JGitText.get().packFileInvalid, path), cause); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ import java.io.IOException; -/** Thrown when a PackFile no longer matches the PackIndex. */ +/** + * Thrown when a PackFile no longer matches the PackIndex. + */ public class PackMismatchException extends IOException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,9 @@ import org.eclipse.jgit.internal.JGitText; -/** Indicates a local repository does not exist. */ +/** + * Indicates a local repository does not exist. + */ public class RepositoryNotFoundException extends TransportException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,6 +77,7 @@ this.revstr = revstr; } + /** {@inheritDoc} */ @Override public String toString() { return super.toString() + ":" + revstr; //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,16 +45,16 @@ package org.eclipse.jgit.errors; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.revwalk.RevWalk; /** - * Indicates a checked exception was thrown inside of {@link RevWalk}. + * Indicates a checked exception was thrown inside of + * {@link org.eclipse.jgit.revwalk.RevWalk}. *

* Usually this exception is thrown from the Iterator created around a RevWalk * instance, as the Iterator API does not allow checked exceptions to be thrown - * from hasNext() or next(). The {@link Exception#getCause()} of this exception - * is the original checked exception that we really wanted to throw back to the - * application for handling and recovery. + * from hasNext() or next(). The {@link java.lang.Exception#getCause()} of this + * exception is the original checked exception that we really wanted to throw + * back to the application for handling and recovery. */ public class RevWalkException extends RuntimeException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,8 +45,10 @@ import org.eclipse.jgit.internal.storage.pack.ObjectToPack; -/** A previously selected representation is no longer available. */ -public class StoredObjectRepresentationNotAvailableException extends Exception { +/** + * A previously selected representation is no longer available. + */ +public class StoredObjectRepresentationNotAvailableException extends Exception { //TODO remove unused ObjectToPack in 5.0 private static final long serialVersionUID = 1L; /** @@ -54,9 +56,28 @@ * * @param otp * the object whose current representation is no longer present. + * @deprecated use + * {@link #StoredObjectRepresentationNotAvailableException(ObjectToPack, Throwable)} + * instead. * @since 3.0 */ + @Deprecated public StoredObjectRepresentationNotAvailableException(ObjectToPack otp) { // Do nothing. } + + /** + * Construct an error for an object. + * + * @param otp + * the object whose current representation is no longer present. + * @param cause + * cause + * @since 4.10 + */ + public StoredObjectRepresentationNotAvailableException(ObjectToPack otp, + Throwable cause) { + super(cause); + // Do nothing. + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,15 @@ package org.eclipse.jgit.errors; -import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.transport.URIish; -/** Thrown when PackParser finds an object larger than a predefined limit */ -public class TooLargeObjectInPackException extends IOException { +/** + * Thrown when PackParser finds an object larger than a predefined limit + */ +public class TooLargeObjectInPackException extends TransportException { private static final long serialVersionUID = 1L; /** @@ -72,11 +74,26 @@ * too large object is known. * * @param objectSize + * a long. * @param maxObjectSizeLimit + * a long. */ public TooLargeObjectInPackException(long objectSize, long maxObjectSizeLimit) { super(MessageFormat.format(JGitText.get().receivePackObjectTooLarge2, Long.valueOf(objectSize), Long.valueOf(maxObjectSizeLimit))); } -} \ No newline at end of file + + /** + * Construct a too large object in pack exception. + * + * @param uri + * URI used for transport + * @param s + * message + * @since 4.4 + */ + public TooLargeObjectInPackException(URIish uri, String s) { + super(uri.setPass(null) + ": " + s); //$NON-NLS-1$ + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,6 @@ package org.eclipse.jgit.errors; import java.util.Locale; -import java.util.ResourceBundle; /** * Common base class for all translation bundle related exceptions. @@ -55,7 +54,8 @@ private final Locale locale; /** - * To construct an instance of {@link TranslationBundleException} + * Construct an instance of + * {@link org.eclipse.jgit.errors.TranslationBundleException} * * @param message * exception message @@ -65,7 +65,7 @@ * locale for which the exception occurred * @param cause * original exception that caused this exception. Usually thrown - * from the {@link ResourceBundle} class. + * from the {@link java.util.ResourceBundle} class. */ protected TranslationBundleException(String message, Class bundleClass, Locale locale, Exception cause) { super(message, cause); @@ -74,6 +74,8 @@ } /** + * Get bundle class + * * @return bundle class for which the exception occurred */ final public Class getBundleClass() { @@ -81,6 +83,8 @@ } /** + * Get locale for which the exception occurred + * * @return locale for which the exception occurred */ final public Locale getLocale() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,6 @@ package org.eclipse.jgit.errors; import java.util.Locale; -import java.util.ResourceBundle; /** * This exception will be thrown when a translation bundle loading @@ -53,8 +52,9 @@ private static final long serialVersionUID = 1L; /** - * Construct a {@link TranslationBundleLoadingException} for the specified - * bundle class and locale. + * Construct a + * {@link org.eclipse.jgit.errors.TranslationBundleLoadingException} for the + * specified bundle class and locale. * * @param bundleClass * the bundle class for which the loading failed @@ -62,11 +62,12 @@ * the locale for which the loading failed * @param cause * the original exception thrown from the - * {@link ResourceBundle#getBundle(String, Locale)} method. + * {@link java.util.ResourceBundle#getBundle(String, Locale)} + * method. */ public TranslationBundleLoadingException(Class bundleClass, Locale locale, Exception cause) { super("Loading of translation bundle failed for [" //$NON-NLS-1$ + bundleClass.getName() + ", " + locale.toString() + "]", //$NON-NLS-1$ //$NON-NLS-2$ bundleClass, locale, cause); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationStringMissingException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationStringMissingException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationStringMissingException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationStringMissingException.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,6 @@ package org.eclipse.jgit.errors; import java.util.Locale; -import java.util.ResourceBundle; /** * This exception will be thrown when a translation string for a translation @@ -55,8 +54,9 @@ private final String key; /** - * Construct a {@link TranslationStringMissingException} for the specified - * bundle class, locale and translation key + * Construct a + * {@link org.eclipse.jgit.errors.TranslationStringMissingException} for the + * specified bundle class, locale and translation key * * @param bundleClass * the bundle class for which a translation string was missing @@ -66,7 +66,7 @@ * the key of the missing translation string * @param cause * the original exception thrown from the - * {@link ResourceBundle#getString(String)} method. + * {@link java.util.ResourceBundle#getString(String)} method. */ public TranslationStringMissingException(Class bundleClass, Locale locale, String key, Exception cause) { super("Translation missing for [" + bundleClass.getName() + ", " //$NON-NLS-1$ //$NON-NLS-2$ @@ -76,6 +76,8 @@ } /** + * Get the key of the missing translation string + * * @return the key of the missing translation string */ public String getKey() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,7 +68,11 @@ entry = dce; } - /** @return the first non-zero stage of the unmerged path */ + /** + * Get the first non-zero stage of the unmerged path + * + * @return the first non-zero stage of the unmerged path + */ public DirCacheEntry getDirCacheEntry() { return entry; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnpackException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnpackException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnpackException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnpackException.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,9 @@ import org.eclipse.jgit.internal.JGitText; -/** Indicates a ReceivePack failure while scanning the pack stream. */ +/** + * Indicates a ReceivePack failure while scanning the pack stream. + */ public class UnpackException extends IOException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedCredentialItem.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedCredentialItem.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedCredentialItem.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedCredentialItem.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,12 @@ */ package org.eclipse.jgit.errors; -import org.eclipse.jgit.transport.CredentialItem; -import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.URIish; /** - * An exception thrown when a {@link CredentialItem} is requested from a - * {@link CredentialsProvider} which is not supported by this provider. + * An exception thrown when a {@link org.eclipse.jgit.transport.CredentialItem} + * is requested from a {@link org.eclipse.jgit.transport.CredentialsProvider} + * which is not supported by this provider. */ public class UnsupportedCredentialItem extends RuntimeException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017, Matthias Sohn + * 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.errors; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Thrown when a PackIndex uses an index version not supported by JGit. + * + * @since 4.5 + */ +public class UnsupportedPackIndexVersionException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Construct an exception. + * + * @param version + * pack index version + */ + public UnsupportedPackIndexVersionException(final int version) { + super(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, + Integer.valueOf(version))); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017, Matthias Sohn + * 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.errors; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Thrown when a PackFile uses a pack version not supported by JGit. + * + * @since 4.5 + */ +public class UnsupportedPackVersionException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Construct an exception. + * + * @param version + * pack version + */ + public UnsupportedPackVersionException(final long version) { + super(MessageFormat.format(JGitText.get().unsupportedPackVersion, + Long.valueOf(version))); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,17 @@ package org.eclipse.jgit.events; -/** Describes a change to one or more keys in the configuration. */ +/** + * Describes a change to one or more keys in the configuration. + */ public class ConfigChangedEvent extends RepositoryEvent { + /** {@inheritDoc} */ @Override public Class getListenerType() { return ConfigChangedListener.class; } + /** {@inheritDoc} */ @Override public void dispatch(ConfigChangedListener listener) { listener.onConfigChanged(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.events; -/** Receives {@link ConfigChangedEvent}s. */ +/** + * Receives {@link org.eclipse.jgit.events.ConfigChangedEvent}s. + */ public interface ConfigChangedListener extends RepositoryListener { /** * Invoked when any change is made to the configuration. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,17 @@ package org.eclipse.jgit.events; -/** Describes a change to one or more paths in the index file. */ +/** + * Describes a change to one or more paths in the index file. + */ public class IndexChangedEvent extends RepositoryEvent { + /** {@inheritDoc} */ @Override public Class getListenerType() { return IndexChangedListener.class; } + /** {@inheritDoc} */ @Override public void dispatch(IndexChangedListener listener) { listener.onIndexChanged(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.events; -/** Receives {@link IndexChangedEvent}s. */ +/** + * Receives {@link org.eclipse.jgit.events.IndexChangedEvent}s. + */ public interface IndexChangedListener extends RepositoryListener { /** * Invoked when any change is made to the index. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.events; -/** Tracks a previously registered {@link RepositoryListener}. */ +/** + * Tracks a previously registered {@link org.eclipse.jgit.events.RepositoryListener}. + */ public class ListenerHandle { private final ListenerList parent; @@ -59,11 +61,14 @@ this.listener = listener; } - /** Remove the listener and stop receiving events. */ + /** + * Remove the listener and stop receiving events. + */ public void remove() { parent.remove(this); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,9 +48,24 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; -/** Manages a thread-safe list of {@link RepositoryListener}s. */ +/** + * Manages a thread-safe list of {@link org.eclipse.jgit.events.RepositoryListener}s. + */ public class ListenerList { - private final ConcurrentMap, CopyOnWriteArrayList> lists = new ConcurrentHashMap, CopyOnWriteArrayList>(); + private final ConcurrentMap, CopyOnWriteArrayList> lists = new ConcurrentHashMap<>(); + + /** + * Register a {@link org.eclipse.jgit.events.WorkingTreeModifiedListener}. + * + * @param listener + * the listener implementation. + * @return handle to later remove the listener. + * @since 4.9 + */ + public ListenerHandle addWorkingTreeModifiedListener( + WorkingTreeModifiedListener listener) { + return addListener(WorkingTreeModifiedListener.class, listener); + } /** * Register an IndexChangedListener. @@ -89,8 +104,6 @@ /** * Add a listener to the list. * - * @param - * the type of listener being registered. * @param type * type of listener being registered. * @param listener @@ -126,7 +139,7 @@ if (list == null) { CopyOnWriteArrayList newList; - newList = new CopyOnWriteArrayList(); + newList = new CopyOnWriteArrayList<>(); list = lists.putIfAbsent(handle.type, newList); if (list == null) list = newList; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,17 @@ package org.eclipse.jgit.events; -/** Describes a change to one or more references of a repository. */ +/** + * Describes a change to one or more references of a repository. + */ public class RefsChangedEvent extends RepositoryEvent { + /** {@inheritDoc} */ @Override public Class getListenerType() { return RefsChangedListener.class; } + /** {@inheritDoc} */ @Override public void dispatch(RefsChangedListener listener) { listener.onRefsChanged(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.events; -/** Receives {@link RefsChangedEvent}s. */ +/** + * Receives {@link org.eclipse.jgit.events.RefsChangedEvent}s. + */ public interface RefsChangedListener extends RepositoryListener { /** * Invoked when any reference changes. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,7 +59,8 @@ * Set the repository this event occurred on. *

* This method should only be invoked once on each event object, and is - * automatically set by {@link Repository#fireEvent(RepositoryEvent)}. + * automatically set by + * {@link org.eclipse.jgit.lib.Repository#fireEvent(RepositoryEvent)}. * * @param r * the repository. @@ -69,12 +70,20 @@ repository = r; } - /** @return the repository that was changed. */ + /** + * Get the repository that was changed + * + * @return the repository that was changed + */ public Repository getRepository() { return repository; } - /** @return type of listener this event dispatches to. */ + /** + * Get type of listener this event dispatches to + * + * @return type of listener this event dispatches to + */ public abstract Class getListenerType(); /** @@ -85,6 +94,7 @@ */ public abstract void dispatch(T listener); + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.events; -/** A listener can register for event delivery. */ +/** + * A listener can register for event delivery. + */ public interface RepositoryListener { // Empty marker interface; see extensions for actual methods. } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.events; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A {@link org.eclipse.jgit.events.RepositoryEvent} describing changes to the + * working tree. It is fired whenever a + * {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies + * (adds/deletes/updates) files in the working tree. + * + * @since 4.9 + */ +public class WorkingTreeModifiedEvent + extends RepositoryEvent { + + private Collection modified; + + private Collection deleted; + + /** + * Creates a new {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} + * with the given collections. + * + * @param modified + * repository-relative paths that were added or updated + * @param deleted + * repository-relative paths that were deleted + */ + public WorkingTreeModifiedEvent(Collection modified, + Collection deleted) { + this.modified = modified; + this.deleted = deleted; + } + + /** + * Determines whether there are any changes recorded in this event. + * + * @return {@code true} if no files were modified or deleted, {@code false} + * otherwise + */ + public boolean isEmpty() { + return (modified == null || modified.isEmpty()) + && (deleted == null || deleted.isEmpty()); + } + + /** + * Retrieves the {@link java.util.Collection} of repository-relative paths + * of files that were modified (added or updated). + * + * @return the set + */ + public @NonNull Collection getModified() { + Collection result = modified; + if (result == null) { + result = Collections.emptyList(); + modified = result; + } + return result; + } + + /** + * Retrieves the {@link java.util.Collection} of repository-relative paths + * of files that were deleted. + * + * @return the set + */ + public @NonNull Collection getDeleted() { + Collection result = deleted; + if (result == null) { + result = Collections.emptyList(); + deleted = result; + } + return result; + } + + /** {@inheritDoc} */ + @Override + public Class getListenerType() { + return WorkingTreeModifiedListener.class; + } + + /** {@inheritDoc} */ + @Override + public void dispatch(WorkingTreeModifiedListener listener) { + listener.onWorkingTreeModified(this); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.events; + +/** + * Receives {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent}s, which are + * fired whenever a {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies + * (adds/deletes/updates) files in the working tree. + * + * @since 4.9 + */ +public interface WorkingTreeModifiedListener extends RepositoryListener { + + /** + * Respond to working tree modifications. + * + * @param event + * a {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} + * object. + */ + void onWorkingTreeModified(WorkingTreeModifiedEvent event); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,13 @@ private final boolean star; + /** + * Whether the char matches + * + * @param c + * a char. + * @return whether the char matches + */ protected abstract boolean matches(char c); AbstractHead(boolean star) { @@ -60,9 +67,11 @@ } /** + * Set {@link org.eclipse.jgit.fnmatch.Head}s which will not be modified. * * @param newHeads - * a list of {@link Head}s which will not be modified. + * a list of {@link org.eclipse.jgit.fnmatch.Head}s which will + * not be modified. */ public final void setNewHeads(List newHeads) { if (this.newHeads != null) @@ -70,6 +79,8 @@ this.newHeads = newHeads; } + /** {@inheritDoc} */ + @Override public List getNextHeads(char c) { if (matches(c)) return newHeads; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,16 +47,24 @@ final class CharacterHead extends AbstractHead { private final char expectedCharacter; + /** + * Constructor for CharacterHead + * + * @param expectedCharacter + * expected {@code char} + */ protected CharacterHead(final char expectedCharacter) { super(false); this.expectedCharacter = expectedCharacter; } + /** {@inheritDoc} */ @Override protected final boolean matches(final char c) { return c == expectedCharacter; } + /** {@inheritDoc} */ @Override public String toString() { return String.valueOf(expectedCharacter); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -120,18 +120,20 @@ private FileNameMatcher(final List headsStartValue, final List heads) { this.headsStartValue = headsStartValue; - this.heads = new ArrayList(heads.size()); + this.heads = new ArrayList<>(heads.size()); this.heads.addAll(heads); - this.listForLocalUseage = new ArrayList(heads.size()); + this.listForLocalUseage = new ArrayList<>(heads.size()); } /** + * Constructor for FileNameMatcher + * * @param patternString * must contain a pattern which fnmatch would accept. * @param invalidWildgetCharacter * if this parameter isn't null then this character will not * match at wildcards(* and ? are wildcards). - * @throws InvalidPatternException + * @throws org.eclipse.jgit.errors.InvalidPatternException * if the patternString contains a invalid fnmatch pattern. */ public FileNameMatcher(final String patternString, @@ -141,11 +143,13 @@ } /** - * A Copy Constructor which creates a new {@link FileNameMatcher} with the - * same state and reset point like other. + * A Copy Constructor which creates a new + * {@link org.eclipse.jgit.fnmatch.FileNameMatcher} with the same state and + * reset point like other. * * @param other - * another {@link FileNameMatcher} instance. + * another {@link org.eclipse.jgit.fnmatch.FileNameMatcher} + * instance. */ public FileNameMatcher(FileNameMatcher other) { this(other.headsStartValue, other.heads); @@ -158,7 +162,7 @@ final List allHeads = parseHeads(patternString, invalidWildgetCharacter); - List nextHeadsSuggestion = new ArrayList(2); + List nextHeadsSuggestion = new ArrayList<>(2); nextHeadsSuggestion.add(LastHead.INSTANCE); for (int i = allHeads.size() - 1; i >= 0; i--) { final AbstractHead head = allHeads.get(i); @@ -172,7 +176,7 @@ head.setNewHeads(nextHeadsSuggestion); } else { head.setNewHeads(nextHeadsSuggestion); - nextHeadsSuggestion = new ArrayList(2); + nextHeadsSuggestion = new ArrayList<>(2); nextHeadsSuggestion.add(head); } } @@ -236,7 +240,7 @@ throws InvalidPatternException { int currentIndex = 0; - List heads = new ArrayList(); + List heads = new ArrayList<>(); while (currentIndex < pattern.length()) { final int groupStart = indexOfUnescaped(pattern, '[', currentIndex); if (groupStart == -1) { @@ -262,7 +266,7 @@ private static List createSimpleHeads( final String patternPart, final Character invalidWildgetCharacter) { - final List heads = new ArrayList( + final List heads = new ArrayList<>( patternPart.length()); boolean escaped = false; @@ -347,6 +351,7 @@ } /** + * Append to the string which is matched against the patterns of this class * * @param stringToMatch * extends the string which is matched against the patterns of @@ -369,20 +374,24 @@ } /** + * Create a {@link org.eclipse.jgit.fnmatch.FileNameMatcher} instance which + * uses the same pattern like this matcher, but has the current state of + * this matcher as reset and start point * - * @return a {@link FileNameMatcher} instance which uses the same pattern - * like this matcher, but has the current state of this matcher as - * reset and start point. + * @return a {@link org.eclipse.jgit.fnmatch.FileNameMatcher} instance which + * uses the same pattern like this matcher, but has the current + * state of this matcher as reset and start point. */ public FileNameMatcher createMatcherForSuffix() { - final List copyOfHeads = new ArrayList(heads.size()); + final List copyOfHeads = new ArrayList<>(heads.size()); copyOfHeads.addAll(heads); return new FileNameMatcher(copyOfHeads); } /** + * Whether the matcher matches * - * @return true, if the string currently being matched does match. + * @return whether the matcher matches */ public boolean isMatch() { if (heads.isEmpty()) @@ -400,9 +409,9 @@ } /** + * Whether a match can be appended * - * @return false, if the string being matched will not match when the string - * gets extended. + * @return a boolean. */ public boolean canAppendMatch() { for (int i = 0; i < heads.size(); i++) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,7 +64,7 @@ GroupHead(String pattern, final String wholePattern) throws InvalidPatternException { super(false); - this.characterClasses = new ArrayList(); + this.characterClasses = new ArrayList<>(); this.inverse = pattern.startsWith("!"); //$NON-NLS-1$ if (inverse) { pattern = pattern.substring(1); @@ -130,6 +130,7 @@ } } + /** {@inheritDoc} */ @Override protected final boolean matches(final char c) { for (CharacterPattern pattern : characterClasses) { @@ -159,6 +160,7 @@ this.end = end; } + @Override public final boolean matches(char c) { return start <= c && c <= end; } @@ -167,6 +169,7 @@ private static final class DigitPattern implements CharacterPattern { static final GroupHead.DigitPattern INSTANCE = new DigitPattern(); + @Override public final boolean matches(char c) { return Character.isDigit(c); } @@ -175,6 +178,7 @@ private static final class LetterPattern implements CharacterPattern { static final GroupHead.LetterPattern INSTANCE = new LetterPattern(); + @Override public final boolean matches(char c) { return Character.isLetter(c); } @@ -183,6 +187,7 @@ private static final class LowerPattern implements CharacterPattern { static final GroupHead.LowerPattern INSTANCE = new LowerPattern(); + @Override public final boolean matches(char c) { return Character.isLowerCase(c); } @@ -191,6 +196,7 @@ private static final class UpperPattern implements CharacterPattern { static final GroupHead.UpperPattern INSTANCE = new UpperPattern(); + @Override public final boolean matches(char c) { return Character.isUpperCase(c); } @@ -199,6 +205,7 @@ private static final class WhitespacePattern implements CharacterPattern { static final GroupHead.WhitespacePattern INSTANCE = new WhitespacePattern(); + @Override public final boolean matches(char c) { return Character.isWhitespace(c); } @@ -211,6 +218,7 @@ this.expectedCharacter = c; } + @Override public final boolean matches(char c) { return this.expectedCharacter == c; } @@ -221,6 +229,7 @@ private static String punctCharacters = "-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"; //$NON-NLS-1$ + @Override public boolean matches(char c) { return punctCharacters.indexOf(c) != -1; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ interface Head { /** + * Get the character which decides which heads are returned * * @param c * the character which decides which heads are returned. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,6 +56,8 @@ // defined because of javadoc and visibility modifier. } + /** {@inheritDoc} */ + @Override public List getNextHeads(char c) { return FileNameMatcher.EMPTY_HEAD_LIST; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,11 +52,13 @@ this.excludedCharacter = excludedCharacter; } + /** {@inheritDoc} */ @Override protected final boolean matches(final char c) { return c != excludedCharacter; } + /** {@inheritDoc} */ @Override public String toString() { return isStar() ? "*" : "?"; //$NON-NLS-1$ //$NON-NLS-2$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,6 +49,7 @@ super(star); } + /** {@inheritDoc} */ @Override protected final boolean matches(final char c) { return true; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,6 +52,8 @@ public class RepoText extends TranslationBundle { /** + * Get an instance of this translation bundle + * * @return an instance of this translation bundle */ public static RepoText get() { @@ -64,6 +66,7 @@ /***/ public String errorIncludeNotImplemented; /***/ public String errorNoDefault; /***/ public String errorNoDefaultFilename; + /***/ public String errorNoFetch; /***/ public String errorParsingManifestFile; /***/ public String errorRemoteUnavailable; /***/ public String invalidManifest; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,8 +57,11 @@ import java.util.Map; import java.util.Set; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; +import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; @@ -77,10 +80,10 @@ */ public class ManifestParser extends DefaultHandler { private final String filename; - private final String baseUrl; + private final URI baseUrl; private final String defaultBranch; private final Repository rootRepo; - private final Map remotes; + private final Map remotes; private final Set plusGroups; private final Set minusGroups; private final List projects; @@ -110,12 +113,22 @@ } /** + * Constructor for ManifestParser + * * @param includedReader + * a + * {@link org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader} + * object. * @param filename + * a {@link java.lang.String} object. * @param defaultBranch + * a {@link java.lang.String} object. * @param baseUrl + * a {@link java.lang.String} object. * @param groups + * a {@link java.lang.String} object. * @param rootRepo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public ManifestParser(IncludedFileReader includedReader, String filename, String defaultBranch, String baseUrl, String groups, @@ -124,15 +137,10 @@ this.filename = filename; this.defaultBranch = defaultBranch; this.rootRepo = rootRepo; + this.baseUrl = normalizeEmptyPath(URI.create(baseUrl)); - // Strip trailing /s to match repo behavior. - int lastIndex = baseUrl.length() - 1; - while (lastIndex >= 0 && baseUrl.charAt(lastIndex) == '/') - lastIndex--; - this.baseUrl = baseUrl.substring(0, lastIndex + 1); - - plusGroups = new HashSet(); - minusGroups = new HashSet(); + plusGroups = new HashSet<>(); + minusGroups = new HashSet<>(); if (groups == null || groups.length() == 0 || groups.equals("default")) { //$NON-NLS-1$ // default means "all,-notdefault" @@ -146,16 +154,17 @@ } } - remotes = new HashMap(); - projects = new ArrayList(); - filteredProjects = new ArrayList(); + remotes = new HashMap<>(); + projects = new ArrayList<>(); + filteredProjects = new ArrayList<>(); } /** * Read the xml file. * * @param inputStream - * @throws IOException + * a {@link java.io.InputStream} object. + * @throws java.io.IOException */ public void read(InputStream inputStream) throws IOException { xmlInRead++; @@ -169,13 +178,11 @@ try { xr.parse(new InputSource(inputStream)); } catch (SAXException e) { - IOException error = new IOException( - RepoText.get().errorParsingManifestFile); - error.initCause(e); - throw error; + throw new IOException(RepoText.get().errorParsingManifestFile, e); } } + /** {@inheritDoc} */ @Override public void startElement( String uri, @@ -192,17 +199,19 @@ attributes.getValue("revision"), //$NON-NLS-1$ attributes.getValue("remote"), //$NON-NLS-1$ attributes.getValue("groups")); //$NON-NLS-1$ + currentProject.setRecommendShallow( + attributes.getValue("clone-depth")); //$NON-NLS-1$ } else if ("remote".equals(qName)) { //$NON-NLS-1$ String alias = attributes.getValue("alias"); //$NON-NLS-1$ String fetch = attributes.getValue("fetch"); //$NON-NLS-1$ - remotes.put(attributes.getValue("name"), fetch); //$NON-NLS-1$ + String revision = attributes.getValue("revision"); //$NON-NLS-1$ + Remote remote = new Remote(fetch, revision); + remotes.put(attributes.getValue("name"), remote); //$NON-NLS-1$ if (alias != null) - remotes.put(alias, fetch); + remotes.put(alias, remote); } else if ("default".equals(qName)) { //$NON-NLS-1$ defaultRemote = attributes.getValue("remote"); //$NON-NLS-1$ defaultRevision = attributes.getValue("revision"); //$NON-NLS-1$ - if (defaultRevision == null) - defaultRevision = defaultBranch; } else if ("copyfile".equals(qName)) { //$NON-NLS-1$ if (currentProject == null) throw new SAXException(RepoText.get().invalidManifest); @@ -211,12 +220,24 @@ currentProject.getPath(), attributes.getValue("src"), //$NON-NLS-1$ attributes.getValue("dest"))); //$NON-NLS-1$ + } else if ("linkfile".equals(qName)) { //$NON-NLS-1$ + if (currentProject == null) { + throw new SAXException(RepoText.get().invalidManifest); + } + currentProject.addLinkFile(new LinkFile( + rootRepo, + currentProject.getPath(), + attributes.getValue("src"), //$NON-NLS-1$ + attributes.getValue("dest"))); //$NON-NLS-1$ } else if ("include".equals(qName)) { //$NON-NLS-1$ String name = attributes.getValue("name"); //$NON-NLS-1$ - InputStream is = null; if (includedReader != null) { - try { - is = includedReader.readIncludeFile(name); + try (InputStream is = includedReader.readIncludeFile(name)) { + if (is == null) { + throw new SAXException( + RepoText.get().errorIncludeNotImplemented); + } + read(is); } catch (Exception e) { throw new SAXException(MessageFormat.format( RepoText.get().errorIncludeFile, name), e); @@ -224,25 +245,17 @@ } else if (filename != null) { int index = filename.lastIndexOf('/'); String path = filename.substring(0, index + 1) + name; - try { - is = new FileInputStream(path); + try (InputStream is = new FileInputStream(path)) { + read(is); } catch (IOException e) { throw new SAXException(MessageFormat.format( RepoText.get().errorIncludeFile, path), e); } } - if (is == null) { - throw new SAXException( - RepoText.get().errorIncludeNotImplemented); - } - try { - read(is); - } catch (IOException e) { - throw new SAXException(e); - } } } + /** {@inheritDoc} */ @Override public void endElement( String uri, @@ -254,6 +267,7 @@ } } + /** {@inheritDoc} */ @Override public void endDocument() throws SAXException { xmlInRead--; @@ -261,15 +275,19 @@ return; // Only do the following after we finished reading everything. - Map remoteUrls = new HashMap(); - URI baseUri; - try { - baseUri = new URI(baseUrl); - } catch (URISyntaxException e) { - throw new SAXException(e); + Map remoteUrls = new HashMap<>(); + if (defaultRevision == null && defaultRemote != null) { + Remote remote = remotes.get(defaultRemote); + if (remote != null) { + defaultRevision = remote.revision; + } + if (defaultRevision == null) { + defaultRevision = defaultBranch; + } } for (RepoProject proj : projects) { String remote = proj.getRemote(); + String revision = defaultRevision; if (remote == null) { if (defaultRemote == null) { if (filename != null) @@ -281,16 +299,24 @@ RepoText.get().errorNoDefault); } remote = defaultRemote; + } else { + Remote r = remotes.get(remote); + if (r != null && r.revision != null) { + revision = r.revision; + } } - String remoteUrl = remoteUrls.get(remote); + URI remoteUrl = remoteUrls.get(remote); if (remoteUrl == null) { - remoteUrl = baseUri.resolve(remotes.get(remote)).toString(); - if (!remoteUrl.endsWith("/")) //$NON-NLS-1$ - remoteUrl = remoteUrl + "/"; //$NON-NLS-1$ + String fetch = remotes.get(remote).fetch; + if (fetch == null) { + throw new SAXException(MessageFormat + .format(RepoText.get().errorNoFetch, remote)); + } + remoteUrl = normalizeEmptyPath(baseUrl.resolve(fetch)); remoteUrls.put(remote, remoteUrl); } - proj.setUrl(remoteUrl + proj.getName()) - .setDefaultRevision(defaultRevision); + proj.setUrl(remoteUrl.resolve(proj.getName()).toString()) + .setDefaultRevision(revision); } filteredProjects.addAll(projects); @@ -298,6 +324,23 @@ removeOverlaps(); } + static URI normalizeEmptyPath(URI u) { + // URI.create("scheme://host").resolve("a/b") => "scheme://hosta/b" + // That seems like bug https://bugs.openjdk.java.net/browse/JDK-4666701. + // We workaround this by special casing the empty path case. + if (u.getHost() != null && !u.getHost().isEmpty() && + (u.getPath() == null || u.getPath().isEmpty())) { + try { + return new URI(u.getScheme(), + u.getUserInfo(), u.getHost(), u.getPort(), + "/", u.getQuery(), u.getFragment()); //$NON-NLS-1$ + } catch (URISyntaxException x) { + throw new IllegalArgumentException(x.getMessage(), x); + } + } + return u; + } + /** * Getter for projects. * @@ -312,7 +355,7 @@ * * @return filtered projects list reference, never null */ - public List getFilteredProjects() { + public @NonNull List getFilteredProjects() { return filteredProjects; } @@ -338,6 +381,26 @@ else last = p; } + removeNestedCopyAndLinkfiles(); + } + + private void removeNestedCopyAndLinkfiles() { + for (RepoProject proj : filteredProjects) { + List copyfiles = new ArrayList<>(proj.getCopyFiles()); + proj.clearCopyFiles(); + for (CopyFile copyfile : copyfiles) { + if (!isNestedReferencefile(copyfile)) { + proj.addCopyFile(copyfile); + } + } + List linkfiles = new ArrayList<>(proj.getLinkFiles()); + proj.clearLinkFiles(); + for (LinkFile linkfile : linkfiles) { + if (!isNestedReferencefile(linkfile)) { + proj.addLinkFile(linkfile); + } + } + } } boolean inGroups(RepoProject proj) { @@ -357,4 +420,32 @@ } return false; } + + private boolean isNestedReferencefile(ReferenceFile referencefile) { + if (referencefile.dest.indexOf('/') == -1) { + // If the referencefile is at root level then it won't be nested. + return false; + } + for (RepoProject proj : filteredProjects) { + if (proj.getPath().compareTo(referencefile.dest) > 0) { + // Early return as remaining projects can't be ancestor of this + // referencefile config (filteredProjects is sorted). + return false; + } + if (proj.isAncestorOf(referencefile.dest)) { + return true; + } + } + return false; + } + + private static class Remote { + final String fetch; + final String revision; + + Remote(String fetch, String revision) { + this.fetch = fetch; + this.revision = revision; + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,15 +42,23 @@ */ package org.eclipse.jgit.gitrepo; +import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME; +import static org.eclipse.jgit.lib.Constants.R_REMOTES; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.GitCommand; import org.eclipse.jgit.api.SubmoduleAddCommand; @@ -62,6 +70,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; @@ -91,8 +100,8 @@ * If called against a bare repository, it will replace all the existing content * of the repository with the contents populated from the manifest. * - * repo manifest allows projects overlapping, e.g. one project's path is - * "foo" and another project's path is "foo/bar". This won't + * repo manifest allows projects overlapping, e.g. one project's manifestPath is + * "foo" and another project's manifestPath is "foo/bar". This won't * work in git submodule, so we'll skip all the sub projects * ("foo/bar" in the example) while converting. * @@ -100,19 +109,22 @@ * @since 3.4 */ public class RepoCommand extends GitCommand { - - private String path; - private String uri; - private String groups; + private String manifestPath; + private String baseUri; + private URI targetUri; + private String groupsParam; private String branch; private String targetBranch = Constants.HEAD; + private boolean recordRemoteBranch = false; + private boolean recordSubmoduleLabels = false; + private boolean recordShallowSubmodules = false; private PersonIdent author; private RemoteReader callback; private InputStream inputStream; private IncludedFileReader includedReader; + private boolean ignoreRemoteFailures = false; private List bareProjects; - private Git git; private ProgressMonitor monitor; /** @@ -133,9 +145,11 @@ * The URI of the remote repository * @param ref * The ref (branch/tag/etc.) to read - * @return the sha1 of the remote repository + * @return the sha1 of the remote repository, or null if the ref does + * not exist. * @throws GitAPIException */ + @Nullable public ObjectId sha1(String uri, String ref) throws GitAPIException; /** @@ -158,6 +172,7 @@ /** A default implementation of {@link RemoteReader} callback. */ public static class DefaultRemoteReader implements RemoteReader { + @Override public ObjectId sha1(String uri, String ref) throws GitAPIException { Map map = Git .lsRemoteRepository() @@ -167,20 +182,14 @@ return r != null ? r.getObjectId() : null; } + @Override public byte[] readFile(String uri, String ref, String path) throws GitAPIException, IOException { File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$ - Repository repo = Git - .cloneRepository() - .setBare(true) - .setDirectory(dir) - .setURI(uri) - .call() - .getRepository(); - try { - return readFileFromRepo(repo, ref, path); + try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir) + .setURI(uri).call()) { + return readFileFromRepo(git.getRepository(), ref, path); } finally { - repo.close(); FileUtils.delete(dir, FileUtils.RECURSIVE); } } @@ -223,7 +232,10 @@ } /** + * Constructor for RepoCommand + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ public RepoCommand(Repository repo) { super(repo); @@ -239,7 +251,7 @@ * @return this command */ public RepoCommand setPath(String path) { - this.path = path; + this.manifestPath = path; return this; } @@ -249,7 +261,7 @@ * Setting inputStream will ignore the path set. It will be closed in * {@link #call}. * - * @param inputStream + * @param inputStream a {@link java.io.InputStream} object. * @return this command * @since 3.5 */ @@ -259,13 +271,36 @@ } /** - * Set base URI of the pathes inside the XML + * Set base URI of the paths inside the XML. This is typically the name of + * the directory holding the manifest repository, eg. for + * https://android.googlesource.com/platform/manifest, this should be + * /platform (if you would run this on android.googlesource.com) or + * https://android.googlesource.com/platform elsewhere. * * @param uri + * the base URI * @return this command */ public RepoCommand setURI(String uri) { - this.uri = uri; + this.baseUri = uri; + return this; + } + + /** + * Set the URI of the superproject (this repository), so the .gitmodules + * file can specify the submodule URLs relative to the superproject. + * + * @param uri + * the URI of the repository holding the superproject. + * @return this command + * @since 4.8 + */ + public RepoCommand setTargetURI(String uri) { + // The repo name is interpreted as a directory, for example + // Gerrit (http://gerrit.googlesource.com/gerrit) has a + // .gitmodules referencing ../plugins/hooks, which is + // on http://gerrit.googlesource.com/plugins/hooks, + this.targetUri = URI.create(uri + "/"); //$NON-NLS-1$ return this; } @@ -276,7 +311,7 @@ * @return this command */ public RepoCommand setGroups(String groups) { - this.groups = groups; + this.groupsParam = groups; return this; } @@ -288,6 +323,7 @@ * revision specified in project, this branch will be used. * * @param branch + * a branch name * @return this command */ public RepoCommand setBranch(String branch) { @@ -305,6 +341,7 @@ * ignored. * * @param branch + * branch name * @return this command * @since 4.1 */ @@ -314,11 +351,66 @@ } /** + * Set whether the branch name should be recorded in .gitmodules. + *

+ * Submodule entries in .gitmodules can include a "branch" field + * to indicate what remote branch each submodule tracks. + *

+ * That field is used by "git submodule update --remote" to update + * to the tip of the tracked branch when asked and by Gerrit to + * update the superproject when a change on that branch is merged. + *

+ * Subprojects that request a specific commit or tag will not have + * a branch name recorded. + *

+ * Not implemented for non-bare repositories. + * + * @param enable Whether to record the branch name + * @return this command + * @since 4.2 + */ + public RepoCommand setRecordRemoteBranch(boolean enable) { + this.recordRemoteBranch = enable; + return this; + } + + /** + * Set whether the labels field should be recorded as a label in + * .gitattributes. + *

+ * Not implemented for non-bare repositories. + * + * @param enable Whether to record the labels in the .gitattributes + * @return this command + * @since 4.4 + */ + public RepoCommand setRecordSubmoduleLabels(boolean enable) { + this.recordSubmoduleLabels = enable; + return this; + } + + /** + * Set whether the clone-depth field should be recorded as a shallow + * recommendation in .gitmodules. + *

+ * Not implemented for non-bare repositories. + * + * @param enable Whether to record the shallow recommendation. + * @return this command + * @since 4.4 + */ + public RepoCommand setRecommendShallow(boolean enable) { + this.recordShallowSubmodules = enable; + return this; + } + + /** * The progress monitor associated with the clone operation. By default, * this is set to NullProgressMonitor * * @see org.eclipse.jgit.lib.NullProgressMonitor * @param monitor + * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return this command */ public RepoCommand setProgressMonitor(final ProgressMonitor monitor) { @@ -327,12 +419,33 @@ } /** + * Set whether to skip projects whose commits don't exist remotely. + *

+ * When set to true, we'll just skip the manifest entry and continue + * on to the next one. + *

+ * When set to false (default), we'll throw an error when remote + * failures occur. + *

+ * Not implemented for non-bare repositories. + * + * @param ignore Whether to ignore the remote failures. + * @return this command + * @since 4.3 + */ + public RepoCommand setIgnoreRemoteFailures(boolean ignore) { + this.ignoreRemoteFailures = ignore; + return this; + } + + /** * Set the author/committer for the bare repository commit. *

* For non-bare repositories, the current user will be used and this will be * ignored. * * @param author + * the author's {@link org.eclipse.jgit.lib.PersonIdent} * @return this command */ public RepoCommand setAuthor(final PersonIdent author) { @@ -346,6 +459,8 @@ * This is only used in bare repositories. * * @param callback + * a {@link org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader} + * object. * @return this command */ public RepoCommand setRemoteReader(final RemoteReader callback) { @@ -357,6 +472,9 @@ * Set the IncludedFileReader callback. * * @param reader + * a + * {@link org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader} + * object. * @return this command * @since 4.0 */ @@ -365,89 +483,140 @@ return this; } + /** {@inheritDoc} */ @Override public RevCommit call() throws GitAPIException { - try { - checkCallable(); - if (uri == null || uri.length() == 0) + checkCallable(); + if (baseUri == null) { + baseUri = ""; //$NON-NLS-1$ + } + if (inputStream == null) { + if (manifestPath == null || manifestPath.length() == 0) throw new IllegalArgumentException( - JGitText.get().uriNotConfigured); - if (inputStream == null) { - if (path == null || path.length() == 0) - throw new IllegalArgumentException( - JGitText.get().pathNotConfigured); - try { - inputStream = new FileInputStream(path); - } catch (IOException e) { - throw new IllegalArgumentException( - JGitText.get().pathNotConfigured); - } - } - - if (repo.isBare()) { - bareProjects = new ArrayList(); - if (author == null) - author = new PersonIdent(repo); - if (callback == null) - callback = new DefaultRemoteReader(); - } else - git = new Git(repo); - - ManifestParser parser = new ManifestParser( - includedReader, path, branch, uri, groups, repo); + JGitText.get().pathNotConfigured); try { - parser.read(inputStream); - for (RepoProject proj : parser.getFilteredProjects()) { - addSubmodule(proj.getUrl(), - proj.getPath(), - proj.getRevision(), - proj.getCopyFiles()); - } - } catch (GitAPIException | IOException e) { - throw new ManifestErrorException(e); + inputStream = new FileInputStream(manifestPath); + } catch (IOException e) { + throw new IllegalArgumentException( + JGitText.get().pathNotConfigured); } + } + + List filteredProjects; + try { + ManifestParser parser = new ManifestParser(includedReader, + manifestPath, branch, baseUri, groupsParam, repo); + parser.read(inputStream); + filteredProjects = parser.getFilteredProjects(); + } catch (IOException e) { + throw new ManifestErrorException(e); } finally { try { - if (inputStream != null) - inputStream.close(); + inputStream.close(); } catch (IOException e) { // Just ignore it, it's not important. } } if (repo.isBare()) { + bareProjects = new ArrayList<>(); + if (author == null) + author = new PersonIdent(repo); + if (callback == null) + callback = new DefaultRemoteReader(); + for (RepoProject proj : filteredProjects) { + addSubmoduleBare(proj.getUrl(), proj.getPath(), + proj.getRevision(), proj.getCopyFiles(), + proj.getLinkFiles(), proj.getGroups(), + proj.getRecommendShallow()); + } DirCache index = DirCache.newInCore(); DirCacheBuilder builder = index.builder(); ObjectInserter inserter = repo.newObjectInserter(); try (RevWalk rw = new RevWalk(repo)) { Config cfg = new Config(); + StringBuilder attributes = new StringBuilder(); for (RepoProject proj : bareProjects) { - String name = proj.getPath(); + String path = proj.getPath(); String nameUri = proj.getName(); - cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$ - cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$ - // create gitlink - DirCacheEntry dcEntry = new DirCacheEntry(name); ObjectId objectId; - if (ObjectId.isId(proj.getRevision())) + if (ObjectId.isId(proj.getRevision())) { objectId = ObjectId.fromString(proj.getRevision()); - else { + } else { objectId = callback.sha1(nameUri, proj.getRevision()); + if (objectId == null && !ignoreRemoteFailures) { + throw new RemoteUnavailableException(nameUri); + } + if (recordRemoteBranch) { + // can be branch or tag + cfg.setString("submodule", path, "branch", //$NON-NLS-1$ //$NON-NLS-2$ + proj.getRevision()); + } + + if (recordShallowSubmodules && proj.getRecommendShallow() != null) { + // The shallow recommendation is losing information. + // As the repo manifests stores the recommended + // depth in the 'clone-depth' field, while + // git core only uses a binary 'shallow = true/false' + // hint, we'll map any depth to 'shallow = true' + cfg.setBoolean("submodule", path, "shallow", //$NON-NLS-1$ //$NON-NLS-2$ + true); + } + } + if (recordSubmoduleLabels) { + StringBuilder rec = new StringBuilder(); + rec.append("/"); //$NON-NLS-1$ + rec.append(path); + for (String group : proj.getGroups()) { + rec.append(" "); //$NON-NLS-1$ + rec.append(group); + } + rec.append("\n"); //$NON-NLS-1$ + attributes.append(rec.toString()); } - if (objectId == null) - throw new RemoteUnavailableException(nameUri); - dcEntry.setObjectId(objectId); - dcEntry.setFileMode(FileMode.GITLINK); - builder.add(dcEntry); - for (CopyFile copyfile : proj.getCopyFiles()) { - byte[] src = callback.readFile( - nameUri, proj.getRevision(), copyfile.src); - objectId = inserter.insert(Constants.OBJ_BLOB, src); - dcEntry = new DirCacheEntry(copyfile.dest); + URI submodUrl = URI.create(nameUri); + if (targetUri != null) { + submodUrl = relativize(targetUri, submodUrl); + } + cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$ + cfg.setString("submodule", path, "url", submodUrl.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + + // create gitlink + if (objectId != null) { + DirCacheEntry dcEntry = new DirCacheEntry(path); dcEntry.setObjectId(objectId); - dcEntry.setFileMode(FileMode.REGULAR_FILE); + dcEntry.setFileMode(FileMode.GITLINK); builder.add(dcEntry); + + for (CopyFile copyfile : proj.getCopyFiles()) { + byte[] src = callback.readFile( + nameUri, proj.getRevision(), copyfile.src); + objectId = inserter.insert(Constants.OBJ_BLOB, src); + dcEntry = new DirCacheEntry(copyfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntry); + } + for (LinkFile linkfile : proj.getLinkFiles()) { + String link; + if (linkfile.dest.contains("/")) { //$NON-NLS-1$ + link = FileUtils.relativizeGitPath( + linkfile.dest.substring(0, + linkfile.dest.lastIndexOf('/')), + proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$ + } else { + link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$ + } + + objectId = inserter.insert(Constants.OBJ_BLOB, + link.getBytes( + Constants.CHARACTER_ENCODING)); + dcEntry = new DirCacheEntry(linkfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.SYMLINK); + builder.add(dcEntry); + } } } String content = cfg.toText(); @@ -460,11 +629,26 @@ dcEntry.setFileMode(FileMode.REGULAR_FILE); builder.add(dcEntry); + if (recordSubmoduleLabels) { + // create a new DirCacheEntry for .gitattributes file. + final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES); + ObjectId attrId = inserter.insert(Constants.OBJ_BLOB, + attributes.toString().getBytes(Constants.CHARACTER_ENCODING)); + dcEntryAttr.setObjectId(attrId); + dcEntryAttr.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntryAttr); + } + builder.finish(); ObjectId treeId = index.writeTree(inserter); // Create a Commit object, populate it and write it ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ + if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) { + // No change. Do nothing. + return rw.parseCommit(headId); + } + CommitBuilder commit = new CommitBuilder(); commit.setTreeId(treeId); if (headId != null) @@ -501,51 +685,134 @@ } return rw.parseCommit(commitId); - } catch (IOException e) { + } catch (GitAPIException | IOException e) { throw new ManifestErrorException(e); } } else { - return git - .commit() - .setMessage(RepoText.get().repoCommitMessage) - .call(); + try (Git git = new Git(repo)) { + for (RepoProject proj : filteredProjects) { + addSubmodule(proj.getUrl(), proj.getPath(), + proj.getRevision(), proj.getCopyFiles(), + proj.getLinkFiles(), git); + } + return git.commit().setMessage(RepoText.get().repoCommitMessage) + .call(); + } catch (GitAPIException | IOException e) { + throw new ManifestErrorException(e); + } } } - private void addSubmodule(String url, String name, String revision, - List copyfiles) throws GitAPIException, IOException { - if (repo.isBare()) { - RepoProject proj = new RepoProject(url, name, revision, null, null); - proj.addCopyFiles(copyfiles); - bareProjects.add(proj); - } else { - SubmoduleAddCommand add = git - .submoduleAdd() - .setPath(name) - .setURI(url); - if (monitor != null) - add.setProgressMonitor(monitor); - - Repository subRepo = add.call(); - if (revision != null) { - try (Git sub = new Git(subRepo)) { - sub.checkout().setName(findRef(revision, subRepo)) - .call(); - } - subRepo.close(); - git.add().addFilepattern(name).call(); - } - for (CopyFile copyfile : copyfiles) { - copyfile.copy(); - git.add().addFilepattern(copyfile.dest).call(); + private void addSubmodule(String url, String path, String revision, + List copyfiles, List linkfiles, Git git) + throws GitAPIException, IOException { + assert (!repo.isBare()); + assert (git != null); + if (!linkfiles.isEmpty()) { + throw new UnsupportedOperationException( + JGitText.get().nonBareLinkFilesNotSupported); + } + + SubmoduleAddCommand add = git.submoduleAdd().setPath(path).setURI(url); + if (monitor != null) + add.setProgressMonitor(monitor); + + Repository subRepo = add.call(); + if (revision != null) { + try (Git sub = new Git(subRepo)) { + sub.checkout().setName(findRef(revision, subRepo)).call(); } + subRepo.close(); + git.add().addFilepattern(path).call(); + } + for (CopyFile copyfile : copyfiles) { + copyfile.copy(); + git.add().addFilepattern(copyfile.dest).call(); + } + } + + private void addSubmoduleBare(String url, String path, String revision, + List copyfiles, List linkfiles, + Set groups, String recommendShallow) { + assert (repo.isBare()); + assert (bareProjects != null); + RepoProject proj = new RepoProject(url, path, revision, null, groups, + recommendShallow); + proj.addCopyFiles(copyfiles); + proj.addLinkFiles(linkfiles); + bareProjects.add(proj); + } + + /* + * Assume we are document "a/b/index.html", what should we put in a href to get to "a/" ? + * Returns the child if either base or child is not a bare path. This provides a missing feature in + * java.net.URI (see http://bugs.java.com/view_bug.do?bug_id=6226081). + */ + private static final String SLASH = "/"; //$NON-NLS-1$ + static URI relativize(URI current, URI target) { + if (!Objects.equals(current.getHost(), target.getHost())) { + return target; + } + + String cur = current.normalize().getPath(); + String dest = target.normalize().getPath(); + + // TODO(hanwen): maybe (absolute, relative) should throw an exception. + if (cur.startsWith(SLASH) != dest.startsWith(SLASH)) { + return target; + } + + while (cur.startsWith(SLASH)) { + cur = cur.substring(1); } + while (dest.startsWith(SLASH)) { + dest = dest.substring(1); + } + + if (cur.indexOf('/') == -1 || dest.indexOf('/') == -1) { + // Avoid having to special-casing in the next two ifs. + String prefix = "prefix/"; //$NON-NLS-1$ + cur = prefix + cur; + dest = prefix + dest; + } + + if (!cur.endsWith(SLASH)) { + // The current file doesn't matter. + int lastSlash = cur.lastIndexOf('/'); + cur = cur.substring(0, lastSlash); + } + String destFile = ""; //$NON-NLS-1$ + if (!dest.endsWith(SLASH)) { + // We always have to provide the destination file. + int lastSlash = dest.lastIndexOf('/'); + destFile = dest.substring(lastSlash + 1, dest.length()); + dest = dest.substring(0, dest.lastIndexOf('/')); + } + + String[] cs = cur.split(SLASH); + String[] ds = dest.split(SLASH); + + int common = 0; + while (common < cs.length && common < ds.length && cs[common].equals(ds[common])) { + common++; + } + + StringJoiner j = new StringJoiner(SLASH); + for (int i = common; i < cs.length; i++) { + j.add(".."); //$NON-NLS-1$ + } + for (int i = common; i < ds.length; i++) { + j.add(ds[i]); + } + + j.add(destFile); + return URI.create(j.toString()); } private static String findRef(String ref, Repository repo) throws IOException { if (!ObjectId.isId(ref)) { - Ref r = repo.getRef(Constants.DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ + Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ if (r != null) return r.getName(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,13 +70,17 @@ private final String remote; private final Set groups; private final List copyfiles; + private final List linkfiles; + private String recommendShallow; private String url; private String defaultRevision; /** - * The representation of a copy file configuration. + * The representation of a reference file configuration. + * + * @since 4.8 */ - public static class CopyFile { + public static class ReferenceFile { final Repository repo; final String path; final String src; @@ -92,12 +96,31 @@ * @param dest * the destination path relative to the super project. */ - public CopyFile(Repository repo, String path, String src, String dest) { + public ReferenceFile(Repository repo, String path, String src, String dest) { this.repo = repo; this.path = path; this.src = src; this.dest = dest; } + } + + /** + * The representation of a copy file configuration. + */ + public static class CopyFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this copyfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public CopyFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } /** * Do the copy file action. @@ -125,6 +148,29 @@ } /** + * The representation of a link file configuration. + * + * @since 4.8 + */ + public static class LinkFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this linkfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public LinkFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } + } + + /** + * Constructor for RepoProject + * * @param name * the relative path to the {@code remote} * @param path @@ -134,10 +180,14 @@ * @param remote * name of the remote definition * @param groups - * comma separated group list + * set of groups + * @param recommendShallow + * recommendation for shallowness + * @since 4.4 */ public RepoProject(String name, String path, String revision, - String remote, String groups) { + String remote, Set groups, + String recommendShallow) { if (name == null) { throw new NullPointerException(); } @@ -148,16 +198,38 @@ this.path = name; this.revision = revision; this.remote = remote; - this.groups = new HashSet(); - if (groups != null && groups.length() > 0) - this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$ - copyfiles = new ArrayList(); + this.groups = groups; + this.recommendShallow = recommendShallow; + copyfiles = new ArrayList<>(); + linkfiles = new ArrayList<>(); + } + + /** + * Constructor for RepoProject + * + * @param name + * the relative path to the {@code remote} + * @param path + * the relative path to the super project + * @param revision + * a SHA-1 or branch name or tag name + * @param remote + * name of the remote definition + * @param groupsParam + * comma separated group list + */ + public RepoProject(String name, String path, String revision, + String remote, String groupsParam) { + this(name, path, revision, remote, new HashSet(), null); + if (groupsParam != null && groupsParam.length() > 0) + this.setGroups(groupsParam); } /** * Set the url of the sub repo. * * @param url + * project url * @return this for chaining. */ public RepoProject setUrl(String url) { @@ -166,9 +238,24 @@ } /** + * Set the url of the sub repo. + * + * @param groupsParam + * comma separated group list + * @return this for chaining. + * @since 4.4 + */ + public RepoProject setGroups(String groupsParam) { + this.groups.clear(); + this.groups.addAll(Arrays.asList(groupsParam.split(","))); //$NON-NLS-1$ + return this; + } + + /** * Set the default revision for the sub repo. * * @param defaultRevision + * the name of the default revision * @return this for chaining. */ public RepoProject setDefaultRevision(String defaultRevision) { @@ -213,6 +300,16 @@ } /** + * Getter for the linkfile configurations. + * + * @return Immutable copy of {@code linkfiles} + * @since 4.8 + */ + public List getLinkFiles() { + return Collections.unmodifiableList(linkfiles); + } + + /** * Get the url of the sub repo. * * @return {@code url} @@ -234,6 +331,7 @@ * Test whether this sub repo belongs to a specified group. * * @param group + * a group * @return true if {@code group} is present. */ public boolean inGroup(String group) { @@ -241,9 +339,40 @@ } /** + * Return the set of groups. + * + * @return a Set of groups. + * @since 4.4 + */ + public Set getGroups() { + return groups; + } + + /** + * Return the recommendation for shallowness. + * + * @return the String of "clone-depth" + * @since 4.4 + */ + public String getRecommendShallow() { + return recommendShallow; + } + + /** + * Sets the recommendation for shallowness. + * + * @param recommendShallow + * recommendation for shallowness + * @since 4.4 + */ + public void setRecommendShallow(String recommendShallow) { + this.recommendShallow = recommendShallow; + } + + /** * Add a copy file configuration. * - * @param copyfile + * @param copyfile a {@link org.eclipse.jgit.gitrepo.RepoProject.CopyFile} object. */ public void addCopyFile(CopyFile copyfile) { copyfiles.add(copyfile); @@ -252,10 +381,51 @@ /** * Add a bunch of copyfile configurations. * - * @param copyfiles + * @param copyFiles + * a collection of + * {@link org.eclipse.jgit.gitrepo.RepoProject.CopyFile} objects + */ + public void addCopyFiles(Collection copyFiles) { + this.copyfiles.addAll(copyFiles); + } + + /** + * Clear all the copyfiles. + * + * @since 4.2 + */ + public void clearCopyFiles() { + this.copyfiles.clear(); + } + + /** + * Add a link file configuration. + * + * @param linkfile a {@link org.eclipse.jgit.gitrepo.RepoProject.LinkFile} object. + * @since 4.8 + */ + public void addLinkFile(LinkFile linkfile) { + linkfiles.add(linkfile); + } + + /** + * Add a bunch of linkfile configurations. + * + * @param linkFiles + * a collection of {@link LinkFile}s + * @since 4.8 */ - public void addCopyFiles(Collection copyfiles) { - this.copyfiles.addAll(copyfiles); + public void addLinkFiles(Collection linkFiles) { + this.linkfiles.addAll(linkFiles); + } + + /** + * Clear all the linkfiles. + * + * @since 4.8 + */ + public void clearLinkFiles() { + this.linkfiles.clear(); } private String getPathWithSlash() { @@ -273,9 +443,22 @@ * @return true if this sub repo is the ancestor of given sub repo. */ public boolean isAncestorOf(RepoProject that) { - return that.getPathWithSlash().startsWith(this.getPathWithSlash()); + return isAncestorOf(that.getPathWithSlash()); + } + + /** + * Check if this sub repo is an ancestor of the given path. + * + * @param thatPath + * path to be checked to see if it is within this repository + * @return true if this sub repo is an ancestor of the given path. + * @since 4.2 + */ + public boolean isAncestorOf(String thatPath) { + return thatPath.startsWith(getPathWithSlash()); } + /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (o instanceof RepoProject) { @@ -285,11 +468,13 @@ return false; } + /** {@inheritDoc} */ @Override public int hashCode() { return this.getPathWithSlash().hashCode(); } + /** {@inheritDoc} */ @Override public int compareTo(RepoProject that) { return this.getPathWithSlash().compareTo(that.getPathWithSlash()); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,6 +71,8 @@ private String commitMessage; /** + * Constructor for CommitMsgHook + * * @param repo * The repository * @param outputStream @@ -81,6 +83,7 @@ super(repo, outputStream); } + /** {@inheritDoc} */ @Override public String call() throws IOException, AbortedByHookException { if (commitMessage == null) { @@ -103,12 +106,15 @@ return getCommitEditMessageFilePath() != null && commitMessage != null; } + /** {@inheritDoc} */ @Override public String getHookName() { return NAME; } /** + * {@inheritDoc} + * * This hook receives one parameter, which is the path to the file holding * the current commit-msg, relative to the repository's work tree. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,9 +42,12 @@ */ package org.eclipse.jgit.hooks; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.io.UnsupportedEncodingException; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.AbortedByHookException; @@ -76,7 +79,10 @@ protected final PrintStream outputStream; /** + * Constructor for GitHook + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. * @param outputStream * The output stream the hook must use. {@code null} is allowed, * in which case the hook will use {@code System.out}. @@ -87,22 +93,23 @@ } /** + * {@inheritDoc} + *

* Run the hook. - * - * @throws IOException - * if IO goes wrong. - * @throws AbortedByHookException - * If the hook has been run and a returned an exit code - * different from zero. */ + @Override public abstract T call() throws IOException, AbortedByHookException; /** + * Get name of the hook + * * @return The name of the hook, which must not be {@code null}. */ public abstract String getHookName(); /** + * Get the repository + * * @return The repository. */ protected Repository getRepository() { @@ -131,6 +138,8 @@ } /** + * Get output stream + * * @return The output stream the hook must use. Never {@code null}, * {@code System.out} is returned by default. */ @@ -141,19 +150,37 @@ /** * Runs the hook, without performing any validity checks. * - * @throws AbortedByHookException + * @throws org.eclipse.jgit.api.errors.AbortedByHookException * If the underlying hook script exited with non-zero. */ protected void doRun() throws AbortedByHookException { final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); - final PrintStream hookErrRedirect = new PrintStream(errorByteArray); + PrintStream hookErrRedirect = null; + try { + hookErrRedirect = new PrintStream(errorByteArray, false, + UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // UTF-8 is guaranteed to be available + } ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(), getHookName(), getParameters(), getOutputStream(), hookErrRedirect, getStdinArgs()); if (result.isExecutedWithError()) { - throw new AbortedByHookException(errorByteArray.toString(), + throw new AbortedByHookException( + new String(errorByteArray.toByteArray(), UTF_8), getHookName(), result.getExitCode()); } } -} \ No newline at end of file + /** + * Check whether a 'native' (i.e. script) hook is installed in the + * repository. + * + * @return whether a native hook script is installed in the repository. + * @since 4.11 + */ + public boolean isNativeHookPresent() { + return FS.DETECTED.findHook(getRepository(), getHookName()) != null; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,8 +43,11 @@ package org.eclipse.jgit.hooks; import java.io.PrintStream; +import java.text.MessageFormat; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.LfsFactory; /** * Factory class for instantiating supported hooks. @@ -54,7 +57,10 @@ public class Hooks { /** + * Create pre-commit hook for the given repository + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. * @param outputStream * The output stream, or {@code null} to use {@code System.out} * @return The pre-commit hook for the given repository. @@ -65,7 +71,25 @@ } /** + * Create post-commit hook for the given repository + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. + * @param outputStream + * The output stream, or {@code null} to use {@code System.out} + * @return The post-commit hook for the given repository. + * @since 4.5 + */ + public static PostCommitHook postCommit(Repository repo, + PrintStream outputStream) { + return new PostCommitHook(repo, outputStream); + } + + /** + * Create commit-msg hook for the given repository + * + * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. * @param outputStream * The output stream, or {@code null} to use {@code System.out} * @return The commit-msg hook for the given repository. @@ -74,4 +98,29 @@ PrintStream outputStream) { return new CommitMsgHook(repo, outputStream); } + + /** + * Create pre-push hook for the given repository + * + * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. + * @param outputStream + * The output stream, or {@code null} to use {@code System.out} + * @return The pre-push hook for the given repository. + * @since 4.2 + */ + public static PrePushHook prePush(Repository repo, PrintStream outputStream) { + if (LfsFactory.getInstance().isAvailable()) { + PrePushHook hook = LfsFactory.getInstance().getPrePushHook(repo, + outputStream); + if (hook != null) { + if (hook.isNativeHookPresent()) { + throw new IllegalStateException(MessageFormat + .format(JGitText.get().lfsHookConflict, repo)); + } + return hook; + } + } + return new PrePushHook(repo, outputStream); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 Obeo. + * 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.hooks; + +import java.io.IOException; +import java.io.PrintStream; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.lib.Repository; + +/** + * The post-commit hook implementation. This hook is run after the + * commit was successfully executed. + * + * @since 4.5 + */ +public class PostCommitHook extends GitHook { + + /** The post-commit hook name. */ + public static final String NAME = "post-commit"; //$NON-NLS-1$ + + /** + * Constructor for PostCommitHook + * + * @param repo + * The repository + * @param outputStream + * The output stream the hook must use. {@code null} is allowed, + * in which case the hook will use {@code System.out}. + */ + protected PostCommitHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + /** {@inheritDoc} */ + @Override + public Void call() throws IOException, AbortedByHookException { + doRun(); + return null; + } + + /** {@inheritDoc} */ + @Override + public String getHookName() { + return NAME; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,8 @@ public static final String NAME = "pre-commit"; //$NON-NLS-1$ /** + * Constructor for PreCommitHook + * * @param repo * The repository * @param outputStream @@ -70,12 +72,14 @@ super(repo, outputStream); } + /** {@inheritDoc} */ @Override public Void call() throws IOException, AbortedByHookException { doRun(); return null; } + /** {@inheritDoc} */ @Override public String getHookName() { return NAME; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2015 Obeo. + * 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.hooks; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteRefUpdate; + +/** + * The pre-push hook implementation. The pre-push hook runs during + * git push, after the remote refs have been updated but before any objects have + * been transferred. + * + * @since 4.2 + */ +public class PrePushHook extends GitHook { + + /** + * Constant indicating the name of the pre-push hook. + */ + public static final String NAME = "pre-push"; //$NON-NLS-1$ + + private String remoteName; + + private String remoteLocation; + + private String refs; + + /** + * Constructor for PrePushHook + * + * @param repo + * The repository + * @param outputStream + * The output stream the hook must use. {@code null} is allowed, + * in which case the hook will use {@code System.out}. + */ + protected PrePushHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + /** {@inheritDoc} */ + @Override + protected String getStdinArgs() { + return refs; + } + + /** {@inheritDoc} */ + @Override + public String call() throws IOException, AbortedByHookException { + if (canRun()) { + doRun(); + } + return ""; //$NON-NLS-1$ + } + + /** + * @return {@code true} + */ + private boolean canRun() { + return true; + } + + /** {@inheritDoc} */ + @Override + public String getHookName() { + return NAME; + } + + /** + * {@inheritDoc} + *

+ * This hook receives two parameters, which is the name and the location of + * the remote repository. + */ + @Override + protected String[] getParameters() { + if (remoteName == null) { + remoteName = remoteLocation; + } + return new String[] { remoteName, remoteLocation }; + } + + /** + * Set remote name + * + * @param name + * remote name + */ + public void setRemoteName(String name) { + remoteName = name; + } + + /** + * Get remote name + * + * @return remote name or null + * @since 4.11 + */ + protected String getRemoteName() { + return remoteName; + } + + /** + * Set remote location + * + * @param location + * a remote location + */ + public void setRemoteLocation(String location) { + remoteLocation = location; + } + + /** + * Set Refs + * + * @param toRefs + * a collection of {@code RemoteRefUpdate}s + */ + public void setRefs(Collection toRefs) { + StringBuilder b = new StringBuilder(); + boolean first = true; + for (RemoteRefUpdate u : toRefs) { + if (!first) + b.append("\n"); //$NON-NLS-1$ + else + first = false; + b.append(u.getSrcRef()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getNewObjectId().getName()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getRemoteName()); + b.append(" "); //$NON-NLS-1$ + ObjectId ooid = u.getExpectedOldObjectId(); + b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid + .getName()); + } + refs = b.toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,8 +42,11 @@ */ package org.eclipse.jgit.ignore; -import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; +import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern; +import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; +import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace; + import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.internal.IMatcher; import org.eclipse.jgit.ignore.internal.PathMatcher; @@ -52,7 +55,7 @@ /** * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting - * also double star ** pattern. + * also double star {@code **} pattern. *

* This class is immutable and thread safe. * @@ -74,6 +77,7 @@ private final boolean dirOnly; /** + * Constructor for FastIgnoreRule * * @param pattern * ignore pattern as described in + * This function does NOT return the actual ignore status of the target! + * Please consult {@link #getResult()} for the negation status. The actual + * ignore status may be true or false depending on whether this rule is an + * ignore rule or a negation rule. + * + * @param path + * Name pattern of the file, relative to the base directory of + * this rule + * @param directory + * Whether the target file is a directory or not + * @param pathMatch + * {@code true} if the match is for the full path: see + * {@link IMatcher#matches(String, int, int)} + * @return True if a match was made. This does not necessarily mean that the + * target is ignored. Call {@link #getResult() getResult()} for the + * result. + * @since 4.11 + */ + public boolean isMatch(String path, boolean directory, boolean pathMatch) { if (path == null) return false; if (path.length() == 0) return false; - boolean match = matcher.matches(path, directory); + boolean match = matcher.matches(path, directory, pathMatch); return match; } /** - * @return True if the pattern is just a file name and not a path + * Whether the pattern is just a file name and not a path + * + * @return {@code true} if the pattern is just a file name and not a path */ public boolean getNameOnly() { return !(matcher instanceof PathMatcher); } /** + * Whether the pattern should match directories only * - * @return True if the pattern should match directories only + * @return {@code true} if the pattern should match directories only */ public boolean dirOnly() { return dirOnly; @@ -189,13 +221,17 @@ } /** - * @return true if the rule never matches (comment line or broken pattern) + * Whether the rule never matches + * + * @return {@code true} if the rule never matches (comment line or broken + * pattern) * @since 4.1 */ public boolean isEmpty() { return matcher == NO_MATCH; } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -208,6 +244,7 @@ } + /** {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; @@ -218,6 +255,7 @@ return result; } + /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java 2019-09-03 12:37:49.000000000 +0000 @@ -81,9 +81,11 @@ /** The rules that have been parsed into this node. */ private final List rules; - /** Create an empty ignore node with no rules. */ + /** + * Create an empty ignore node with no rules. + */ public IgnoreNode() { - rules = new ArrayList(); + rules = new ArrayList<>(); } /** @@ -91,7 +93,7 @@ * * @param rules * list of rules. - **/ + */ public IgnoreNode(List rules) { this.rules = rules; } @@ -102,7 +104,7 @@ * @param in * input stream holding the standard ignore format. The caller is * responsible for closing the stream. - * @throws IOException + * @throws java.io.IOException * Error thrown when reading an ignore file. */ public void parse(InputStream in) throws IOException { @@ -122,7 +124,11 @@ return new BufferedReader(new InputStreamReader(in, Constants.CHARSET)); } - /** @return list of all ignore rules held by this node. */ + /** + * Get list of all ignore rules held by this node + * + * @return list of all ignore rules held by this node + */ public List getRules() { return Collections.unmodifiableList(rules); } @@ -139,7 +145,13 @@ * @return status of the path. */ public MatchResult isIgnored(String entryPath, boolean isDirectory) { - return isIgnored(entryPath, isDirectory, false); + final Boolean result = checkIgnored(entryPath, isDirectory); + if (result == null) { + return MatchResult.CHECK_PARENT; + } + + return result.booleanValue() ? MatchResult.IGNORED + : MatchResult.NOT_IGNORED; } /** @@ -153,47 +165,51 @@ * true if the target item is a directory. * @param negateFirstMatch * true if the first match should be negated + * @deprecated negateFirstMatch is not honored anymore * @return status of the path. * @since 3.6 */ + @Deprecated public MatchResult isIgnored(String entryPath, boolean isDirectory, boolean negateFirstMatch) { - if (rules.isEmpty()) - if (negateFirstMatch) - return MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH; - else - return MatchResult.CHECK_PARENT; + final Boolean result = checkIgnored(entryPath, isDirectory); + if (result == null) { + return negateFirstMatch + ? MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH + : MatchResult.CHECK_PARENT; + } + + return result.booleanValue() ? MatchResult.IGNORED + : MatchResult.NOT_IGNORED; + } - // Parse rules in the reverse order that they were read + /** + * Determine if an entry path matches an ignore rule. + * + * @param entryPath + * the path to test. The path must be relative to this ignore + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @return Boolean.TRUE, if the entry is ignored; Boolean.FALSE, if the + * entry is forced to be not ignored (negated match); or null, if + * undetermined + * @since 4.11 + */ + public Boolean checkIgnored(String entryPath, boolean isDirectory) { + // Parse rules in the reverse order that they were read because later + // rules have higher priority for (int i = rules.size() - 1; i > -1; i--) { FastIgnoreRule rule = rules.get(i); - if (rule.isMatch(entryPath, isDirectory)) { - if (rule.getResult()) { - // rule matches: path could be ignored - if (negateFirstMatch) - // ignore current match, reset "negate" flag, continue - negateFirstMatch = false; - else - // valid match, just return - return MatchResult.IGNORED; - } else { - // found negated rule - if (negateFirstMatch) - // not possible to re-include excluded ignore rule - return MatchResult.NOT_IGNORED; - else - // set the flag and continue - negateFirstMatch = true; - } + if (rule.isMatch(entryPath, isDirectory, true)) { + return Boolean.valueOf(rule.getResult()); } } - if (negateFirstMatch) - // negated rule found but there is no previous rule in *this* file - return MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH; - // *this* file has no matching rules - return MatchResult.CHECK_PARENT; + return null; } + /** {@inheritDoc} */ @Override public String toString() { return rules.toString(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,8 +46,6 @@ * Base class for default methods as {@link #toString()} and such. *

* This class is immutable and thread safe. - * - * @since 3.6 */ public abstract class AbstractMatcher implements IMatcher { @@ -66,16 +64,19 @@ this.dirOnly = dirOnly; } + /** {@inheritDoc} */ @Override public String toString() { return pattern; } + /** {@inheritDoc} */ @Override public int hashCode() { return pattern.hashCode(); } + /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014, Andrey Loskutov + * Copyright (C) 2014, 2017 Andrey Loskutov * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -44,8 +44,6 @@ /** * Generic string matcher - * - * @since 3.6 */ public interface IMatcher { @@ -53,12 +51,14 @@ * Matcher that does not match any pattern. */ public static final IMatcher NO_MATCH = new IMatcher() { - public boolean matches(String path, boolean assumeDirectory) { + @Override + public boolean matches(String path, boolean assumeDirectory, + boolean pathMatch) { return false; } - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + @Override + public boolean matches(String segment, int startIncl, int endExcl) { return false; } }; @@ -71,9 +71,15 @@ * @param assumeDirectory * true to assume this path as directory (even if it doesn't end * with a slash) + * @param pathMatch + * {@code true} if the match is for the full path: prefix-only + * matches are not allowed, and + * {@link org.eclipse.jgit.ignore.internal.NameMatcher}s must + * match only the last component (if they can -- they may not, if + * they are anchored at the beginning) * @return true if this matcher pattern matches given string */ - boolean matches(String path, boolean assumeDirectory); + boolean matches(String path, boolean assumeDirectory, boolean pathMatch); /** * Matches only part of given string @@ -84,11 +90,7 @@ * start index, inclusive * @param endExcl * end index, exclusive - * @param assumeDirectory - * true to assume this path as directory (even if it doesn't end - * with a slash) * @return true if this matcher pattern matches given string */ - boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory); + boolean matches(String segment, int startIncl, int endExcl); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,6 @@ /** * Matcher for simple regex patterns starting with an asterisk, e.g. "*.tmp" - * - * @since 3.6 */ public class LeadingAsteriskMatcher extends NameMatcher { @@ -57,8 +55,9 @@ "Pattern must have leading asterisk: " + pattern); //$NON-NLS-1$ } - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + /** {@inheritDoc} */ + @Override + public boolean matches(String segment, int startIncl, int endExcl) { // faster local access, same as in string.indexOf() String s = subPattern; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,8 +47,6 @@ /** * Matcher built from patterns for file names (single path segments). This class * is immutable and thread safe. - * - * @since 3.6 */ public class NameMatcher extends AbstractMatcher { @@ -66,51 +64,80 @@ pattern = Strings.deleteBackslash(pattern); } beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash; - if (!beginning) + if (!beginning) { this.subPattern = pattern; - else + } else { this.subPattern = pattern.substring(1); + } } - public boolean matches(String path, boolean assumeDirectory) { - int end = 0; - int firstChar = 0; - do { - firstChar = getFirstNotSlash(path, end); - end = getFirstSlash(path, firstChar); - boolean match = matches(path, firstChar, end, assumeDirectory); - if (match) + /** {@inheritDoc} */ + @Override + public boolean matches(String path, boolean assumeDirectory, + boolean pathMatch) { + // A NameMatcher's pattern does not contain a slash. + int start = 0; + int stop = path.length(); + if (stop > 0 && path.charAt(0) == slash) { + start++; + } + if (pathMatch) { + // Can match only after the last slash + int lastSlash = path.lastIndexOf(slash, stop - 1); + if (lastSlash == stop - 1) { + // Skip trailing slash + lastSlash = path.lastIndexOf(slash, lastSlash - 1); + stop--; + } + boolean match; + if (lastSlash < start) { + match = matches(path, start, stop); + } else { + // Can't match if the path contains a slash if the pattern is + // anchored at the beginning + match = !beginning + && matches(path, lastSlash + 1, stop); + } + if (match && dirOnly) { + match = assumeDirectory; + } + return match; + } + while (start < stop) { + int end = path.indexOf(slash, start); + if (end < 0) { + end = stop; + } + if (end > start && matches(path, start, end)) { // make sure the directory matches: either if we are done with // segment and there is next one, or if the directory is assumed - return !dirOnly ? true : (end > 0 && end != path.length()) - || assumeDirectory; - } while (!beginning && end != path.length()); + return !dirOnly || assumeDirectory || end < stop; + } + if (beginning) { + break; + } + start = end + 1; + } return false; } - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + /** {@inheritDoc} */ + @Override + public boolean matches(String segment, int startIncl, int endExcl) { // faster local access, same as in string.indexOf() String s = subPattern; - if (s.length() != (endExcl - startIncl)) + int length = s.length(); + if (length != (endExcl - startIncl)) { return false; - for (int i = 0; i < s.length(); i++) { + } + for (int i = 0; i < length; i++) { char c1 = s.charAt(i); char c2 = segment.charAt(i + startIncl); - if (c1 != c2) + if (c1 != c2) { return false; + } } return true; } - private int getFirstNotSlash(String s, int start) { - int slashIdx = s.indexOf(slash, start); - return slashIdx == start ? start + 1 : start; - } - - private int getFirstSlash(String s, int start) { - int slashIdx = s.indexOf(slash, start); - return slashIdx == -1 ? s.length() : slashIdx; - } - } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,27 +52,28 @@ import java.util.List; import org.eclipse.jgit.errors.InvalidPatternException; -import org.eclipse.jgit.ignore.FastIgnoreRule; import org.eclipse.jgit.ignore.internal.Strings.PatternState; /** * Matcher built by patterns consists of multiple path segments. *

* This class is immutable and thread safe. - * - * @since 3.6 */ public class PathMatcher extends AbstractMatcher { - private static final WildMatcher WILD = WildMatcher.INSTANCE; + private static final WildMatcher WILD_NO_DIRECTORY = new WildMatcher(false); + + private static final WildMatcher WILD_ONLY_DIRECTORY = new WildMatcher( + true); private final List matchers; private final char slash; - private boolean beginning; + private final boolean beginning; - PathMatcher(String pattern, Character pathSeparator, boolean dirOnly) + private PathMatcher(String pattern, Character pathSeparator, + boolean dirOnly) throws InvalidPatternException { super(pattern, dirOnly); slash = getPathSeparator(pathSeparator); @@ -89,32 +90,39 @@ && count(path, slash, true) > 0; } - static private List createMatchers(List segments, + private static List createMatchers(List segments, Character pathSeparator, boolean dirOnly) throws InvalidPatternException { - List matchers = new ArrayList(segments.size()); + List matchers = new ArrayList<>(segments.size()); for (int i = 0; i < segments.size(); i++) { String segment = segments.get(i); IMatcher matcher = createNameMatcher0(segment, pathSeparator, - dirOnly); - if (matcher == WILD && i > 0 - && matchers.get(matchers.size() - 1) == WILD) - // collapse wildmatchers **/** is same as ** - continue; + dirOnly, i == segments.size() - 1); + if (i > 0) { + final IMatcher last = matchers.get(matchers.size() - 1); + if (isWild(matcher) && isWild(last)) + // collapse wildmatchers **/** is same as **, but preserve + // dirOnly flag (i.e. always use the last wildmatcher) + matchers.remove(matchers.size() - 1); + } + matchers.add(matcher); } return matchers; } /** + * Create path matcher * * @param pattern + * a pattern * @param pathSeparator * if this parameter isn't null then this character will not * match at wildcards(* and ? are wildcards). * @param dirOnly + * a boolean. * @return never null - * @throws InvalidPatternException + * @throws org.eclipse.jgit.errors.InvalidPatternException */ public static IMatcher createPathMatcher(String pattern, Character pathSeparator, boolean dirOnly) @@ -125,7 +133,7 @@ int slashIdx = pattern.indexOf(slash, 1); if (slashIdx > 0 && slashIdx < pattern.length() - 1) return new PathMatcher(pattern, pathSeparator, dirOnly); - return createNameMatcher0(pattern, pathSeparator, dirOnly); + return createNameMatcher0(pattern, pathSeparator, dirOnly, true); } /** @@ -152,12 +160,13 @@ } private static IMatcher createNameMatcher0(String segment, - Character pathSeparator, boolean dirOnly) + Character pathSeparator, boolean dirOnly, boolean lastSegment) throws InvalidPatternException { // check if we see /** or ** segments => double star pattern if (WildMatcher.WILDMATCH.equals(segment) || WildMatcher.WILDMATCH2.equals(segment)) - return WILD; + return dirOnly && lastSegment ? WILD_ONLY_DIRECTORY + : WILD_NO_DIRECTORY; PatternState state = checkWildCards(segment); switch (state) { @@ -172,10 +181,14 @@ } } - public boolean matches(String path, boolean assumeDirectory) { - if (matchers == null) - return simpleMatch(path, assumeDirectory); - return iterate(path, 0, path.length(), assumeDirectory); + /** {@inheritDoc} */ + @Override + public boolean matches(String path, boolean assumeDirectory, + boolean pathMatch) { + if (matchers == null) { + return simpleMatch(path, assumeDirectory, pathMatch); + } + return iterate(path, 0, path.length(), assumeDirectory, pathMatch); } /* @@ -183,97 +196,148 @@ * wildcards or single segments (mean: this is multi-segment path which must * be at the beginning of the another string) */ - private boolean simpleMatch(String path, boolean assumeDirectory) { + private boolean simpleMatch(String path, boolean assumeDirectory, + boolean pathMatch) { boolean hasSlash = path.indexOf(slash) == 0; - if (beginning && !hasSlash) + if (beginning && !hasSlash) { path = slash + path; - - if (!beginning && hasSlash) + } + if (!beginning && hasSlash) { path = path.substring(1); - - if (path.equals(pattern)) - // Exact match - if (dirOnly && !assumeDirectory) - // Directory expectations not met - return false; - else - // Directory expectations met - return true; - + } + if (path.equals(pattern)) { + // Exact match: must meet directory expectations + return !dirOnly || assumeDirectory; + } /* * Add slashes for startsWith check. This avoids matching e.g. * "/src/new" to /src/newfile" but allows "/src/new" to match * "/src/new/newfile", as is the git standard */ - if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR)) + String prefix = pattern + slash; + if (pathMatch) { + return path.equals(prefix) && (!dirOnly || assumeDirectory); + } + if (path.startsWith(prefix)) { return true; - + } return false; } - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + /** {@inheritDoc} */ + @Override + public boolean matches(String segment, int startIncl, int endExcl) { throw new UnsupportedOperationException( "Path matcher works only on entire paths"); //$NON-NLS-1$ } - boolean iterate(final String path, final int startIncl, final int endExcl, - boolean assumeDirectory) { + private boolean iterate(final String path, final int startIncl, + final int endExcl, boolean assumeDirectory, boolean pathMatch) { int matcher = 0; int right = startIncl; boolean match = false; int lastWildmatch = -1; + // ** matches may get extended if a later match fails. When that + // happens, we must extend the ** by exactly one segment. + // wildmatchBacktrackPos records the end of the segment after a ** + // match, so that we can reset correctly. + int wildmatchBacktrackPos = -1; while (true) { int left = right; right = path.indexOf(slash, right); if (right == -1) { - if (left < endExcl) + if (left < endExcl) { match = matches(matcher, path, left, endExcl, - assumeDirectory); + assumeDirectory, pathMatch); + } else { + // a/** should not match a/ or a + match = match && !isWild(matchers.get(matcher)); + } if (match) { - if (matcher == matchers.size() - 2 - && matchers.get(matcher + 1) == WILD) - // ** can match *nothing*: a/b/** match also a/b - return true; if (matcher < matchers.size() - 1 - && matchers.get(matcher) == WILD) { + && isWild(matchers.get(matcher))) { // ** can match *nothing*: a/**/b match also a/b matcher++; match = matches(matcher, path, left, endExcl, - assumeDirectory); - } else if (dirOnly && !assumeDirectory) + assumeDirectory, pathMatch); + } else if (dirOnly && !assumeDirectory) { // Directory expectations not met return false; + } } return match && matcher + 1 == matchers.size(); } - if (right - left > 0) - match = matches(matcher, path, left, right, assumeDirectory); - else { + if (wildmatchBacktrackPos < 0) { + wildmatchBacktrackPos = right; + } + if (right - left > 0) { + match = matches(matcher, path, left, right, assumeDirectory, + pathMatch); + } else { // path starts with slash??? right++; continue; } if (match) { - if (matchers.get(matcher) == WILD) { + boolean wasWild = isWild(matchers.get(matcher)); + if (wasWild) { lastWildmatch = matcher; + wildmatchBacktrackPos = -1; // ** can match *nothing*: a/**/b match also a/b right = left - 1; } matcher++; - if (matcher == matchers.size()) - return true; - } else if (lastWildmatch != -1) + if (matcher == matchers.size()) { + // We had a prefix match here. + if (!pathMatch) { + return true; + } else { + if (right == endExcl - 1) { + // Extra slash at the end: actually a full match. + // Must meet directory expectations + return !dirOnly || assumeDirectory; + } + // Prefix matches only if pattern ended with /** + if (wasWild) { + return true; + } + if (lastWildmatch >= 0) { + // Consider pattern **/x and input x/x. + // We've matched the prefix x/ so far: we + // must try to extend the **! + matcher = lastWildmatch + 1; + right = wildmatchBacktrackPos; + wildmatchBacktrackPos = -1; + } else { + return false; + } + } + } + } else if (lastWildmatch != -1) { matcher = lastWildmatch + 1; - else + right = wildmatchBacktrackPos; + wildmatchBacktrackPos = -1; + } else { return false; + } right++; } } - boolean matches(int matcherIdx, String path, int startIncl, int endExcl, - boolean assumeDirectory) { + private boolean matches(int matcherIdx, String path, int startIncl, + int endExcl, boolean assumeDirectory, boolean pathMatch) { IMatcher matcher = matchers.get(matcherIdx); - return matcher.matches(path, startIncl, endExcl, assumeDirectory); + + final boolean matches = matcher.matches(path, startIncl, endExcl); + if (!matches || !pathMatch || matcherIdx < matchers.size() - 1 + || !(matcher instanceof AbstractMatcher)) { + return matches; + } + + return assumeDirectory || !((AbstractMatcher) matcher).dirOnly; + } + + private static boolean isWild(IMatcher matcher) { + return matcher == WILD_NO_DIRECTORY || matcher == WILD_ONLY_DIRECTORY; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014, Andrey Loskutov + * Copyright (C) 2014, 2017 Andrey Loskutov * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -56,10 +56,8 @@ import org.eclipse.jgit.internal.JGitText; /** - * Various {@link String} related utility methods, written mostly to avoid - * generation of new String objects (e.g. via splitting Strings etc). - * - * @since 3.6 + * Various {@link java.lang.String} related utility methods, written mostly to + * avoid generation of new String objects (e.g. via splitting Strings etc). */ public class Strings { @@ -69,6 +67,8 @@ } /** + * Strip trailing characters + * * @param pattern * non null * @param c @@ -76,21 +76,68 @@ * @return new string with all trailing characters removed */ public static String stripTrailing(String pattern, char c) { - while (pattern.length() > 0 - && pattern.charAt(pattern.length() - 1) == c) - pattern = pattern.substring(0, pattern.length() - 1); - return pattern; + for (int i = pattern.length() - 1; i >= 0; i--) { + char charAt = pattern.charAt(i); + if (charAt != c) { + if (i == pattern.length() - 1) { + return pattern; + } + return pattern.substring(0, i + 1); + } + } + return ""; //$NON-NLS-1$ + } + + /** + * Strip trailing whitespace characters + * + * @param pattern + * non null + * @return new string with all trailing whitespace removed + */ + public static String stripTrailingWhitespace(String pattern) { + for (int i = pattern.length() - 1; i >= 0; i--) { + char charAt = pattern.charAt(i); + if (!Character.isWhitespace(charAt)) { + if (i == pattern.length() - 1) { + return pattern; + } + return pattern.substring(0, i + 1); + } + } + return ""; //$NON-NLS-1$ + } + + /** + * Check if pattern is a directory pattern ending with a path separator + * + * @param pattern + * non null + * @return {@code true} if the last character, which is not whitespace, is a + * path separator + */ + public static boolean isDirectoryPattern(String pattern) { + for (int i = pattern.length() - 1; i >= 0; i--) { + char charAt = pattern.charAt(i); + if (!Character.isWhitespace(charAt)) { + return charAt == FastIgnoreRule.PATH_SEPARATOR; + } + } + return false; } static int count(String s, char c, boolean ignoreFirstLast) { int start = 0; int count = 0; - while (true) { + int length = s.length(); + while (start < length) { start = s.indexOf(c, start); - if (start == -1) + if (start == -1) { break; - if (!ignoreFirstLast || (start != 0 && start != s.length())) + } + if (!ignoreFirstLast || (start != 0 && start != length - 1)) { count++; + } start++; } return count; @@ -110,7 +157,7 @@ if (count < 1) throw new IllegalStateException( "Pattern must have at least two segments: " + pattern); //$NON-NLS-1$ - List segments = new ArrayList(count); + List segments = new ArrayList<>(count); int right = 0; while (true) { int left = right; @@ -141,9 +188,7 @@ private static boolean isComplexWildcard(String pattern) { int idx1 = pattern.indexOf('['); if (idx1 != -1) { - int idx2 = pattern.indexOf(']', idx1); - if (idx2 > idx1) - return true; + return true; } if (pattern.indexOf('?') != -1) { return true; @@ -324,7 +369,10 @@ case '[': if (in_brackets > 0) { - sb.append('\\').append('['); + if (!seenEscape) { + sb.append('\\'); + } + sb.append('['); ignoreLastBracket = true; } else { if (!seenEscape) { @@ -397,12 +445,10 @@ try { return Pattern.compile(sb.toString()); } catch (PatternSyntaxException e) { - InvalidPatternException patternException = new InvalidPatternException( + throw new InvalidPatternException( MessageFormat.format(JGitText.get().invalidIgnoreRule, pattern), - pattern); - patternException.initCause(e); - throw patternException; + pattern, e); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,6 @@ /** * Matcher for simple patterns ending with an asterisk, e.g. "Makefile.*" - * - * @since 3.6 */ public class TrailingAsteriskMatcher extends NameMatcher { @@ -57,8 +55,9 @@ "Pattern must have trailing asterisk: " + pattern); //$NON-NLS-1$ } - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + /** {@inheritDoc} */ + @Override + public boolean matches(String segment, int startIncl, int endExcl) { // faster local access, same as in string.indexOf() String s = subPattern; // we don't need to count '*' character itself diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,11 +50,9 @@ /** * Matcher built from path segments containing wildcards. This matcher converts - * glob wildcards to Java {@link Pattern}'s. + * glob wildcards to Java {@link java.util.regex.Pattern}'s. *

* This class is immutable and thread safe. - * - * @since 3.6 */ public class WildCardMatcher extends NameMatcher { @@ -66,9 +64,9 @@ p = convertGlob(subPattern); } + /** {@inheritDoc} */ @Override - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + public boolean matches(String segment, int startIncl, int endExcl) { return p.matcher(segment.substring(startIncl, endExcl)).matches(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,8 +47,6 @@ * matcher matches any path. *

* This class is immutable and thread safe. - * - * @since 3.6 */ public final class WildMatcher extends AbstractMatcher { @@ -57,19 +55,26 @@ // double star for the beginning of pattern static final String WILDMATCH2 = "/**"; //$NON-NLS-1$ - static final WildMatcher INSTANCE = new WildMatcher(); - - private WildMatcher() { - super(WILDMATCH, false); + WildMatcher(boolean dirOnly) { + super(WILDMATCH, dirOnly); } - public final boolean matches(String path, boolean assumeDirectory) { - return true; + /** {@inheritDoc} */ + @Override + public final boolean matches(String path, boolean assumeDirectory, + boolean pathMatch) { + return !dirOnly || assumeDirectory + || !pathMatch && isSubdirectory(path); } - public final boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { + /** {@inheritDoc} */ + @Override + public final boolean matches(String segment, int startIncl, int endExcl) { return true; } + private static boolean isSubdirectory(String path) { + final int slashIndex = path.indexOf('/'); + return slashIndex >= 0 && slashIndex < path.length() - 1; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017, 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.internal.fsck; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.CorruptPackIndexException; +import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType; +import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Holds all fsck errors of a git repository. + */ +public class FsckError { + /** Represents a corrupt object. */ + public static class CorruptObject { + final ObjectId id; + + final int type; + + ObjectChecker.ErrorType errorType; + + /** + * @param id + * the object identifier. + * @param type + * type of the object. + */ + public CorruptObject(ObjectId id, int type) { + this.id = id; + this.type = type; + } + + void setErrorType(ObjectChecker.ErrorType errorType) { + this.errorType = errorType; + } + + /** @return identifier of the object. */ + public ObjectId getId() { + return id; + } + + /** @return type of the object. */ + public int getType() { + return type; + } + + /** @return error type of the corruption. */ + @Nullable + public ObjectChecker.ErrorType getErrorType() { + return errorType; + } + } + + /** Represents a corrupt pack index file. */ + public static class CorruptIndex { + String fileName; + + CorruptPackIndexException.ErrorType errorType; + + /** + * @param fileName + * the file name of the pack index. + * @param errorType + * the type of error as reported in + * {@link CorruptPackIndexException}. + */ + public CorruptIndex(String fileName, ErrorType errorType) { + this.fileName = fileName; + this.errorType = errorType; + } + + /** @return the file name of the index file. */ + public String getFileName() { + return fileName; + } + + /** @return the error type of the corruption. */ + public ErrorType getErrorType() { + return errorType; + } + } + + private final Set corruptObjects = new HashSet<>(); + + private final Set missingObjects = new HashSet<>(); + + private final Set corruptIndices = new HashSet<>(); + + private final Set nonCommitHeads = new HashSet<>(); + + /** + * Get corrupt objects from all pack files + * + * @return corrupt objects from all pack files + */ + public Set getCorruptObjects() { + return corruptObjects; + } + + /** + * Get missing objects that should present in pack files + * + * @return missing objects that should present in pack files + */ + public Set getMissingObjects() { + return missingObjects; + } + + /** + * Get corrupt index files associated with the packs + * + * @return corrupt index files associated with the packs + */ + public Set getCorruptIndices() { + return corruptIndices; + } + + /** + * Get refs/heads/* which point to non-commit object + * + * @return refs/heads/* which point to non-commit object + */ + public Set getNonCommitHeads() { + return nonCommitHeads; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2017, 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.internal.fsck; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.CRC32; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.CorruptPackIndexException; +import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject; +import org.eclipse.jgit.internal.storage.dfs.ReadableChannel; +import org.eclipse.jgit.internal.storage.file.PackIndex; +import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.transport.PackParser; +import org.eclipse.jgit.transport.PackedObjectInfo; + +/** + * A read-only pack parser for object validity checking. + */ +public class FsckPackParser extends PackParser { + private final CRC32 crc; + + private final ReadableChannel channel; + + private final Set corruptObjects = new HashSet<>(); + + private long expectedObjectCount = -1L; + + private long offset; + + private int blockSize; + + /** + * Constructor for FsckPackParser + * + * @param db + * the object database which stores repository's data. + * @param channel + * readable channel of the pack file. + */ + public FsckPackParser(ObjectDatabase db, ReadableChannel channel) { + super(db, Channels.newInputStream(channel)); + this.channel = channel; + setCheckObjectCollisions(false); + this.crc = new CRC32(); + this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536; + } + + /** {@inheritDoc} */ + @Override + protected void onPackHeader(long objCnt) throws IOException { + if (expectedObjectCount >= 0) { + // Some DFS pack files don't contain the correct object count, e.g. + // INSERT/RECEIVE packs don't always contain the correct object + // count in their headers. Overwrite the expected object count + // after parsing the pack header. + setExpectedObjectCount(expectedObjectCount); + } + } + + /** {@inheritDoc} */ + @Override + protected void onBeginWholeObject(long streamPosition, int type, + long inflatedSize) throws IOException { + crc.reset(); + } + + /** {@inheritDoc} */ + @Override + protected void onObjectHeader(Source src, byte[] raw, int pos, int len) + throws IOException { + crc.update(raw, pos, len); + } + + /** {@inheritDoc} */ + @Override + protected void onObjectData(Source src, byte[] raw, int pos, int len) + throws IOException { + crc.update(raw, pos, len); + } + + /** {@inheritDoc} */ + @Override + protected void onEndWholeObject(PackedObjectInfo info) throws IOException { + info.setCRC((int) crc.getValue()); + } + + /** {@inheritDoc} */ + @Override + protected void onBeginOfsDelta(long deltaStreamPosition, + long baseStreamPosition, long inflatedSize) throws IOException { + crc.reset(); + } + + /** {@inheritDoc} */ + @Override + protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId, + long inflatedSize) throws IOException { + crc.reset(); + } + + /** {@inheritDoc} */ + @Override + protected UnresolvedDelta onEndDelta() throws IOException { + UnresolvedDelta delta = new UnresolvedDelta(); + delta.setCRC((int) crc.getValue()); + return delta; + } + + /** {@inheritDoc} */ + @Override + protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode, + byte[] data) throws IOException { + // FsckPackParser ignores this event. + } + + /** {@inheritDoc} */ + @Override + protected void verifySafeObject(final AnyObjectId id, final int type, + final byte[] data) { + try { + super.verifySafeObject(id, type, data); + } catch (CorruptObjectException e) { + // catch the exception and continue parse the pack file + CorruptObject o = new CorruptObject(id.toObjectId(), type); + if (e.getErrorType() != null) { + o.setErrorType(e.getErrorType()); + } + corruptObjects.add(o); + } + } + + /** {@inheritDoc} */ + @Override + protected void onPackFooter(byte[] hash) throws IOException { + // Do nothing. + } + + /** {@inheritDoc} */ + @Override + protected boolean onAppendBase(int typeCode, byte[] data, + PackedObjectInfo info) throws IOException { + // Do nothing. + return false; + } + + /** {@inheritDoc} */ + @Override + protected void onEndThinPack() throws IOException { + // Do nothing. + } + + /** {@inheritDoc} */ + @Override + protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, + ObjectTypeAndSize info) throws IOException { + crc.reset(); + offset = obj.getOffset(); + return readObjectHeader(info); + } + + /** {@inheritDoc} */ + @Override + protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, + ObjectTypeAndSize info) throws IOException { + crc.reset(); + offset = delta.getOffset(); + return readObjectHeader(info); + } + + /** {@inheritDoc} */ + @Override + protected int readDatabase(byte[] dst, int pos, int cnt) + throws IOException { + // read from input instead of database. + int n = read(offset, dst, pos, cnt); + if (n > 0) { + offset += n; + } + return n; + } + + int read(long channelPosition, byte[] dst, int pos, int cnt) + throws IOException { + long block = channelPosition / blockSize; + byte[] bytes = readFromChannel(block); + if (bytes == null) { + return -1; + } + int offs = (int) (channelPosition - block * blockSize); + int bytesToCopy = Math.min(cnt, bytes.length - offs); + if (bytesToCopy < 1) { + return -1; + } + System.arraycopy(bytes, offs, dst, pos, bytesToCopy); + return bytesToCopy; + } + + private byte[] readFromChannel(long block) throws IOException { + channel.position(block * blockSize); + ByteBuffer buf = ByteBuffer.allocate(blockSize); + int totalBytesRead = 0; + while (totalBytesRead < blockSize) { + int bytesRead = channel.read(buf); + if (bytesRead == -1) { + if (totalBytesRead == 0) { + return null; + } + return Arrays.copyOf(buf.array(), totalBytesRead); + } + totalBytesRead += bytesRead; + } + return buf.array(); + } + + /** {@inheritDoc} */ + @Override + protected boolean checkCRC(int oldCRC) { + return oldCRC == (int) crc.getValue(); + } + + /** {@inheritDoc} */ + @Override + protected void onStoreStream(byte[] raw, int pos, int len) + throws IOException { + // Do nothing. + } + + /** + * Get corrupt objects reported by + * {@link org.eclipse.jgit.lib.ObjectChecker} + * + * @return corrupt objects that are reported by + * {@link org.eclipse.jgit.lib.ObjectChecker}. + */ + public Set getCorruptObjects() { + return corruptObjects; + } + + /** + * Verify the existing index file with all objects from the pack. + * + * @param idx + * index file associate with the pack + * @throws org.eclipse.jgit.errors.CorruptPackIndexException + * when the index file is corrupt. + */ + public void verifyIndex(PackIndex idx) + throws CorruptPackIndexException { + ObjectIdOwnerMap inPack = new ObjectIdOwnerMap<>(); + for (int i = 0; i < getObjectCount(); i++) { + PackedObjectInfo entry = getObject(i); + inPack.add(new ObjFromPack(entry)); + + long offs = idx.findOffset(entry); + if (offs == -1) { + throw new CorruptPackIndexException( + MessageFormat.format(JGitText.get().missingObject, + Integer.valueOf(entry.getType()), + entry.getName()), + ErrorType.MISSING_OBJ); + } else if (offs != entry.getOffset()) { + throw new CorruptPackIndexException(MessageFormat + .format(JGitText.get().mismatchOffset, entry.getName()), + ErrorType.MISMATCH_OFFSET); + } + + try { + if (idx.hasCRC32Support() + && (int) idx.findCRC32(entry) != entry.getCRC()) { + throw new CorruptPackIndexException( + MessageFormat.format(JGitText.get().mismatchCRC, + entry.getName()), + ErrorType.MISMATCH_CRC); + } + } catch (MissingObjectException e) { + throw new CorruptPackIndexException(MessageFormat + .format(JGitText.get().missingCRC, entry.getName()), + ErrorType.MISSING_CRC); + } + } + + for (MutableEntry entry : idx) { + if (!inPack.contains(entry.toObjectId())) { + throw new CorruptPackIndexException(MessageFormat.format( + JGitText.get().unknownObjectInIndex, entry.name()), + ErrorType.UNKNOWN_OBJ); + } + } + } + + /** + * Set the object count for overwriting the expected object count from pack + * header. + * + * @param objectCount + * the actual expected object count. + */ + public void overwriteObjectCount(long objectCount) { + this.expectedObjectCount = objectCount; + } + + static class ObjFromPack extends ObjectIdOwnerMap.Entry { + ObjFromPack(AnyObjectId id) { + super(id); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,4 @@ +/** + * Git fsck support. + */ +package org.eclipse.jgit.internal.fsck; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,8 @@ public class JGitText extends TranslationBundle { /** + * Get an instance of this translation bundle + * * @return an instance of this translation bundle */ public static JGitText get() { @@ -79,6 +81,9 @@ /***/ public String atLeastOnePathIsRequired; /***/ public String atLeastOnePatternIsRequired; /***/ public String atLeastTwoFiltersNeeded; + /***/ public String atomicPushNotSupported; + /***/ public String atomicRefUpdatesNotSupported; + /***/ public String atomicSymRefNotSupported; /***/ public String authenticationNotSupported; /***/ public String badBase64InputCharacterAt; /***/ public String badEntryDelimiter; @@ -88,6 +93,7 @@ /***/ public String badObjectType; /***/ public String badRef; /***/ public String badSectionEntry; + /***/ public String badShallowLine; /***/ public String bareRepositoryNoWorkdirAndIndex; /***/ public String base64InputNotProperlyPadded; /***/ public String baseLengthIncorrect; @@ -96,14 +102,20 @@ /***/ public String blameNotCommittedYet; /***/ public String blobNotFound; /***/ public String blobNotFoundForPath; + /***/ public String blockLimitNotMultipleOfBlockSize; + /***/ public String blockLimitNotPositive; + /***/ public String blockSizeNotPowerOf2; + /***/ public String bothRefTargetsMustNotBeNull; /***/ public String branchNameInvalid; /***/ public String buildingBitmaps; /***/ public String cachedPacksPreventsIndexCreation; /***/ public String cachedPacksPreventsListingObjects; + /***/ public String cannotAccessLastModifiedForSafeDeletion; /***/ public String cannotBeCombined; /***/ public String cannotBeRecursiveWhenTreesAreIncluded; /***/ public String cannotChangeActionOnComment; /***/ public String cannotChangeToComment; + /***/ public String cannotCheckoutFromUnbornBranch; /***/ public String cannotCheckoutOursSwitchBranch; /***/ public String cannotCombineSquashWithNoff; /***/ public String cannotCombineTreeFilterWithRevFilter; @@ -142,10 +154,12 @@ /***/ public String cannotParseGitURIish; /***/ public String cannotPullOnARepoWithState; /***/ public String cannotRead; + /***/ public String cannotReadBackDelta; /***/ public String cannotReadBlob; /***/ public String cannotReadCommit; /***/ public String cannotReadFile; /***/ public String cannotReadHEAD; + /***/ public String cannotReadIndex; /***/ public String cannotReadObject; /***/ public String cannotReadObjectsPath; /***/ public String cannotReadTree; @@ -155,6 +169,7 @@ /***/ public String cannotStoreObjects; /***/ public String cannotResolveUniquelyAbbrevObjectId; /***/ public String cannotUnloadAModifiedTree; + /***/ public String cannotUpdateUnbornBranch; /***/ public String cannotWorkWithOtherStagesThanZeroRightNow; /***/ public String cannotWriteObjectsPath; /***/ public String canOnlyCherryPickCommitsWithOneParent; @@ -164,12 +179,16 @@ /***/ public String cantPassMeATree; /***/ public String channelMustBeInRange1_255; /***/ public String characterClassIsNotSupported; + /***/ public String checkingOutFiles; /***/ public String checkoutConflictWithFile; /***/ public String checkoutConflictWithFiles; /***/ public String checkoutUnexpectedResult; /***/ public String classCastNotA; /***/ public String cloneNonEmptyDirectory; + /***/ public String closeLockTokenFailed; + /***/ public String closed; /***/ public String collisionOn; + /***/ public String commandClosedStderrButDidntExit; /***/ public String commandRejectedByHook; /***/ public String commandWasCalledInTheWrongState; /***/ public String commitAlreadyExists; @@ -177,18 +196,23 @@ /***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported; /***/ public String commitAmendOnInitialNotPossible; /***/ public String compressingObjects; + /***/ public String configSubsectionContainsNewline; + /***/ public String configSubsectionContainsNullByte; + /***/ public String configValueContainsNullByte; + /***/ public String configHandleIsStale; /***/ public String connectionFailed; /***/ public String connectionTimeOut; /***/ public String contextMustBeNonNegative; /***/ public String corruptionDetectedReReadingAt; + /***/ public String corruptObjectBadDate; + /***/ public String corruptObjectBadEmail; /***/ public String corruptObjectBadStream; /***/ public String corruptObjectBadStreamCorruptHeader; + /***/ public String corruptObjectBadTimezone; /***/ public String corruptObjectDuplicateEntryNames; /***/ public String corruptObjectGarbageAfterSize; /***/ public String corruptObjectIncorrectLength; /***/ public String corruptObjectIncorrectSorting; - /***/ public String corruptObjectInvalidAuthor; - /***/ public String corruptObjectInvalidCommitter; /***/ public String corruptObjectInvalidEntryMode; /***/ public String corruptObjectInvalidMode; /***/ public String corruptObjectInvalidModeChar; @@ -207,11 +231,11 @@ /***/ public String corruptObjectInvalidNamePrn; /***/ public String corruptObjectInvalidObject; /***/ public String corruptObjectInvalidParent; - /***/ public String corruptObjectInvalidTagger; /***/ public String corruptObjectInvalidTree; /***/ public String corruptObjectInvalidType; /***/ public String corruptObjectInvalidType2; /***/ public String corruptObjectMalformedHeader; + /***/ public String corruptObjectMissingEmail; /***/ public String corruptObjectNameContainsByte; /***/ public String corruptObjectNameContainsChar; /***/ public String corruptObjectNameContainsNullByte; @@ -237,7 +261,9 @@ /***/ public String corruptObjectTruncatedInMode; /***/ public String corruptObjectTruncatedInName; /***/ public String corruptObjectTruncatedInObjectId; + /***/ public String corruptObjectZeroId; /***/ public String corruptPack; + /***/ public String corruptUseCnt; /***/ public String couldNotCheckOutBecauseOfConflicts; /***/ public String couldNotDeleteLockFileShouldNotHappen; /***/ public String couldNotDeleteTemporaryIndexFileShouldNotHappen; @@ -256,12 +282,14 @@ /***/ public String createBranchFailedUnknownReason; /***/ public String createBranchUnexpectedResult; /***/ public String createNewFileFailed; + /***/ public String createRequiresZeroOldId; /***/ public String credentialPassword; /***/ public String credentialUsername; /***/ public String daemonAlreadyRunning; /***/ public String daysAgo; /***/ public String deleteBranchUnexpectedResult; /***/ public String deleteFileFailed; + /***/ public String deleteRequiresZeroNewId; /***/ public String deleteTagUnexpectedResult; /***/ public String deletingNotSupported; /***/ public String destinationIsNotAWildcard; @@ -287,8 +315,10 @@ /***/ public String emptyPathNotPermitted; /***/ public String emptyRef; /***/ public String encryptionError; + /***/ public String encryptionOnlyPBE; /***/ public String endOfFileInEscape; /***/ public String entryNotFoundByPath; + /***/ public String enumValueNotSupported0; /***/ public String enumValueNotSupported2; /***/ public String enumValueNotSupported3; /***/ public String enumValuesNotAvailable; @@ -304,6 +334,7 @@ /***/ public String exceptionCaughtDuringExecutionOfAddCommand; /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; /***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand; + /***/ public String exceptionCaughtDuringExecutionOfCommand; /***/ public String exceptionCaughtDuringExecutionOfCommitCommand; /***/ public String exceptionCaughtDuringExecutionOfFetchCommand; /***/ public String exceptionCaughtDuringExecutionOfLsRemoteCommand; @@ -314,7 +345,6 @@ /***/ public String exceptionCaughtDuringExecutionOfRevertCommand; /***/ public String exceptionCaughtDuringExecutionOfRmCommand; /***/ public String exceptionCaughtDuringExecutionOfTagCommand; - /***/ public String exceptionCaughtDuringExcecutionOfCommand; /***/ public String exceptionHookExecutionInterrupted; /***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand; /***/ public String exceptionOccurredDuringReadingOfGIT_DIR; @@ -323,12 +353,15 @@ /***/ public String expectedACKNAKGot; /***/ public String expectedBooleanStringValue; /***/ public String expectedCharacterEncodingGuesses; + /***/ public String expectedDirectoryNotSubmodule; /***/ public String expectedEOFReceived; /***/ public String expectedGot; /***/ public String expectedLessThanGot; /***/ public String expectedPktLineWithService; /***/ public String expectedReceivedContentType; /***/ public String expectedReportForRefNotReceived; + /***/ public String failedAtomicFileCreation; + /***/ public String failedToDetermineFilterDefinition; /***/ public String failedUpdatingRefs; /***/ public String failureDueToOneOfTheFollowing; /***/ public String failureUpdatingFETCH_HEAD; @@ -337,21 +370,28 @@ /***/ public String fileIsTooBigForThisConvenienceMethod; /***/ public String fileIsTooLarge; /***/ public String fileModeNotSetForPath; + /***/ public String filterExecutionFailed; + /***/ public String filterExecutionFailedRc; /***/ public String findingGarbage; /***/ public String flagIsDisposed; /***/ public String flagNotFromThis; /***/ public String flagsAlreadyCreated; /***/ public String funnyRefname; /***/ public String gcFailed; + /***/ public String gcLogExists; + /***/ public String gcTooManyUnpruned; /***/ public String gitmodulesNotFound; /***/ public String headRequiredToStash; /***/ public String hoursAgo; + /***/ public String httpConfigCannotNormalizeURL; + /***/ public String httpConfigInvalidURL; /***/ public String hugeIndexesAreNotSupportedByJgitYet; /***/ public String hunkBelongsToAnotherFile; /***/ public String hunkDisconnectedFromFile; /***/ public String hunkHeaderDoesNotMatchBodyLineCountOf; /***/ public String illegalArgumentNotA; /***/ public String illegalCombinationOfArguments; + /***/ public String illegalHookName; /***/ public String illegalPackingPhase; /***/ public String illegalStateExists; /***/ public String improperlyPaddedBase64Input; @@ -363,6 +403,8 @@ /***/ public String indexSignatureIsInvalid; /***/ public String indexWriteException; /***/ public String initFailedBareRepoDifferentDirs; + /***/ public String initFailedDirIsNoDirectory; + /***/ public String initFailedGitDirIsNoDirectory; /***/ public String initFailedNonBareRepoSameDirs; /***/ public String inMemoryBufferLimitExceeded; /***/ public String inputDidntMatchLength; @@ -378,8 +420,11 @@ /***/ public String invalidChannel; /***/ public String invalidCharacterInBase64Data; /***/ public String invalidCommitParentNumber; + /***/ public String invalidDepth; /***/ public String invalidEncryption; + /***/ public String invalidExpandWildcard; /***/ public String invalidGitdirRef; + /***/ public String invalidGitModules; /***/ public String invalidGitType; /***/ public String invalidId; /***/ public String invalidId0; @@ -389,8 +434,10 @@ /***/ public String invalidIntegerValue; /***/ public String invalidKey; /***/ public String invalidLineInConfigFile; + /***/ public String invalidLineInConfigFileWithParam; /***/ public String invalidModeFor; /***/ public String invalidModeForPath; + /***/ public String invalidNameContainsDotDot; /***/ public String invalidObject; /***/ public String invalidOldIdSent; /***/ public String invalidPacketLineHeader; @@ -399,16 +446,25 @@ /***/ public String invalidPathPeriodAtEndWindows; /***/ public String invalidPathSpaceAtEndWindows; /***/ public String invalidPathReservedOnWindows; + /***/ public String invalidRedirectLocation; /***/ public String invalidReflogRevision; /***/ public String invalidRefName; + /***/ public String invalidReftableBlock; + /***/ public String invalidReftableCRC; + /***/ public String invalidReftableFile; /***/ public String invalidRemote; /***/ public String invalidShallowObject; /***/ public String invalidStageForPath; + /***/ public String invalidSystemProperty; /***/ public String invalidTagOption; /***/ public String invalidTimeout; + /***/ public String invalidTimeUnitValue2; + /***/ public String invalidTimeUnitValue3; + /***/ public String invalidTreeZeroLengthName; /***/ public String invalidURL; /***/ public String invalidWildcards; /***/ public String invalidRefSpec; + /***/ public String invalidRepositoryStateNoHead; /***/ public String invalidWindowSize; /***/ public String isAStaticFlagAndHasNorevWalkInstance; /***/ public String JRELacksMD5Implementation; @@ -418,6 +474,7 @@ /***/ public String largeObjectException; /***/ public String largeObjectOutOfMemory; /***/ public String lengthExceedsMaximumArraySize; + /***/ public String lfsHookConflict; /***/ public String listingAlternates; /***/ public String listingPacks; /***/ public String localObjectsIncomplete; @@ -439,8 +496,11 @@ /***/ public String mergeRecursiveTooManyMergeBasesFor; /***/ public String messageAndTaggerNotAllowedInUnannotatedTags; /***/ public String minutesAgo; + /***/ public String mismatchOffset; + /***/ public String mismatchCRC; /***/ public String missingAccesskey; /***/ public String missingConfigurationForKey; + /***/ public String missingCRC; /***/ public String missingDeltaBase; /***/ public String missingForwardImageInGITBinaryPatch; /***/ public String missingObject; @@ -454,10 +514,12 @@ /***/ public String months; /***/ public String monthsAgo; /***/ public String multipleMergeBasesFor; + /***/ public String nameMustNotBeNullOrEmpty; /***/ public String need2Arguments; /***/ public String needPackOut; /***/ public String needsAtLeastOneEntry; /***/ public String needsWorkdir; + /***/ public String newIdMustNotBeNull; /***/ public String newlineInQuotesNotAllowed; /***/ public String noApplyInDelete; /***/ public String noClosingBracket; @@ -466,7 +528,10 @@ /***/ public String noHMACsupport; /***/ public String noMergeBase; /***/ public String noMergeHeadSpecified; + /***/ public String nonBareLinkFilesNotSupported; + /***/ public String noPathAttributesFound; /***/ public String noSuchRef; + /***/ public String noSuchSubmodule; /***/ public String notABoolean; /***/ public String notABundle; /***/ public String notADIRCFile; @@ -484,11 +549,13 @@ /***/ public String objectAtHasBadZlibStream; /***/ public String objectAtPathDoesNotHaveId; /***/ public String objectIsCorrupt; + /***/ public String objectIsCorrupt3; /***/ public String objectIsNotA; /***/ public String objectNotFound; /***/ public String objectNotFoundIn; /***/ public String obtainingCommitsForCherryPick; /***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack; + /***/ public String oldIdMustNotBeNull; /***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable; /***/ public String onlyOneFetchSupported; /***/ public String onlyOneOperationCallPerConnectionIsSupported; @@ -496,18 +563,21 @@ /***/ public String openingConnection; /***/ public String operationCanceled; /***/ public String outputHasAlreadyBeenStarted; + /***/ public String overflowedReftableBlock; /***/ public String packChecksumMismatch; /***/ public String packCorruptedWhileWritingToFilesystem; /***/ public String packDoesNotMatchIndex; /***/ public String packedRefsHandleIsStale; /***/ public String packetSizeMustBeAtLeast; /***/ public String packetSizeMustBeAtMost; + /***/ public String packedRefsCorruptionDetected; /***/ public String packfileCorruptionDetected; /***/ public String packFileInvalid; /***/ public String packfileIsTruncated; /***/ public String packfileIsTruncatedNoParam; /***/ public String packHandleIsStale; /***/ public String packHasUnresolvedDeltas; + /***/ public String packInaccessible; /***/ public String packingCancelledDuringObjectsWriting; /***/ public String packObjectCountMismatch; /***/ public String packRefs; @@ -521,6 +591,7 @@ /***/ public String pathIsNotInWorkingDir; /***/ public String pathNotConfigured; /***/ public String peeledLineBeforeRef; + /***/ public String peeledRefIsRequired; /***/ public String peerDidNotSupplyACompleteObjectGraph; /***/ public String personIdentEmailNonNull; /***/ public String personIdentNameNonNull; @@ -539,7 +610,9 @@ /***/ public String pushCertificateInvalidSignature; /***/ public String pushIsNotSupportedForBundleTransport; /***/ public String pushNotPermitted; + /***/ public String pushOptionsNotSupported; /***/ public String rawLogMessageDoesNotParseAsLogEntry; + /***/ public String readerIsRequired; /***/ public String readingObjectsFromLocalRepositoryFailed; /***/ public String readTimedOut; /***/ public String receivePackObjectTooLarge1; @@ -547,6 +620,11 @@ /***/ public String receivePackInvalidLimit; /***/ public String receivePackTooLarge; /***/ public String receivingObjects; + /***/ public String redirectBlocked; + /***/ public String redirectHttp; + /***/ public String redirectLimitExceeded; + /***/ public String redirectLocationMissing; + /***/ public String redirectsOff; /***/ public String refAlreadyExists; /***/ public String refAlreadyExists1; /***/ public String reflogEntryNotFound; @@ -595,7 +673,9 @@ /***/ public String sequenceTooLargeForDiffAlgorithm; /***/ public String serviceNotEnabledNoName; /***/ public String serviceNotPermitted; + /***/ public String sha1CollisionDetected1; /***/ public String shallowCommitsAlreadyInitialized; + /***/ public String shallowPacksRequireDepthWalk; /***/ public String shortCompressedStreamAt; /***/ public String shortReadOfBlock; /***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes; @@ -610,6 +690,15 @@ /***/ public String sourceRefDoesntResolveToAnyObject; /***/ public String sourceRefNotSpecifiedForRefspec; /***/ public String squashCommitNotUpdatingHEAD; + /***/ public String sshUserNameError; + /***/ public String sslFailureExceptionMessage; + /***/ public String sslFailureInfo; + /***/ public String sslFailureCause; + /***/ public String sslFailureTrustExplanation; + /***/ public String sslTrustAlways; + /***/ public String sslTrustForRepo; + /***/ public String sslTrustNow; + /***/ public String sslVerifyCannotSave; /***/ public String staleRevFlagsOn; /***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported; /***/ public String stashApplyConflict; @@ -621,6 +710,7 @@ /***/ public String stashDropDeleteRefFailed; /***/ public String stashDropFailed; /***/ public String stashDropMissingReflog; + /***/ public String stashDropNotSupported; /***/ public String stashFailed; /***/ public String stashResolveFailed; /***/ public String statelessRPCRequiresOptionToBeEnabled; @@ -628,8 +718,11 @@ /***/ public String storePushCertOneRef; /***/ public String storePushCertReflog; /***/ public String submoduleExists; - /***/ public String submodulesNotSupported; + /***/ public String submoduleNameInvalid; /***/ public String submoduleParentRemoteUrlInvalid; + /***/ public String submodulePathInvalid; + /***/ public String submodulesNotSupported; + /***/ public String submoduleUrlInvalid; /***/ public String supportOnlyPackIndexVersion2; /***/ public String symlinkCannotBeWrittenAsTheLinkTarget; /***/ public String systemConfigFileInvalid; @@ -638,7 +731,11 @@ /***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported; /***/ public String transactionAborted; /***/ public String theFactoryMustNotBeNull; + /***/ public String threadInterruptedWhileRunning; + /***/ public String timeIsUncertain; /***/ public String timerAlreadyTerminated; + /***/ public String tooManyCommands; + /***/ public String tooManyIncludeRecursions; /***/ public String topologicalSortRequired; /***/ public String transportExceptionBadRef; /***/ public String transportExceptionEmptyRef; @@ -655,6 +752,7 @@ /***/ public String transportProtoSFTP; /***/ public String transportProtoSSH; /***/ public String transportProtoTest; + /***/ public String transportProvidedRefWithNoObjectId; /***/ public String transportSSHRetryInterrupt; /***/ public String treeEntryAlreadyExists; /***/ public String treeFilterMarkerTooManyFilters; @@ -666,28 +764,35 @@ /***/ public String tSizeMustBeGreaterOrEqual1; /***/ public String unableToCheckConnectivity; /***/ public String unableToCreateNewObject; + /***/ public String unableToReadPackfile; + /***/ public String unableToRemovePath; /***/ public String unableToStore; /***/ public String unableToWrite; /***/ public String unauthorized; + /***/ public String underflowedReftableBlock; /***/ public String unencodeableFile; /***/ public String unexpectedCompareResult; /***/ public String unexpectedEndOfConfigFile; /***/ public String unexpectedEndOfInput; + /***/ public String unexpectedEofInPack; /***/ public String unexpectedHunkTrailer; /***/ public String unexpectedOddResult; /***/ public String unexpectedRefReport; /***/ public String unexpectedReportLine; /***/ public String unexpectedReportLine2; + /***/ public String unexpectedSubmoduleStatus; /***/ public String unknownOrUnsupportedCommand; /***/ public String unknownDIRCVersion; /***/ public String unknownHost; /***/ public String unknownIndexVersionOrCorruptIndex; /***/ public String unknownObject; + /***/ public String unknownObjectInIndex; /***/ public String unknownObjectType; /***/ public String unknownObjectType2; /***/ public String unknownRepositoryFormat; /***/ public String unknownRepositoryFormat2; /***/ public String unknownZlibError; + /***/ public String unlockLockFileFailed; /***/ public String unmergedPath; /***/ public String unmergedPaths; /***/ public String unpackException; @@ -704,6 +809,9 @@ /***/ public String unsupportedOperationNotAddAtEnd; /***/ public String unsupportedPackIndexVersion; /***/ public String unsupportedPackVersion; + /***/ public String unsupportedReftableVersion; + /***/ public String unsupportedRepositoryDescription; + /***/ public String updateRequiresOldIdAndNewId; /***/ public String updatingHeadFailed; /***/ public String updatingReferences; /***/ public String updatingRefFailed; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.jgit.internal.ketch.KetchConstants.TERM; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.time.ProposedTimestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The initial {@link Round} for a leaderless repository, used to establish a + * leader. + */ +class ElectionRound extends Round { + private static final Logger log = LoggerFactory.getLogger(ElectionRound.class); + + private long term; + + ElectionRound(KetchLeader leader, LogIndex head) { + super(leader, head); + } + + @Override + void start() throws IOException { + ObjectId id; + try (Repository git = leader.openRepository(); + ProposedTimestamp ts = getSystem().getClock().propose(); + ObjectInserter inserter = git.newObjectInserter()) { + id = bumpTerm(git, ts, inserter); + inserter.flush(); + blockUntil(ts); + } + runAsync(id); + } + + @Override + void success() { + // Do nothing upon election, KetchLeader will copy the term. + } + + long getTerm() { + return term; + } + + private ObjectId bumpTerm(Repository git, ProposedTimestamp ts, + ObjectInserter inserter) throws IOException { + CommitBuilder b = new CommitBuilder(); + if (!ObjectId.zeroId().equals(acceptedOldIndex)) { + try (RevWalk rw = new RevWalk(git)) { + RevCommit c = rw.parseCommit(acceptedOldIndex); + if (getSystem().requireMonotonicLeaderElections()) { + if (ts.read(SECONDS) < c.getCommitTime()) { + throw new TimeIsUncertainException(); + } + } + b.setTreeId(c.getTree()); + b.setParentId(acceptedOldIndex); + term = parseTerm(c.getFooterLines(TERM)) + 1; + } + } else { + term = 1; + b.setTreeId(inserter.insert(new TreeFormatter())); + } + + StringBuilder msg = new StringBuilder(); + msg.append(KetchConstants.TERM.getName()) + .append(": ") //$NON-NLS-1$ + .append(term); + + String tag = leader.getSystem().newLeaderTag(); + if (tag != null && !tag.isEmpty()) { + msg.append(' ').append(tag); + } + + b.setAuthor(leader.getSystem().newCommitter(ts)); + b.setCommitter(b.getAuthor()); + b.setMessage(msg.toString()); + + if (log.isDebugEnabled()) { + log.debug("Trying to elect myself " + b.getMessage()); //$NON-NLS-1$ + } + return inserter.insert(b); + } + + private static long parseTerm(List footer) { + if (footer.isEmpty()) { + return 0; + } + + String s = footer.get(0); + int p = s.indexOf(' '); + if (p > 0) { + s = s.substring(0, p); + } + return Long.parseLong(s, 10); + } + + private void blockUntil(ProposedTimestamp ts) throws IOException { + try { + ts.blockUntil(getSystem().getMaxWaitForMonotonicClock()); + } catch (InterruptedException | TimeoutException e) { + throw new TimeIsUncertainException(e); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import org.eclipse.jgit.revwalk.FooterKey; + +/** + * Frequently used constants in a Ketch system. + */ +public class KetchConstants { + /** + * Default reference namespace holding {@link #ACCEPTED} and + * {@link #COMMITTED} references and the {@link #STAGE} sub-namespace. + */ + public static final String DEFAULT_TXN_NAMESPACE = "refs/txn/"; //$NON-NLS-1$ + + /** Reference name holding the RefTree accepted by a follower. */ + public static final String ACCEPTED = "accepted"; //$NON-NLS-1$ + + /** Reference name holding the RefTree known to be committed. */ + public static final String COMMITTED = "committed"; //$NON-NLS-1$ + + /** Reference subdirectory holding proposed heads. */ + public static final String STAGE = "stage/"; //$NON-NLS-1$ + + /** Footer containing the current term. */ + public static final FooterKey TERM = new FooterKey("Term"); //$NON-NLS-1$ + + /** Section for Ketch configuration ({@code ketch}). */ + public static final String CONFIG_SECTION_KETCH = "ketch"; //$NON-NLS-1$ + + /** Behavior for a replica ({@code remote.$name.ketch-type}) */ + public static final String CONFIG_KEY_TYPE = "ketch-type"; //$NON-NLS-1$ + + /** Behavior for a replica ({@code remote.$name.ketch-commit}) */ + public static final String CONFIG_KEY_COMMIT = "ketch-commit"; //$NON-NLS-1$ + + /** Behavior for a replica ({@code remote.$name.ketch-speed}) */ + public static final String CONFIG_KEY_SPEED = "ketch-speed"; //$NON-NLS-1$ + + private KetchConstants() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.net.URISyntaxException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepository; +import org.eclipse.jgit.lib.Repository; + +/** + * A cache of live leader instances, keyed by repository. + *

+ * Ketch only assigns a leader to a repository when needed. If + * {@link #get(Repository)} is called for a repository that does not have a + * leader, the leader is created and added to the cache. + */ +public class KetchLeaderCache { + private final KetchSystem system; + private final ConcurrentMap leaders; + private final Lock startLock; + + /** + * Initialize a new leader cache. + * + * @param system + * system configuration for the leaders + */ + public KetchLeaderCache(KetchSystem system) { + this.system = system; + leaders = new ConcurrentHashMap<>(); + startLock = new ReentrantLock(true /* fair */); + } + + /** + * Lookup the leader instance for a given repository. + * + * @param repo + * repository to get the leader for. + * @return the leader instance for the repository. + * @throws java.net.URISyntaxException + * remote configuration contains an invalid URL. + */ + public KetchLeader get(Repository repo) + throws URISyntaxException { + String key = computeKey(repo); + KetchLeader leader = leaders.get(key); + if (leader != null) { + return leader; + } + return startLeader(key, repo); + } + + private KetchLeader startLeader(String key, Repository repo) + throws URISyntaxException { + startLock.lock(); + try { + KetchLeader leader = leaders.get(key); + if (leader != null) { + return leader; + } + leader = system.createLeader(repo); + leaders.put(key, leader); + return leader; + } finally { + startLock.unlock(); + } + } + + private static String computeKey(Repository repo) { + if (repo instanceof DfsRepository) { + DfsRepository dfs = (DfsRepository) repo; + return dfs.getDescription().getRepositoryName(); + } + + if (repo.getDirectory() != null) { + return repo.getDirectory().toURI().toString(); + } + + throw new IllegalArgumentException(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchLeader.State.CANDIDATE; +import static org.eclipse.jgit.internal.ketch.KetchLeader.State.LEADER; +import static org.eclipse.jgit.internal.ketch.KetchLeader.State.SHUTDOWN; +import static org.eclipse.jgit.internal.ketch.KetchReplica.Participation.FOLLOWER_ONLY; +import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A leader managing consensus across remote followers. + *

+ * A leader instance starts up in + * {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#CANDIDATE} and tries + * to begin a new term by sending an + * {@link org.eclipse.jgit.internal.ketch.ElectionRound} to all replicas. Its + * term starts if a majority of replicas have accepted this leader instance for + * the term. + *

+ * Once elected by a majority the instance enters + * {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#LEADER} and runs + * proposals offered to {@link #queueProposal(Proposal)}. This continues until + * the leader is timed out for inactivity, or is deposed by a competing leader + * gaining its own majority. + *

+ * Once timed out or deposed this {@code KetchLeader} instance should be + * discarded, and a new instance takes over. + *

+ * Each leader instance coordinates a group of + * {@link org.eclipse.jgit.internal.ketch.KetchReplica}s. Replica instances are + * owned by the leader instance and must be discarded when the leader is + * discarded. + *

+ * In Ketch all push requests are issued through the leader. The steps are as + * follows (see {@link org.eclipse.jgit.internal.ketch.KetchPreReceive} for an + * example): + *

+ *

+ * The leader gains consensus by first pushing the needed objects and a + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree} representing the + * desired target repository state to the {@code refs/txn/accepted} branch on + * each of the replicas. Once a majority has succeeded, the leader commits the + * state by either pushing the {@code refs/txn/accepted} value to + * {@code refs/txn/committed} (for Ketch-aware replicas) or by pushing updates + * to {@code refs/heads/master}, etc. for stock Git replicas. + *

+ * Internally, the actual transport to replicas is performed on background + * threads via the {@link org.eclipse.jgit.internal.ketch.KetchSystem}'s + * executor service. For performance, the + * {@link org.eclipse.jgit.internal.ketch.KetchLeader}, + * {@link org.eclipse.jgit.internal.ketch.KetchReplica} and + * {@link org.eclipse.jgit.internal.ketch.Proposal} objects share some state, + * and may invoke each other's methods on different threads. This access is + * protected by the leader's {@link #lock} object. Care must be taken to prevent + * concurrent access by correctly obtaining the leader's lock. + */ +public abstract class KetchLeader { + private static final Logger log = LoggerFactory.getLogger(KetchLeader.class); + + /** Current state of the leader instance. */ + public static enum State { + /** Newly created instance trying to elect itself leader. */ + CANDIDATE, + + /** Leader instance elected by a majority. */ + LEADER, + + /** Instance has been deposed by another with a more recent term. */ + DEPOSED, + + /** Leader has been gracefully shutdown, e.g. due to inactivity. */ + SHUTDOWN; + } + + private final KetchSystem system; + + /** Leader's knowledge of replicas for this repository. */ + private KetchReplica[] voters; + private KetchReplica[] followers; + private LocalReplica self; + + /** + * Lock protecting all data within this leader instance. + *

+ * This lock extends into the {@link KetchReplica} instances used by the + * leader. They share the same lock instance to simplify concurrency. + */ + final Lock lock; + + private State state = CANDIDATE; + + /** Term of this leader, once elected. */ + private long term; + + /** + * Pending proposals accepted into the queue in FIFO order. + *

+ * These proposals were preflighted and do not contain any conflicts with + * each other and their expectations matched the leader's local view of the + * agreed upon {@code refs/txn/accepted} tree. + */ + private final List queued; + + /** + * State of the repository's RefTree after applying all entries in + * {@link #queued}. New proposals must be consistent with this tree to be + * appended to the end of {@link #queued}. + *

+ * Must be deep-copied with {@link RefTree#copy()} if + * {@link #roundHoldsReferenceToRefTree} is {@code true}. + */ + private RefTree refTree; + + /** + * If {@code true} {@link #refTree} must be duplicated before queuing the + * next proposal. The {@link #refTree} was passed into the constructor of a + * {@link ProposalRound}, and that external reference to the {@link RefTree} + * object is held by the proposal until it materializes the tree object in + * the object store. This field is set {@code true} when the proposal begins + * execution and set {@code false} once tree objects are persisted in the + * local repository's object store or {@link #refTree} is replaced with a + * copy to isolate it from any running rounds. + *

+ * If proposals arrive less frequently than the {@code RefTree} is written + * out to the repository the {@link #roundHoldsReferenceToRefTree} behavior + * avoids duplicating {@link #refTree}, reducing both time and memory used. + * However if proposals arrive more frequently {@link #refTree} must be + * duplicated to prevent newly queued proposals from corrupting the + * {@link #runningRound}. + */ + volatile boolean roundHoldsReferenceToRefTree; + + /** End of the leader's log. */ + private LogIndex headIndex; + + /** Leader knows this (and all prior) states are committed. */ + private LogIndex committedIndex; + + /** + * Is the leader idle with no work pending? If {@code true} there is no work + * for the leader (normal state). This field is {@code false} when the + * leader thread is scheduled for execution, or while {@link #runningRound} + * defines a round in progress. + */ + private boolean idle; + + /** Current round the leader is preparing and waiting for a vote on. */ + private Round runningRound; + + /** + * Construct a leader for a Ketch instance. + * + * @param system + * Ketch system configuration the leader must adhere to. + */ + protected KetchLeader(KetchSystem system) { + this.system = system; + this.lock = new ReentrantLock(true /* fair */); + this.queued = new ArrayList<>(4); + this.idle = true; + } + + /** @return system configuration. */ + KetchSystem getSystem() { + return system; + } + + /** + * Configure the replicas used by this Ketch instance. + *

+ * Replicas should be configured once at creation before any proposals are + * executed. Once elections happen, reconfiguration is a complicated + * concept that is not currently supported. + * + * @param replicas + * members participating with the same repository. + */ + public void setReplicas(Collection replicas) { + List v = new ArrayList<>(5); + List f = new ArrayList<>(5); + for (KetchReplica r : replicas) { + switch (r.getParticipation()) { + case FULL: + v.add(r); + break; + + case FOLLOWER_ONLY: + f.add(r); + break; + } + } + + Collection validVoters = validVoterCounts(); + if (!validVoters.contains(Integer.valueOf(v.size()))) { + throw new IllegalArgumentException(MessageFormat.format( + KetchText.get().unsupportedVoterCount, + Integer.valueOf(v.size()), + validVoters)); + } + + LocalReplica me = findLocal(v); + if (me == null) { + throw new IllegalArgumentException( + KetchText.get().localReplicaRequired); + } + + lock.lock(); + try { + voters = v.toArray(new KetchReplica[v.size()]); + followers = f.toArray(new KetchReplica[f.size()]); + self = me; + } finally { + lock.unlock(); + } + } + + private static Collection validVoterCounts() { + @SuppressWarnings("boxing") + Integer[] valid = { + // An odd number of voting replicas is required. + 1, 3, 5, 7, 9 }; + return Arrays.asList(valid); + } + + private static LocalReplica findLocal(Collection voters) { + for (KetchReplica r : voters) { + if (r instanceof LocalReplica) { + return (LocalReplica) r; + } + } + return null; + } + + /** + * Get an instance of the repository for use by a leader thread. + *

+ * The caller will close the repository. + * + * @return opened repository for use by the leader thread. + * @throws java.io.IOException + * cannot reopen the repository for the leader. + */ + protected abstract Repository openRepository() throws IOException; + + /** + * Queue a reference update proposal for consensus. + *

+ * This method does not wait for consensus to be reached. The proposal is + * checked to look for risks of conflicts, and then submitted into the queue + * for distribution as soon as possible. + *

+ * Callers must use {@link org.eclipse.jgit.internal.ketch.Proposal#await()} + * to see if the proposal is done. + * + * @param proposal + * the proposed reference updates to queue for consideration. + * Once execution is complete the individual reference result + * fields will be populated with the outcome. + * @throws java.lang.InterruptedException + * current thread was interrupted. The proposal may have been + * aborted if it was not yet queued for execution. + * @throws java.io.IOException + * unrecoverable error preventing proposals from being attempted + * by this leader. + */ + public void queueProposal(Proposal proposal) + throws InterruptedException, IOException { + try { + lock.lockInterruptibly(); + } catch (InterruptedException e) { + proposal.abort(); + throw e; + } + try { + if (refTree == null) { + initialize(); + for (Proposal p : queued) { + refTree.apply(p.getCommands()); + } + } else if (roundHoldsReferenceToRefTree) { + refTree = refTree.copy(); + roundHoldsReferenceToRefTree = false; + } + + if (!refTree.apply(proposal.getCommands())) { + // A conflict exists so abort the proposal. + proposal.abort(); + return; + } + + queued.add(proposal); + proposal.notifyState(QUEUED); + + if (idle) { + scheduleLeader(); + } + } finally { + lock.unlock(); + } + } + + private void initialize() throws IOException { + try (Repository git = openRepository(); RevWalk rw = new RevWalk(git)) { + self.initialize(git); + + ObjectId accepted = self.getTxnAccepted(); + if (!ObjectId.zeroId().equals(accepted)) { + RevCommit c = rw.parseCommit(accepted); + headIndex = LogIndex.unknown(accepted); + refTree = RefTree.read(rw.getObjectReader(), c.getTree()); + } else { + headIndex = LogIndex.unknown(ObjectId.zeroId()); + refTree = RefTree.newEmptyTree(); + } + } + } + + private void scheduleLeader() { + idle = false; + system.getExecutor().execute(new Runnable() { + @Override + public void run() { + runLeader(); + } + }); + } + + private void runLeader() { + Round round; + lock.lock(); + try { + switch (state) { + case CANDIDATE: + round = new ElectionRound(this, headIndex); + break; + + case LEADER: + round = newProposalRound(); + break; + + case DEPOSED: + case SHUTDOWN: + default: + log.warn("Leader cannot run {}", state); //$NON-NLS-1$ + // TODO(sop): Redirect proposals. + return; + } + } finally { + lock.unlock(); + } + + try { + round.start(); + } catch (IOException e) { + // TODO(sop) Depose leader if it cannot use its repository. + log.error(KetchText.get().leaderFailedToStore, e); + lock.lock(); + try { + nextRound(); + } finally { + lock.unlock(); + } + } + } + + private ProposalRound newProposalRound() { + List todo = new ArrayList<>(queued); + queued.clear(); + roundHoldsReferenceToRefTree = true; + return new ProposalRound(this, headIndex, todo, refTree); + } + + /** @return term of this leader's reign. */ + long getTerm() { + return term; + } + + /** @return end of the leader's log. */ + LogIndex getHead() { + return headIndex; + } + + /** + * @return state leader knows it has committed across a quorum of replicas. + */ + LogIndex getCommitted() { + return committedIndex; + } + + boolean isIdle() { + return idle; + } + + void runAsync(Round round) { + lock.lock(); + try { + // End of the log is this round. Once transport begins it is + // reasonable to assume at least one replica will eventually get + // this, and there is reasonable probability it commits. + headIndex = round.acceptedNewIndex; + runningRound = round; + + for (KetchReplica replica : voters) { + replica.pushTxnAcceptedAsync(round); + } + for (KetchReplica replica : followers) { + replica.pushTxnAcceptedAsync(round); + } + } finally { + lock.unlock(); + } + } + + /** + * Asynchronous signal from a replica after completion. + *

+ * Must be called while {@link #lock} is held by the replica. + * + * @param replica + * replica posting a completion event. + */ + void onReplicaUpdate(KetchReplica replica) { + if (log.isDebugEnabled()) { + log.debug("Replica {} finished:\n{}", //$NON-NLS-1$ + replica.describeForLog(), snapshot()); + } + + if (replica.getParticipation() == FOLLOWER_ONLY) { + // Followers cannot vote, so votes haven't changed. + return; + } else if (runningRound == null) { + // No round running, no need to tally votes. + return; + } + + assert headIndex.equals(runningRound.acceptedNewIndex); + int matching = 0; + for (KetchReplica r : voters) { + if (r.hasAccepted(headIndex)) { + matching++; + } + } + + int quorum = voters.length / 2 + 1; + boolean success = matching >= quorum; + if (!success) { + return; + } + + switch (state) { + case CANDIDATE: + term = ((ElectionRound) runningRound).getTerm(); + state = LEADER; + if (log.isDebugEnabled()) { + log.debug("Won election, running term " + term); //$NON-NLS-1$ + } + + //$FALL-THROUGH$ + case LEADER: + committedIndex = headIndex; + if (log.isDebugEnabled()) { + log.debug("Committed {} in term {}", //$NON-NLS-1$ + committedIndex.describeForLog(), + Long.valueOf(term)); + } + nextRound(); + commitAsync(replica); + notifySuccess(runningRound); + if (log.isDebugEnabled()) { + log.debug("Leader state:\n{}", snapshot()); //$NON-NLS-1$ + } + break; + + default: + log.debug("Leader ignoring replica while in {}", state); //$NON-NLS-1$ + break; + } + } + + private void notifySuccess(Round round) { + // Drop the leader lock while notifying Proposal listeners. + lock.unlock(); + try { + round.success(); + } finally { + lock.lock(); + } + } + + private void commitAsync(KetchReplica caller) { + for (KetchReplica r : voters) { + if (r == caller) { + continue; + } + if (r.shouldPushUnbatchedCommit(committedIndex, isIdle())) { + r.pushCommitAsync(committedIndex); + } + } + for (KetchReplica r : followers) { + if (r == caller) { + continue; + } + if (r.shouldPushUnbatchedCommit(committedIndex, isIdle())) { + r.pushCommitAsync(committedIndex); + } + } + } + + /** Schedule the next round; invoked while {@link #lock} is held. */ + void nextRound() { + runningRound = null; + + if (queued.isEmpty()) { + idle = true; + } else { + // Caller holds lock. Reschedule leader on a new thread so + // the call stack can unwind and lock is not held unexpectedly + // during prepare for the next round. + scheduleLeader(); + } + } + + /** + * Snapshot this leader + * + * @return snapshot of this leader + */ + public LeaderSnapshot snapshot() { + lock.lock(); + try { + LeaderSnapshot s = new LeaderSnapshot(); + s.state = state; + s.term = term; + s.headIndex = headIndex; + s.committedIndex = committedIndex; + s.idle = isIdle(); + for (KetchReplica r : voters) { + s.replicas.add(r.snapshot()); + } + for (KetchReplica r : followers) { + s.replicas.add(r.snapshot()); + } + return s; + } finally { + lock.unlock(); + } + } + + /** + * Gracefully shutdown this leader and cancel outstanding operations. + */ + public void shutdown() { + lock.lock(); + try { + if (state != SHUTDOWN) { + state = SHUTDOWN; + for (KetchReplica r : voters) { + r.shutdown(); + } + for (KetchReplica r : followers) { + r.shutdown(); + } + } + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return snapshot().toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.jgit.internal.ketch.Proposal.State.EXECUTED; +import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.transport.PreReceiveHook; +import org.eclipse.jgit.transport.ProgressSpinner; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.ReceivePack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PreReceiveHook for handling push traffic in a Ketch system. + *

+ * Install an instance on {@link org.eclipse.jgit.transport.ReceivePack} to + * capture the commands and other connection state and relay them through the + * {@link org.eclipse.jgit.internal.ketch.KetchLeader}, allowing the leader to + * gain consensus about the new reference state. + */ +public class KetchPreReceive implements PreReceiveHook { + private static final Logger log = LoggerFactory.getLogger(KetchPreReceive.class); + + private final KetchLeader leader; + + /** + * Construct a hook executing updates through a + * {@link org.eclipse.jgit.internal.ketch.KetchLeader}. + * + * @param leader + * leader for this repository. + */ + public KetchPreReceive(KetchLeader leader) { + this.leader = leader; + } + + /** {@inheritDoc} */ + @Override + public void onPreReceive(ReceivePack rp, Collection cmds) { + cmds = ReceiveCommand.filter(cmds, NOT_ATTEMPTED); + if (cmds.isEmpty()) { + return; + } + + try { + Proposal proposal = new Proposal(rp.getRevWalk(), cmds) + .setPushCertificate(rp.getPushCertificate()) + .setAuthor(rp.getRefLogIdent()) + .setMessage("push"); //$NON-NLS-1$ + leader.queueProposal(proposal); + if (proposal.isDone()) { + // This failed fast, e.g. conflict or bad precondition. + return; + } + + ProgressSpinner spinner = new ProgressSpinner( + rp.getMessageOutputStream()); + if (proposal.getState() == QUEUED) { + waitForQueue(proposal, spinner); + } + if (!proposal.isDone()) { + waitForPropose(proposal, spinner); + } + } catch (IOException | InterruptedException e) { + String msg = JGitText.get().transactionAborted; + for (ReceiveCommand cmd : cmds) { + if (cmd.getResult() == NOT_ATTEMPTED) { + cmd.setResult(REJECTED_OTHER_REASON, msg); + } + } + log.error(msg, e); + } + } + + private void waitForQueue(Proposal proposal, ProgressSpinner spinner) + throws InterruptedException { + spinner.beginTask(KetchText.get().waitingForQueue, 1, SECONDS); + while (!proposal.awaitStateChange(QUEUED, 250, MILLISECONDS)) { + spinner.update(); + } + switch (proposal.getState()) { + case RUNNING: + default: + spinner.endTask(KetchText.get().starting); + break; + + case EXECUTED: + spinner.endTask(KetchText.get().accepted); + break; + + case ABORTED: + spinner.endTask(KetchText.get().failed); + break; + } + } + + private void waitForPropose(Proposal proposal, ProgressSpinner spinner) + throws InterruptedException { + spinner.beginTask(KetchText.get().proposingUpdates, 2, SECONDS); + while (!proposal.await(250, MILLISECONDS)) { + spinner.update(); + } + spinner.endTask(proposal.getState() == EXECUTED + ? KetchText.get().accepted + : KetchText.get().failed); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed.BATCHED; +import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed.FAST; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.CURRENT; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.LAGGING; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.OFFLINE; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.UNKNOWN; +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Ketch replica, either {@link org.eclipse.jgit.internal.ketch.LocalReplica} + * or {@link org.eclipse.jgit.internal.ketch.RemoteGitReplica}. + *

+ * Replicas can be either a stock Git replica, or a Ketch-aware replica. + *

+ * A stock Git replica has no special knowledge of Ketch and simply stores + * objects and references. Ketch communicates with the stock Git replica using + * the Git push wire protocol. The + * {@link org.eclipse.jgit.internal.ketch.KetchLeader} commits an agreed upon + * state by pushing all references to the Git replica, for example + * {@code "refs/heads/master"} is pushed during commit. Stock Git replicas use + * {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#ALL_REFS} to + * record the final state. + *

+ * Ketch-aware replicas understand the {@code RefTree} sent during the proposal + * and during commit are able to update their own reference space to match the + * state represented by the {@code RefTree}. Ketch-aware replicas typically use + * a {@link org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase} and + * {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#TXN_COMMITTED} + * to record the final state. + *

+ * KetchReplica instances are tightly coupled with a single + * {@link org.eclipse.jgit.internal.ketch.KetchLeader}. Some state may be + * accessed by the leader thread and uses the leader's own + * {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} to protect shared + * data. + */ +public abstract class KetchReplica { + static final Logger log = LoggerFactory.getLogger(KetchReplica.class); + private static final byte[] PEEL = { ' ', '^' }; + + /** Participation of a replica in establishing consensus. */ + public enum Participation { + /** Replica can vote. */ + FULL, + + /** Replica does not vote, but tracks leader. */ + FOLLOWER_ONLY; + } + + /** How this replica wants to receive Ketch commit operations. */ + public enum CommitMethod { + /** All references are pushed to the peer as standard Git. */ + ALL_REFS, + + /** Only {@code refs/txn/committed} is written/updated. */ + TXN_COMMITTED; + } + + /** Delay before committing to a replica. */ + public enum CommitSpeed { + /** + * Send the commit immediately, even if it could be batched with the + * next proposal. + */ + FAST, + + /** + * If the next proposal is available, batch the commit with it, + * otherwise just send the commit. This generates less network use, but + * may provide slower consistency on the replica. + */ + BATCHED; + } + + /** Current state of a replica. */ + public enum State { + /** Leader has not yet contacted the replica. */ + UNKNOWN, + + /** Replica is behind the consensus. */ + LAGGING, + + /** Replica matches the consensus. */ + CURRENT, + + /** Replica has a different (or unknown) history. */ + DIVERGENT, + + /** Replica's history contains the leader's history. */ + AHEAD, + + /** Replica can not be contacted. */ + OFFLINE; + } + + private final KetchLeader leader; + private final String replicaName; + private final Participation participation; + private final CommitMethod commitMethod; + private final CommitSpeed commitSpeed; + private final long minRetryMillis; + private final long maxRetryMillis; + private final Map> staged; + private final Map running; + private final Map waiting; + private final List queued; + + /** + * Value known for {@code "refs/txn/accepted"}. + *

+ * Raft literature refers to this as {@code matchIndex}. + */ + private ObjectId txnAccepted; + + /** + * Value known for {@code "refs/txn/committed"}. + *

+ * Raft literature refers to this as {@code commitIndex}. In traditional + * Raft this is a state variable inside the follower implementation, but + * Ketch keeps it in the leader. + */ + private ObjectId txnCommitted; + + /** What is happening with this replica. */ + private State state = UNKNOWN; + private String error; + + /** Scheduled retry due to communication failure. */ + private Future retryFuture; + private long lastRetryMillis; + private long retryAtMillis; + + /** + * Configure a replica representation. + * + * @param leader + * instance this replica follows. + * @param name + * unique-ish name identifying this replica for debugging. + * @param cfg + * how Ketch should treat the replica. + */ + protected KetchReplica(KetchLeader leader, String name, ReplicaConfig cfg) { + this.leader = leader; + this.replicaName = name; + this.participation = cfg.getParticipation(); + this.commitMethod = cfg.getCommitMethod(); + this.commitSpeed = cfg.getCommitSpeed(); + this.minRetryMillis = cfg.getMinRetry(MILLISECONDS); + this.maxRetryMillis = cfg.getMaxRetry(MILLISECONDS); + this.staged = new HashMap<>(); + this.running = new HashMap<>(); + this.waiting = new HashMap<>(); + this.queued = new ArrayList<>(4); + } + + /** + * Get system configuration. + * + * @return system configuration. + */ + public KetchSystem getSystem() { + return getLeader().getSystem(); + } + + /** + * Get leader instance this replica follows. + * + * @return leader instance this replica follows. + */ + public KetchLeader getLeader() { + return leader; + } + + /** + * Get unique-ish name for debugging. + * + * @return unique-ish name for debugging. + */ + public String getName() { + return replicaName; + } + + /** + * Get description of this replica for error/debug logging purposes. + * + * @return description of this replica for error/debug logging purposes. + */ + protected String describeForLog() { + return getName(); + } + + /** + * Get how the replica participates in this Ketch system. + * + * @return how the replica participates in this Ketch system. + */ + public Participation getParticipation() { + return participation; + } + + /** + * Get how Ketch will commit to the repository. + * + * @return how Ketch will commit to the repository. + */ + public CommitMethod getCommitMethod() { + return commitMethod; + } + + /** + * Get when Ketch will commit to the repository. + * + * @return when Ketch will commit to the repository. + */ + public CommitSpeed getCommitSpeed() { + return commitSpeed; + } + + /** + * Called by leader to perform graceful shutdown. + *

+ * Default implementation cancels any scheduled retry. Subclasses may add + * additional logic before or after calling {@code super.shutdown()}. + *

+ * Called with {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} held + * by caller. + */ + protected void shutdown() { + Future f = retryFuture; + if (f != null) { + retryFuture = null; + f.cancel(true); + } + } + + ReplicaSnapshot snapshot() { + ReplicaSnapshot s = new ReplicaSnapshot(this); + s.accepted = txnAccepted; + s.committed = txnCommitted; + s.state = state; + s.error = error; + s.retryAtMillis = waitingForRetry() ? retryAtMillis : 0; + return s; + } + + /** + * Update the leader's view of the replica after a poll. + *

+ * Called with {@link KetchLeader#lock} held by caller. + * + * @param refs + * map of refs from the replica. + */ + void initialize(Map refs) { + if (txnAccepted == null) { + txnAccepted = getId(refs.get(getSystem().getTxnAccepted())); + } + if (txnCommitted == null) { + txnCommitted = getId(refs.get(getSystem().getTxnCommitted())); + } + } + + ObjectId getTxnAccepted() { + return txnAccepted; + } + + boolean hasAccepted(LogIndex id) { + return equals(txnAccepted, id); + } + + private static boolean equals(@Nullable ObjectId a, LogIndex b) { + return a != null && b != null && AnyObjectId.equals(a, b); + } + + /** + * Schedule a proposal round with the replica. + *

+ * Called with {@link KetchLeader#lock} held by caller. + * + * @param round + * current round being run by the leader. + */ + void pushTxnAcceptedAsync(Round round) { + List cmds = new ArrayList<>(); + if (commitSpeed == BATCHED) { + LogIndex committedIndex = leader.getCommitted(); + if (equals(txnAccepted, committedIndex) + && !equals(txnCommitted, committedIndex)) { + prepareTxnCommitted(cmds, committedIndex); + } + } + + // TODO(sop) Lagging replicas should build accept on the fly. + if (round.stageCommands != null) { + for (ReceiveCommand cmd : round.stageCommands) { + // TODO(sop): Do not send certain object graphs to replica. + cmds.add(copy(cmd)); + } + } + cmds.add(new ReceiveCommand( + round.acceptedOldIndex, round.acceptedNewIndex, + getSystem().getTxnAccepted())); + pushAsync(new ReplicaPushRequest(this, cmds)); + } + + private static ReceiveCommand copy(ReceiveCommand c) { + return new ReceiveCommand(c.getOldId(), c.getNewId(), c.getRefName()); + } + + boolean shouldPushUnbatchedCommit(LogIndex committed, boolean leaderIdle) { + return (leaderIdle || commitSpeed == FAST) && hasAccepted(committed); + } + + void pushCommitAsync(LogIndex committed) { + List cmds = new ArrayList<>(); + prepareTxnCommitted(cmds, committed); + pushAsync(new ReplicaPushRequest(this, cmds)); + } + + private void prepareTxnCommitted(List cmds, + ObjectId committed) { + removeStaged(cmds, committed); + cmds.add(new ReceiveCommand( + txnCommitted, committed, + getSystem().getTxnCommitted())); + } + + private void removeStaged(List cmds, ObjectId committed) { + List a = staged.remove(committed); + if (a != null) { + delete(cmds, a); + } + if (staged.isEmpty() || !(committed instanceof LogIndex)) { + return; + } + + LogIndex committedIndex = (LogIndex) committed; + Iterator>> itr = staged + .entrySet().iterator(); + while (itr.hasNext()) { + Map.Entry> e = itr.next(); + if (e.getKey() instanceof LogIndex) { + LogIndex stagedIndex = (LogIndex) e.getKey(); + if (stagedIndex.isBefore(committedIndex)) { + delete(cmds, e.getValue()); + itr.remove(); + } + } + } + } + + private static void delete(List cmds, + List createCmds) { + for (ReceiveCommand cmd : createCmds) { + ObjectId id = cmd.getNewId(); + String name = cmd.getRefName(); + cmds.add(new ReceiveCommand(id, ObjectId.zeroId(), name)); + } + } + + /** + * Determine the next push for this replica (if any) and start it. + *

+ * If the replica has successfully accepted the committed state of the + * leader, this method will push all references to the replica using the + * configured {@link CommitMethod}. + *

+ * If the replica is {@link State#LAGGING} this method will begin catch up + * by sending a more recent {@code refs/txn/accepted}. + *

+ * Must be invoked with {@link KetchLeader#lock} held by caller. + */ + private void runNextPushRequest() { + LogIndex committed = leader.getCommitted(); + if (!equals(txnCommitted, committed) + && shouldPushUnbatchedCommit(committed, leader.isIdle())) { + pushCommitAsync(committed); + } + + if (queued.isEmpty() || !running.isEmpty() || waitingForRetry()) { + return; + } + + // Collapse all queued requests into a single request. + Map cmdMap = new HashMap<>(); + for (ReplicaPushRequest req : queued) { + for (ReceiveCommand cmd : req.getCommands()) { + String name = cmd.getRefName(); + ReceiveCommand old = cmdMap.remove(name); + if (old != null) { + cmd = new ReceiveCommand( + old.getOldId(), cmd.getNewId(), + name); + } + cmdMap.put(name, cmd); + } + } + queued.clear(); + waiting.clear(); + + List next = new ArrayList<>(cmdMap.values()); + for (ReceiveCommand cmd : next) { + running.put(cmd.getRefName(), cmd); + } + startPush(new ReplicaPushRequest(this, next)); + } + + private void pushAsync(ReplicaPushRequest req) { + if (defer(req)) { + // TODO(sop) Collapse during long retry outage. + for (ReceiveCommand cmd : req.getCommands()) { + waiting.put(cmd.getRefName(), cmd); + } + queued.add(req); + } else { + for (ReceiveCommand cmd : req.getCommands()) { + running.put(cmd.getRefName(), cmd); + } + startPush(req); + } + } + + private boolean defer(ReplicaPushRequest req) { + if (waitingForRetry()) { + // Prior communication failure; everything is deferred. + return true; + } + + for (ReceiveCommand nextCmd : req.getCommands()) { + ReceiveCommand priorCmd = waiting.get(nextCmd.getRefName()); + if (priorCmd == null) { + priorCmd = running.get(nextCmd.getRefName()); + } + if (priorCmd != null) { + // Another request pending on same ref; that must go first. + // Verify priorCmd.newId == nextCmd.oldId? + return true; + } + } + return false; + } + + private boolean waitingForRetry() { + Future f = retryFuture; + return f != null && !f.isDone(); + } + + private void retryLater(ReplicaPushRequest req) { + Collection cmds = req.getCommands(); + for (ReceiveCommand cmd : cmds) { + cmd.setResult(NOT_ATTEMPTED, null); + if (!waiting.containsKey(cmd.getRefName())) { + waiting.put(cmd.getRefName(), cmd); + } + } + queued.add(0, new ReplicaPushRequest(this, cmds)); + + if (!waitingForRetry()) { + long delay = KetchSystem.delay( + lastRetryMillis, + minRetryMillis, maxRetryMillis); + if (log.isDebugEnabled()) { + log.debug("Retrying {} after {} ms", //$NON-NLS-1$ + describeForLog(), Long.valueOf(delay)); + } + lastRetryMillis = delay; + retryAtMillis = SystemReader.getInstance().getCurrentTime() + delay; + retryFuture = getSystem().getExecutor() + .schedule(new WeakRetryPush(this), delay, MILLISECONDS); + } + } + + /** Weakly holds a retrying replica, allowing it to garbage collect. */ + static class WeakRetryPush extends WeakReference + implements Callable { + WeakRetryPush(KetchReplica r) { + super(r); + } + + @Override + public Void call() throws Exception { + KetchReplica r = get(); + if (r != null) { + r.doRetryPush(); + } + return null; + } + } + + private void doRetryPush() { + leader.lock.lock(); + try { + retryFuture = null; + runNextPushRequest(); + } finally { + leader.lock.unlock(); + } + } + + /** + * Begin executing a single push. + *

+ * This method must move processing onto another thread. Called with + * {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} held by caller. + * + * @param req + * the request to send to the replica. + */ + protected abstract void startPush(ReplicaPushRequest req); + + /** + * Callback from {@link ReplicaPushRequest} upon success or failure. + *

+ * Acquires the {@link KetchLeader#lock} and updates the leader's internal + * knowledge about this replica to reflect what has been learned during a + * push to the replica. In some cases of divergence this method may take + * some time to determine how the replica has diverged; to reduce contention + * this is evaluated before acquiring the leader lock. + * + * @param repo + * local repository instance used by the push thread. + * @param req + * push request just attempted. + */ + void afterPush(@Nullable Repository repo, ReplicaPushRequest req) { + ReceiveCommand acceptCmd = null; + ReceiveCommand commitCmd = null; + List stages = null; + + for (ReceiveCommand cmd : req.getCommands()) { + String name = cmd.getRefName(); + if (name.equals(getSystem().getTxnAccepted())) { + acceptCmd = cmd; + } else if (name.equals(getSystem().getTxnCommitted())) { + commitCmd = cmd; + } else if (cmd.getResult() == OK && cmd.getType() == CREATE + && name.startsWith(getSystem().getTxnStage())) { + if (stages == null) { + stages = new ArrayList<>(); + } + stages.add(cmd); + } + } + + State newState = null; + ObjectId acceptId = readId(req, acceptCmd); + if (repo != null && acceptCmd != null && acceptCmd.getResult() != OK + && req.getException() == null) { + try (LagCheck lag = new LagCheck(this, repo)) { + newState = lag.check(acceptId, acceptCmd); + acceptId = lag.getRemoteId(); + } + } + + leader.lock.lock(); + try { + for (ReceiveCommand cmd : req.getCommands()) { + running.remove(cmd.getRefName()); + } + + Throwable err = req.getException(); + if (err != null) { + state = OFFLINE; + error = err.toString(); + retryLater(req); + leader.onReplicaUpdate(this); + return; + } + + lastRetryMillis = 0; + error = null; + updateView(req, acceptId, commitCmd); + + if (acceptCmd != null && acceptCmd.getResult() == OK) { + state = hasAccepted(leader.getHead()) ? CURRENT : LAGGING; + if (stages != null) { + staged.put(acceptCmd.getNewId(), stages); + } + } else if (newState != null) { + state = newState; + } + + leader.onReplicaUpdate(this); + runNextPushRequest(); + } finally { + leader.lock.unlock(); + } + } + + private void updateView(ReplicaPushRequest req, @Nullable ObjectId acceptId, + ReceiveCommand commitCmd) { + if (acceptId != null) { + txnAccepted = acceptId; + } + + ObjectId committed = readId(req, commitCmd); + if (committed != null) { + txnCommitted = committed; + } else if (acceptId != null && txnCommitted == null) { + // Initialize during first conversation. + Map adv = req.getRefs(); + if (adv != null) { + Ref refs = adv.get(getSystem().getTxnCommitted()); + txnCommitted = getId(refs); + } + } + } + + @Nullable + private static ObjectId readId(ReplicaPushRequest req, + @Nullable ReceiveCommand cmd) { + if (cmd == null) { + // Ref was not in the command list, do not trust advertisement. + return null; + + } else if (cmd.getResult() == OK) { + // Currently at newId. + return cmd.getNewId(); + } + + Map refs = req.getRefs(); + return refs != null ? getId(refs.get(cmd.getRefName())) : null; + } + + /** + * Fetch objects from the remote using the calling thread. + *

+ * Called without {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock}. + * + * @param repo + * local repository to fetch objects into. + * @param req + * the request to fetch from a replica. + * @throws java.io.IOException + * communication with the replica was not possible. + */ + protected abstract void blockingFetch(Repository repo, + ReplicaFetchRequest req) throws IOException; + + /** + * Build a list of commands to commit + * {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#ALL_REFS}. + * + * @param git + * local leader repository to read committed state from. + * @param current + * all known references in the replica's repository. Typically + * this comes from a push advertisement. + * @param committed + * state being pushed to {@code refs/txn/committed}. + * @return commands to update during commit. + * @throws java.io.IOException + * cannot read the committed state. + */ + protected Collection prepareCommit(Repository git, + Map current, ObjectId committed) throws IOException { + List delta = new ArrayList<>(); + Map remote = new HashMap<>(current); + try (RevWalk rw = new RevWalk(git); + TreeWalk tw = new TreeWalk(rw.getObjectReader())) { + tw.setRecursive(true); + tw.addTree(rw.parseCommit(committed).getTree()); + while (tw.next()) { + if (tw.getRawMode(0) != TYPE_GITLINK + || tw.isPathSuffix(PEEL, 2)) { + // Symbolic references cannot be pushed. + // Caching peeled values is handled remotely. + continue; + } + + // TODO(sop) Do not send certain ref names to replica. + String name = RefTree.refName(tw.getPathString()); + Ref oldRef = remote.remove(name); + ObjectId oldId = getId(oldRef); + ObjectId newId = tw.getObjectId(0); + if (!AnyObjectId.equals(oldId, newId)) { + delta.add(new ReceiveCommand(oldId, newId, name)); + } + } + } + + // Delete any extra references not in the committed state. + for (Ref ref : remote.values()) { + if (canDelete(ref)) { + delta.add(new ReceiveCommand( + ref.getObjectId(), ObjectId.zeroId(), + ref.getName())); + } + } + return delta; + } + + boolean canDelete(Ref ref) { + String name = ref.getName(); + if (HEAD.equals(name)) { + return false; + } + if (name.startsWith(getSystem().getTxnNamespace())) { + return false; + } + // TODO(sop) Do not delete precious names from replica. + return true; + } + + @NonNull + static ObjectId getId(@Nullable Ref ref) { + if (ref != null) { + ObjectId id = ref.getObjectId(); + if (id != null) { + return id; + } + } + return ObjectId.zeroId(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchConstants.ACCEPTED; +import static org.eclipse.jgit.internal.ketch.KetchConstants.COMMITTED; +import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_TYPE; +import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_SECTION_KETCH; +import static org.eclipse.jgit.internal.ketch.KetchConstants.DEFAULT_TXN_NAMESPACE; +import static org.eclipse.jgit.internal.ketch.KetchConstants.STAGE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_NAME; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REMOTE; + +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.time.MonotonicClock; +import org.eclipse.jgit.util.time.MonotonicSystemClock; +import org.eclipse.jgit.util.time.ProposedTimestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Ketch system-wide configuration. + *

+ * This class provides useful defaults for testing and small proof of concepts. + * Full scale installations are expected to subclass and override methods to + * provide consistent configuration across all managed repositories. + *

+ * Servers should configure their own + * {@link java.util.concurrent.ScheduledExecutorService}. + */ +public class KetchSystem { + private static final Random RNG = new Random(); + + /** + * Get default executor, one thread per available processor. + * + * @return default executor, one thread per available processor. + */ + public static ScheduledExecutorService defaultExecutor() { + return DefaultExecutorHolder.I; + } + + private final ScheduledExecutorService executor; + private final MonotonicClock clock; + private final String txnNamespace; + private final String txnAccepted; + private final String txnCommitted; + private final String txnStage; + + /** + * Create a default system with a thread pool of 1 thread per CPU. + */ + public KetchSystem() { + this(defaultExecutor(), new MonotonicSystemClock(), DEFAULT_TXN_NAMESPACE); + } + + /** + * Create a Ketch system with the provided executor service. + * + * @param executor + * thread pool to run background operations. + * @param clock + * clock to create timestamps. + * @param txnNamespace + * reference namespace for the RefTree graph and associated + * transaction state. Must begin with {@code "refs/"} and end + * with {@code '/'}, for example {@code "refs/txn/"}. + */ + public KetchSystem(ScheduledExecutorService executor, MonotonicClock clock, + String txnNamespace) { + this.executor = executor; + this.clock = clock; + this.txnNamespace = txnNamespace; + this.txnAccepted = txnNamespace + ACCEPTED; + this.txnCommitted = txnNamespace + COMMITTED; + this.txnStage = txnNamespace + STAGE; + } + + /** + * Get executor to perform background operations. + * + * @return executor to perform background operations. + */ + public ScheduledExecutorService getExecutor() { + return executor; + } + + /** + * Get clock to obtain timestamps from. + * + * @return clock to obtain timestamps from. + */ + public MonotonicClock getClock() { + return clock; + } + + /** + * Get how long the leader will wait for the {@link #getClock()}'s + * {@code ProposedTimestamp} used in commits proposed to the RefTree graph + * ({@link #getTxnAccepted()}) + * + * @return how long the leader will wait for the {@link #getClock()}'s + * {@code ProposedTimestamp} used in commits proposed to the RefTree + * graph ({@link #getTxnAccepted()}). Defaults to 5 seconds. + */ + public Duration getMaxWaitForMonotonicClock() { + return Duration.ofSeconds(5); + } + + /** + * Whether elections should require monotonically increasing commit + * timestamps + * + * @return {@code true} if elections should require monotonically increasing + * commit timestamps. This requires a very good + * {@link org.eclipse.jgit.util.time.MonotonicClock}. + */ + public boolean requireMonotonicLeaderElections() { + return false; + } + + /** + * Get the namespace used for the RefTree graph and transaction management. + * + * @return reference namespace such as {@code "refs/txn/"}. + */ + public String getTxnNamespace() { + return txnNamespace; + } + + /** + * Get name of the accepted RefTree graph. + * + * @return name of the accepted RefTree graph. + */ + public String getTxnAccepted() { + return txnAccepted; + } + + /** + * Get name of the committed RefTree graph. + * + * @return name of the committed RefTree graph. + */ + public String getTxnCommitted() { + return txnCommitted; + } + + /** + * Get prefix for staged objects, e.g. {@code "refs/txn/stage/"}. + * + * @return prefix for staged objects, e.g. {@code "refs/txn/stage/"}. + */ + public String getTxnStage() { + return txnStage; + } + + /** + * Create new committer {@code PersonIdent} for ketch system + * + * @param time + * timestamp for the committer. + * @return identity line for the committer header of a RefTreeGraph. + */ + public PersonIdent newCommitter(ProposedTimestamp time) { + String name = "ketch"; //$NON-NLS-1$ + String email = "ketch@system"; //$NON-NLS-1$ + return new PersonIdent(name, email, time); + } + + /** + * Construct a random tag to identify a candidate during leader election. + *

+ * Multiple processes trying to elect themselves leaders at exactly the same + * time (rounded to seconds) using the same + * {@link #newCommitter(ProposedTimestamp)} identity strings, for the same + * term, may generate the same ObjectId for the election commit and falsely + * assume they have both won. + *

+ * Candidates add this tag to their election ballot commit to disambiguate + * the election. The tag only needs to be unique for a given triplet of + * {@link #newCommitter(ProposedTimestamp)}, system time (rounded to + * seconds), and term. If every replica in the system uses a unique + * {@code newCommitter} (such as including the host name after the + * {@code "@"} in the email address) the tag could be the empty string. + *

+ * The default implementation generates a few bytes of random data. + * + * @return unique tag; null or empty string if {@code newCommitter()} is + * sufficiently unique to identify the leader. + */ + @Nullable + public String newLeaderTag() { + int n = RNG.nextInt(1 << (6 * 4)); + return String.format("%06x", Integer.valueOf(n)); //$NON-NLS-1$ + } + + /** + * Construct the KetchLeader instance of a repository. + * + * @param repo + * local repository stored by the leader. + * @return leader instance. + * @throws java.net.URISyntaxException + * a follower configuration contains an unsupported URI. + */ + public KetchLeader createLeader(final Repository repo) + throws URISyntaxException { + KetchLeader leader = new KetchLeader(this) { + @Override + protected Repository openRepository() { + repo.incrementOpen(); + return repo; + } + }; + leader.setReplicas(createReplicas(leader, repo)); + return leader; + } + + /** + * Get the collection of replicas for a repository. + *

+ * The collection of replicas must include the local repository. + * + * @param leader + * the leader driving these replicas. + * @param repo + * repository to get the replicas of. + * @return collection of replicas for the specified repository. + * @throws java.net.URISyntaxException + * a configured URI is invalid. + */ + protected List createReplicas(KetchLeader leader, + Repository repo) throws URISyntaxException { + List replicas = new ArrayList<>(); + Config cfg = repo.getConfig(); + String localName = getLocalName(cfg); + for (String name : cfg.getSubsections(CONFIG_KEY_REMOTE)) { + if (!hasParticipation(cfg, name)) { + continue; + } + + ReplicaConfig kc = ReplicaConfig.newFromConfig(cfg, name); + if (name.equals(localName)) { + replicas.add(new LocalReplica(leader, name, kc)); + continue; + } + + RemoteConfig rc = new RemoteConfig(cfg, name); + List uris = rc.getPushURIs(); + if (uris.isEmpty()) { + uris = rc.getURIs(); + } + for (URIish uri : uris) { + String n = uris.size() == 1 ? name : uri.getHost(); + replicas.add(new RemoteGitReplica(leader, n, uri, kc, rc)); + } + } + return replicas; + } + + private static boolean hasParticipation(Config cfg, String name) { + return cfg.getString(CONFIG_KEY_REMOTE, name, CONFIG_KEY_TYPE) != null; + } + + private static String getLocalName(Config cfg) { + return cfg.getString(CONFIG_SECTION_KETCH, null, CONFIG_KEY_NAME); + } + + static class DefaultExecutorHolder { + private static final Logger log = LoggerFactory.getLogger(KetchSystem.class); + static final ScheduledExecutorService I = create(); + + private static ScheduledExecutorService create() { + int cores = Runtime.getRuntime().availableProcessors(); + int threads = Math.max(5, cores); + log.info("Using {} threads", Integer.valueOf(threads)); //$NON-NLS-1$ + return Executors.newScheduledThreadPool( + threads, + new ThreadFactory() { + private final AtomicInteger threadCnt = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + int id = threadCnt.incrementAndGet(); + Thread thr = new Thread(r); + thr.setName("KetchExecutor-" + id); //$NON-NLS-1$ + return thr; + } + }); + } + + private DefaultExecutorHolder() { + } + } + + /** + * Compute a delay in a {@code min..max} interval with random jitter. + * + * @param last + * amount of delay waited before the last attempt. This is used + * to seed the next delay interval. Should be 0 if there was no + * prior delay. + * @param min + * shortest amount of allowable delay between attempts. + * @param max + * longest amount of allowable delay between attempts. + * @return new amount of delay to wait before the next attempt. + */ + static long delay(long last, long min, long max) { + long r = Math.max(0, last * 3 - min); + if (r > 0) { + int c = (int) Math.min(r + 1, Integer.MAX_VALUE); + r = RNG.nextInt(c); + } + return Math.max(Math.min(min + r, max), min); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Translation bundle for the Ketch implementation. + */ +public class KetchText extends TranslationBundle { + /** + * Get an instance of this translation bundle. + * + * @return instance of this translation bundle. + */ + public static KetchText get() { + return NLS.getBundleFor(KetchText.class); + } + + // @formatter:off + /***/ public String accepted; + /***/ public String cannotFetchFromLocalReplica; + /***/ public String failed; + /***/ public String invalidFollowerUri; + /***/ public String leaderFailedToStore; + /***/ public String localReplicaRequired; + /***/ public String mismatchedTxnNamespace; + /***/ public String outsideTxnNamespace; + /***/ public String proposingUpdates; + /***/ public String queuedProposalFailedToApply; + /***/ public String starting; + /***/ public String unsupportedVoterCount; + /***/ public String waitingForQueue; +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.AHEAD; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.DIVERGENT; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.LAGGING; +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.UNKNOWN; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * A helper to check if a {@link KetchReplica} is ahead or behind the leader. + */ +class LagCheck implements AutoCloseable { + private final KetchReplica replica; + private final Repository repo; + private RevWalk rw; + private ObjectId remoteId; + + LagCheck(KetchReplica replica, Repository repo) { + this.replica = replica; + this.repo = repo; + initRevWalk(); + } + + private void initRevWalk() { + if (rw != null) { + rw.close(); + } + + rw = new RevWalk(repo); + rw.setRetainBody(false); + } + + /** {@inheritDoc} */ + @Override + public void close() { + if (rw != null) { + rw.close(); + rw = null; + } + } + + ObjectId getRemoteId() { + return remoteId; + } + + KetchReplica.State check(ObjectId acceptId, ReceiveCommand acceptCmd) { + remoteId = acceptId; + if (remoteId == null) { + // Nothing advertised by the replica, value is unknown. + return UNKNOWN; + } + + if (AnyObjectId.equals(remoteId, ObjectId.zeroId())) { + // Replica does not have the txnAccepted reference. + return LAGGING; + } + + try { + RevCommit remote; + try { + remote = parseRemoteCommit(acceptCmd.getRefName()); + } catch (RefGoneException gone) { + // Replica does not have the txnAccepted reference. + return LAGGING; + } catch (MissingObjectException notFound) { + // Local repository does not know this commit so it cannot + // be including the replica's log. + return DIVERGENT; + } + + RevCommit head = rw.parseCommit(acceptCmd.getNewId()); + if (rw.isMergedInto(remote, head)) { + return LAGGING; + } + + // TODO(sop) Check term to see if my leader was deposed. + if (rw.isMergedInto(head, remote)) { + return AHEAD; + } else { + return DIVERGENT; + } + } catch (IOException err) { + KetchReplica.log.error(String.format( + "Cannot compare %s", //$NON-NLS-1$ + acceptCmd.getRefName()), err); + return UNKNOWN; + } + } + + private RevCommit parseRemoteCommit(String refName) + throws IOException, MissingObjectException, RefGoneException { + try { + return rw.parseCommit(remoteId); + } catch (MissingObjectException notLocal) { + // Fall through and try to acquire the object by fetching it. + } + + ReplicaFetchRequest fetch = new ReplicaFetchRequest( + Collections.singleton(refName), + Collections. emptySet()); + try { + replica.blockingFetch(repo, fetch); + } catch (IOException fetchErr) { + KetchReplica.log.error(String.format( + "Cannot fetch %s (%s) from %s", //$NON-NLS-1$ + remoteId.abbreviate(8).name(), refName, + replica.describeForLog()), fetchErr); + throw new MissingObjectException(remoteId, OBJ_COMMIT); + } + + Map adv = fetch.getRefs(); + if (adv == null) { + throw new MissingObjectException(remoteId, OBJ_COMMIT); + } + + Ref ref = adv.get(refName); + if (ref == null || ref.getObjectId() == null) { + throw new RefGoneException(); + } + + initRevWalk(); + remoteId = ref.getObjectId(); + return rw.parseCommit(remoteId); + } + + private static class RefGoneException extends Exception { + private static final long serialVersionUID = 1L; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchReplica.State.OFFLINE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.ObjectId; + +/** + * A snapshot of a leader and its view of the world. + */ +public class LeaderSnapshot { + final List replicas = new ArrayList<>(); + KetchLeader.State state; + long term; + LogIndex headIndex; + LogIndex committedIndex; + boolean idle; + + LeaderSnapshot() { + } + + /** + * Get unmodifiable view of configured replicas. + * + * @return unmodifiable view of configured replicas. + */ + public Collection getReplicas() { + return Collections.unmodifiableList(replicas); + } + + /** + * Get current state of the leader. + * + * @return current state of the leader. + */ + public KetchLeader.State getState() { + return state; + } + + /** + * Whether the leader is not running a round to reach consensus, and has no + * rounds queued. + * + * @return {@code true} if the leader is not running a round to reach + * consensus, and has no rounds queued. + */ + public boolean isIdle() { + return idle; + } + + /** + * Get term of this leader + * + * @return term of this leader. Valid only if {@link #getState()} is + * currently + * {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#LEADER}. + */ + public long getTerm() { + return term; + } + + /** + * Get end of the leader's log + * + * @return end of the leader's log; null if leader hasn't started up enough + * to begin its own election. + */ + @Nullable + public LogIndex getHead() { + return headIndex; + } + + /** + * Get state the leader knows is committed on a majority of participant + * replicas + * + * @return state the leader knows is committed on a majority of participant + * replicas. Null until the leader instance has committed a log + * index within its own term. + */ + @Nullable + public LogIndex getCommitted() { + return committedIndex; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append(isIdle() ? "IDLE" : "RUNNING"); //$NON-NLS-1$ //$NON-NLS-2$ + s.append(" state ").append(getState()); //$NON-NLS-1$ + if (getTerm() > 0) { + s.append(" term ").append(getTerm()); //$NON-NLS-1$ + } + s.append('\n'); + s.append(String.format( + "%-10s %12s %12s\n", //$NON-NLS-1$ + "Replica", "Accepted", "Committed")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + s.append("------------------------------------\n"); //$NON-NLS-1$ + debug(s, "(leader)", getHead(), getCommitted()); //$NON-NLS-1$ + s.append('\n'); + for (ReplicaSnapshot r : getReplicas()) { + debug(s, r); + s.append('\n'); + } + s.append('\n'); + return s.toString(); + } + + private static void debug(StringBuilder b, ReplicaSnapshot s) { + KetchReplica replica = s.getReplica(); + debug(b, replica.getName(), s.getAccepted(), s.getCommitted()); + b.append(String.format(" %-8s %s", //$NON-NLS-1$ + replica.getParticipation(), s.getState())); + if (s.getState() == OFFLINE) { + String err = s.getErrorMessage(); + if (err != null) { + b.append(" (").append(err).append(')'); //$NON-NLS-1$ + } + } + } + + private static void debug(StringBuilder s, String name, + ObjectId accepted, ObjectId committed) { + s.append(String.format( + "%-10s %-12s %-12s", //$NON-NLS-1$ + name, str(accepted), str(committed))); + } + + static String str(ObjectId c) { + if (c instanceof LogIndex) { + return ((LogIndex) c).describeForLog(); + } else if (c != null) { + return c.abbreviate(8).name(); + } + return "-"; //$NON-NLS-1$ + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.ALL_REFS; +import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.TXN_COMMITTED; +import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.time.MonotonicClock; +import org.eclipse.jgit.util.time.ProposedTimestamp; + +/** + * Ketch replica running on the same system as the + * {@link org.eclipse.jgit.internal.ketch.KetchLeader}. + */ +public class LocalReplica extends KetchReplica { + /** + * Configure a local replica. + * + * @param leader + * instance this replica follows. + * @param name + * unique-ish name identifying this replica for debugging. + * @param cfg + * how Ketch should treat the local system. + */ + public LocalReplica(KetchLeader leader, String name, ReplicaConfig cfg) { + super(leader, name, cfg); + } + + /** {@inheritDoc} */ + @Override + protected String describeForLog() { + return String.format("%s (leader)", getName()); //$NON-NLS-1$ + } + + /** + * Initializes local replica by reading accepted and committed references. + *

+ * Loads accepted and committed references from the reference database of + * the local replica and stores their current ObjectIds in memory. + * + * @param repo + * repository to initialize state from. + * @throws IOException + * cannot read repository state. + */ + void initialize(Repository repo) throws IOException { + RefDatabase refdb = repo.getRefDatabase(); + if (refdb instanceof RefTreeDatabase) { + RefTreeDatabase treeDb = (RefTreeDatabase) refdb; + String txnNamespace = getSystem().getTxnNamespace(); + if (!txnNamespace.equals(treeDb.getTxnNamespace())) { + throw new IOException(MessageFormat.format( + KetchText.get().mismatchedTxnNamespace, + txnNamespace, treeDb.getTxnNamespace())); + } + refdb = treeDb.getBootstrap(); + } + initialize(refdb.exactRef( + getSystem().getTxnAccepted(), + getSystem().getTxnCommitted())); + } + + /** {@inheritDoc} */ + @Override + protected void startPush(final ReplicaPushRequest req) { + getSystem().getExecutor().execute(new Runnable() { + @Override + public void run() { + MonotonicClock clk = getSystem().getClock(); + try (Repository git = getLeader().openRepository(); + ProposedTimestamp ts = clk.propose()) { + try { + update(git, req, ts); + req.done(git); + } catch (Throwable err) { + req.setException(git, err); + } + } catch (IOException err) { + req.setException(null, err); + } + } + }); + } + + /** {@inheritDoc} */ + @Override + protected void blockingFetch(Repository repo, ReplicaFetchRequest req) + throws IOException { + throw new IOException(KetchText.get().cannotFetchFromLocalReplica); + } + + private void update(Repository git, ReplicaPushRequest req, + ProposedTimestamp ts) throws IOException { + RefDatabase refdb = git.getRefDatabase(); + CommitMethod method = getCommitMethod(); + + // Local replica probably uses RefTreeDatabase, the request should + // be only for the txnNamespace, so drop to the bootstrap layer. + if (refdb instanceof RefTreeDatabase) { + if (!isOnlyTxnNamespace(req.getCommands())) { + return; + } + + refdb = ((RefTreeDatabase) refdb).getBootstrap(); + method = TXN_COMMITTED; + } + + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addProposedTimestamp(ts); + batch.setRefLogIdent(getSystem().newCommitter(ts)); + batch.setRefLogMessage("ketch", false); //$NON-NLS-1$ + batch.setAllowNonFastForwards(true); + + // RefDirectory updates multiple references sequentially. + // Run everything else first, then accepted (if present), + // then committed (if present). This ensures an earlier + // failure will not update these critical references. + ReceiveCommand accepted = null; + ReceiveCommand committed = null; + for (ReceiveCommand cmd : req.getCommands()) { + String name = cmd.getRefName(); + if (name.equals(getSystem().getTxnAccepted())) { + accepted = cmd; + } else if (name.equals(getSystem().getTxnCommitted())) { + committed = cmd; + } else { + batch.addCommand(cmd); + } + } + if (committed != null && method == ALL_REFS) { + Map refs = refdb.getRefs(ALL); + batch.addCommand(prepareCommit(git, refs, committed.getNewId())); + } + if (accepted != null) { + batch.addCommand(accepted); + } + if (committed != null) { + batch.addCommand(committed); + } + + try (RevWalk rw = new RevWalk(git)) { + batch.execute(rw, NullProgressMonitor.INSTANCE); + } + + // KetchReplica only cares about accepted and committed in + // advertisement. If they failed, store the current values + // back in the ReplicaPushRequest. + List failed = new ArrayList<>(2); + checkFailed(failed, accepted); + checkFailed(failed, committed); + if (!failed.isEmpty()) { + String[] arr = failed.toArray(new String[failed.size()]); + req.setRefs(refdb.exactRef(arr)); + } + } + + private static void checkFailed(List failed, ReceiveCommand cmd) { + if (cmd != null && cmd.getResult() != OK) { + failed.add(cmd.getRefName()); + } + } + + private boolean isOnlyTxnNamespace(Collection cmdList) { + // Be paranoid and reject non txnNamespace names, this + // is a programming error in Ketch that should not occur. + + String txnNamespace = getSystem().getTxnNamespace(); + for (ReceiveCommand cmd : cmdList) { + if (!cmd.getRefName().startsWith(txnNamespace)) { + cmd.setResult(REJECTED_OTHER_REASON, + MessageFormat.format( + KetchText.get().outsideTxnNamespace, + cmd.getRefName(), txnNamespace)); + ReceiveCommand.abort(cmdList); + return false; + } + } + return true; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; + +/** + * An ObjectId for a commit extended with incrementing log index. + *

+ * For any two LogIndex instances, {@code A} is an ancestor of {@code C} + * reachable through parent edges in the graph if {@code A.index < C.index}. + * LogIndex provides a performance optimization for Ketch, the same information + * can be obtained from {@link org.eclipse.jgit.revwalk.RevWalk}. + *

+ * Index values are only valid within a single + * {@link org.eclipse.jgit.internal.ketch.KetchLeader} instance after it has won + * an election. By restricting scope to a single leader new leaders do not need + * to traverse the entire history to determine the next {@code index} for new + * proposals. This differs from Raft, where leader election uses the log index + * and the term number to determine which replica holds a sufficiently + * up-to-date log. Since Ketch uses Git objects for storage of its replicated + * log, it keeps the term number as Raft does but uses standard Git operations + * to imply the log index. + *

+ * {@link org.eclipse.jgit.internal.ketch.Round#runAsync(AnyObjectId)} bumps the + * index as each new round is constructed. + */ +public class LogIndex extends ObjectId { + static LogIndex unknown(AnyObjectId id) { + return new LogIndex(id, 0); + } + + private final long index; + + private LogIndex(AnyObjectId id, long index) { + super(id); + this.index = index; + } + + LogIndex nextIndex(AnyObjectId id) { + return new LogIndex(id, index + 1); + } + + /** + * Get index provided by the current leader instance. + * + * @return index provided by the current leader instance. + */ + public long getIndex() { + return index; + } + + /** + * Check if this log position committed before another log position. + *

+ * Only valid for log positions in memory for the current leader. + * + * @param c + * other (more recent) log position. + * @return true if this log position was before {@code c} or equal to c and + * therefore any agreement of {@code c} implies agreement on this + * log position. + */ + boolean isBefore(LogIndex c) { + return index <= c.index; + } + + /** + * Create string suitable for debug logging containing the log index and + * abbreviated ObjectId. + * + * @return string suitable for debug logging containing the log index and + * abbreviated ObjectId. + */ + @SuppressWarnings("boxing") + public String describeForLog() { + return String.format("%5d/%s", index, abbreviate(6).name()); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @SuppressWarnings("boxing") + @Override + public String toString() { + return String.format("LogId[%5d/%s]", index, name()); //$NON-NLS-1$ + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,4 @@ +/** + * Distributed consensus system built on Git. + */ +package org.eclipse.jgit.internal.ketch; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.Proposal.State.ABORTED; +import static org.eclipse.jgit.internal.ketch.Proposal.State.EXECUTED; +import static org.eclipse.jgit.internal.ketch.Proposal.State.NEW; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.storage.reftree.Command; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.PushCertificate; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.time.ProposedTimestamp; + +/** + * A proposal to be applied in a Ketch system. + *

+ * Pushing to a Ketch leader results in the leader making a proposal. The + * proposal includes the list of reference updates. The leader attempts to send + * the proposal to a quorum of replicas by pushing the proposal to a "staging" + * area under the {@code refs/txn/stage/} namespace. If the proposal succeeds + * then the changes are durable and the leader can commit the proposal. + *

+ * Proposals are executed by + * {@link org.eclipse.jgit.internal.ketch.KetchLeader#queueProposal(Proposal)}, + * which runs them asynchronously in the background. Proposals are thread-safe + * futures allowing callers to {@link #await()} for results or be notified by + * callback using {@link #addListener(Runnable)}. + */ +public class Proposal { + /** Current state of the proposal. */ + public enum State { + /** Proposal has not yet been given to a {@link KetchLeader}. */ + NEW(false), + + /** + * Proposal was validated and has entered the queue, but a round + * containing this proposal has not started yet. + */ + QUEUED(false), + + /** Round containing the proposal has begun and is in progress. */ + RUNNING(false), + + /** + * Proposal was executed through a round. Individual results from + * {@link Proposal#getCommands()}, {@link Command#getResult()} explain + * the success or failure outcome. + */ + EXECUTED(true), + + /** Proposal was aborted and did not reach consensus. */ + ABORTED(true); + + private final boolean done; + + private State(boolean done) { + this.done = done; + } + + /** @return true if this is a terminal state. */ + public boolean isDone() { + return done; + } + } + + private final List commands; + private PersonIdent author; + private String message; + private PushCertificate pushCert; + + private List timestamps; + private final List listeners = new CopyOnWriteArrayList<>(); + private final AtomicReference state = new AtomicReference<>(NEW); + + /** + * Create a proposal from a list of Ketch commands. + * + * @param cmds + * prepared list of commands. + */ + public Proposal(List cmds) { + commands = Collections.unmodifiableList(new ArrayList<>(cmds)); + } + + /** + * Create a proposal from a collection of received commands. + * + * @param rw + * walker to assist in preparing commands. + * @param cmds + * list of pending commands. + * @throws org.eclipse.jgit.errors.MissingObjectException + * newId of a command is not found locally. + * @throws java.io.IOException + * local objects cannot be accessed. + */ + public Proposal(RevWalk rw, Collection cmds) + throws MissingObjectException, IOException { + commands = asCommandList(rw, cmds); + } + + private static List asCommandList(RevWalk rw, + Collection cmds) + throws MissingObjectException, IOException { + List commands = new ArrayList<>(cmds.size()); + for (ReceiveCommand cmd : cmds) { + commands.add(new Command(rw, cmd)); + } + return Collections.unmodifiableList(commands); + } + + /** + * Get commands from this proposal. + * + * @return commands from this proposal. + */ + public Collection getCommands() { + return commands; + } + + /** + * Get optional author of the proposal. + * + * @return optional author of the proposal. + */ + @Nullable + public PersonIdent getAuthor() { + return author; + } + + /** + * Set the author for the proposal. + * + * @param who + * optional identity of the author of the proposal. + * @return {@code this} + */ + public Proposal setAuthor(@Nullable PersonIdent who) { + author = who; + return this; + } + + /** + * Get optional message for the commit log of the RefTree. + * + * @return optional message for the commit log of the RefTree. + */ + @Nullable + public String getMessage() { + return message; + } + + /** + * Set the message to appear in the commit log of the RefTree. + * + * @param msg + * message text for the commit. + * @return {@code this} + */ + public Proposal setMessage(@Nullable String msg) { + message = msg != null && !msg.isEmpty() ? msg : null; + return this; + } + + /** + * Get optional certificate signing the references. + * + * @return optional certificate signing the references. + */ + @Nullable + public PushCertificate getPushCertificate() { + return pushCert; + } + + /** + * Set the push certificate signing the references. + * + * @param cert + * certificate, may be null. + * @return {@code this} + */ + public Proposal setPushCertificate(@Nullable PushCertificate cert) { + pushCert = cert; + return this; + } + + /** + * Get timestamps that Ketch must block for. + * + * @return timestamps that Ketch must block for. These may have been used as + * commit times inside the objects involved in the proposal. + */ + public List getProposedTimestamps() { + if (timestamps != null) { + return timestamps; + } + return Collections.emptyList(); + } + + /** + * Request the proposal to wait for the affected timestamps to resolve. + * + * @param ts + * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. + * @return {@code this}. + */ + public Proposal addProposedTimestamp(ProposedTimestamp ts) { + if (timestamps == null) { + timestamps = new ArrayList<>(4); + } + timestamps.add(ts); + return this; + } + + /** + * Add a callback to be invoked when the proposal is done. + *

+ * A proposal is done when it has entered either + * {@link org.eclipse.jgit.internal.ketch.Proposal.State#EXECUTED} or + * {@link org.eclipse.jgit.internal.ketch.Proposal.State#ABORTED} state. If + * the proposal is already done {@code callback.run()} is immediately + * invoked on the caller's thread. + * + * @param callback + * method to run after the proposal is done. The callback may be + * run on a Ketch system thread and should be completed quickly. + */ + public void addListener(Runnable callback) { + boolean runNow = false; + synchronized (state) { + if (state.get().isDone()) { + runNow = true; + } else { + listeners.add(callback); + } + } + if (runNow) { + callback.run(); + } + } + + /** Set command result as OK. */ + void success() { + for (Command c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(OK); + } + } + notifyState(EXECUTED); + } + + /** Mark commands as "transaction aborted". */ + void abort() { + Command.abort(commands, null); + notifyState(ABORTED); + } + + /** + * Read the current state of the proposal. + * + * @return read the current state of the proposal. + */ + public State getState() { + return state.get(); + } + + /** + * Whether the proposal was attempted + * + * @return {@code true} if the proposal was attempted. A true value does not + * mean consensus was reached, only that the proposal was considered + * and will not be making any more progress beyond its current + * state. + */ + public boolean isDone() { + return state.get().isDone(); + } + + /** + * Wait for the proposal to be attempted and {@link #isDone()} to be true. + * + * @throws java.lang.InterruptedException + * caller was interrupted before proposal executed. + */ + public void await() throws InterruptedException { + synchronized (state) { + while (!state.get().isDone()) { + state.wait(); + } + } + } + + /** + * Wait for the proposal to be attempted and {@link #isDone()} to be true. + * + * @param wait + * how long to wait. + * @param unit + * unit describing the wait time. + * @return true if the proposal is done; false if the method timed out. + * @throws java.lang.InterruptedException + * caller was interrupted before proposal executed. + */ + public boolean await(long wait, TimeUnit unit) throws InterruptedException { + synchronized (state) { + if (state.get().isDone()) { + return true; + } + state.wait(unit.toMillis(wait)); + return state.get().isDone(); + } + } + + /** + * Wait for the proposal to exit a state. + * + * @param notIn + * state the proposal should not be in to return. + * @param wait + * how long to wait. + * @param unit + * unit describing the wait time. + * @return true if the proposal exited the state; false on time out. + * @throws java.lang.InterruptedException + * caller was interrupted before proposal executed. + */ + public boolean awaitStateChange(State notIn, long wait, TimeUnit unit) + throws InterruptedException { + synchronized (state) { + if (state.get() != notIn) { + return true; + } + state.wait(unit.toMillis(wait)); + return state.get() != notIn; + } + } + + void notifyState(State s) { + synchronized (state) { + state.set(s); + state.notifyAll(); + } + if (s.isDone()) { + for (Runnable callback : listeners) { + callback.run(); + } + listeners.clear(); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Ketch Proposal {\n"); //$NON-NLS-1$ + s.append(" ").append(state.get()).append('\n'); //$NON-NLS-1$ + if (author != null) { + s.append(" author ").append(author).append('\n'); //$NON-NLS-1$ + } + if (message != null) { + s.append(" message ").append(message).append('\n'); //$NON-NLS-1$ + } + for (Command c : commands) { + s.append(" "); //$NON-NLS-1$ + format(s, c.getOldRef(), "CREATE"); //$NON-NLS-1$ + s.append(' '); + format(s, c.getNewRef(), "DELETE"); //$NON-NLS-1$ + s.append(' ').append(c.getRefName()); + if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) { + s.append(' ').append(c.getResult()); // $NON-NLS-1$ + } + s.append('\n'); + } + s.append('}'); + return s.toString(); + } + + private static void format(StringBuilder s, @Nullable Ref r, String n) { + if (r == null) { + s.append(n); + } else if (r.isSymbolic()) { + s.append(r.getTarget().getName()); + } else { + ObjectId id = r.getObjectId(); + if (id != null) { + s.append(id.abbreviate(8).name()); + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.Proposal.State.RUNNING; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.reftree.Command; +import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.time.ProposedTimestamp; + +/** A {@link Round} that aggregates and sends user {@link Proposal}s. */ +class ProposalRound extends Round { + private final List todo; + private RefTree queuedTree; + + ProposalRound(KetchLeader leader, LogIndex head, List todo, + @Nullable RefTree tree) { + super(leader, head); + this.todo = todo; + + if (tree != null && canCombine(todo)) { + this.queuedTree = tree; + } else { + leader.roundHoldsReferenceToRefTree = false; + } + } + + private static boolean canCombine(List todo) { + Proposal first = todo.get(0); + for (int i = 1; i < todo.size(); i++) { + if (!canCombine(first, todo.get(i))) { + return false; + } + } + return true; + } + + private static boolean canCombine(Proposal a, Proposal b) { + String aMsg = nullToEmpty(a.getMessage()); + String bMsg = nullToEmpty(b.getMessage()); + return aMsg.equals(bMsg) && canCombine(a.getAuthor(), b.getAuthor()); + } + + private static String nullToEmpty(@Nullable String str) { + return str != null ? str : ""; //$NON-NLS-1$ + } + + private static boolean canCombine(@Nullable PersonIdent a, + @Nullable PersonIdent b) { + if (a != null && b != null) { + // Same name and email address. Combine timestamp as the two + // proposals are running concurrently and appear together or + // not at all from the point of view of an outside reader. + return a.getName().equals(b.getName()) + && a.getEmailAddress().equals(b.getEmailAddress()); + } + + // If a and b are null, both will be the system identity. + return a == null && b == null; + } + + @Override + void start() throws IOException { + for (Proposal p : todo) { + p.notifyState(RUNNING); + } + try { + ObjectId id; + try (Repository git = leader.openRepository(); + ProposedTimestamp ts = getSystem().getClock().propose()) { + id = insertProposals(git, ts); + blockUntil(ts); + } + runAsync(id); + } catch (NoOp e) { + for (Proposal p : todo) { + p.success(); + } + leader.lock.lock(); + try { + leader.nextRound(); + } finally { + leader.lock.unlock(); + } + } catch (IOException e) { + abort(); + throw e; + } + } + + private ObjectId insertProposals(Repository git, ProposedTimestamp ts) + throws IOException, NoOp { + ObjectId id; + try (ObjectInserter inserter = git.newObjectInserter()) { + // TODO(sop) Process signed push certificates. + + if (queuedTree != null) { + id = insertSingleProposal(git, ts, inserter); + } else { + id = insertMultiProposal(git, ts, inserter); + } + + stageCommands = makeStageList(git, inserter); + inserter.flush(); + } + return id; + } + + private ObjectId insertSingleProposal(Repository git, ProposedTimestamp ts, + ObjectInserter inserter) throws IOException, NoOp { + // Fast path: tree is passed in with all proposals applied. + ObjectId treeId = queuedTree.writeTree(inserter); + queuedTree = null; + leader.roundHoldsReferenceToRefTree = false; + + if (!ObjectId.zeroId().equals(acceptedOldIndex)) { + try (RevWalk rw = new RevWalk(git)) { + RevCommit c = rw.parseCommit(acceptedOldIndex); + if (treeId.equals(c.getTree())) { + throw new NoOp(); + } + } + } + + Proposal p = todo.get(0); + CommitBuilder b = new CommitBuilder(); + b.setTreeId(treeId); + if (!ObjectId.zeroId().equals(acceptedOldIndex)) { + b.setParentId(acceptedOldIndex); + } + b.setCommitter(leader.getSystem().newCommitter(ts)); + b.setAuthor(p.getAuthor() != null ? p.getAuthor() : b.getCommitter()); + b.setMessage(message(p)); + return inserter.insert(b); + } + + private ObjectId insertMultiProposal(Repository git, ProposedTimestamp ts, + ObjectInserter inserter) throws IOException, NoOp { + // The tree was not passed in, or there are multiple proposals + // each needing their own commit. Reset the tree and replay each + // proposal in order as individual commits. + ObjectId lastIndex = acceptedOldIndex; + ObjectId oldTreeId; + RefTree tree; + if (ObjectId.zeroId().equals(lastIndex)) { + oldTreeId = ObjectId.zeroId(); + tree = RefTree.newEmptyTree(); + } else { + try (RevWalk rw = new RevWalk(git)) { + RevCommit c = rw.parseCommit(lastIndex); + oldTreeId = c.getTree(); + tree = RefTree.read(rw.getObjectReader(), c.getTree()); + } + } + + PersonIdent committer = leader.getSystem().newCommitter(ts); + for (Proposal p : todo) { + if (!tree.apply(p.getCommands())) { + // This should not occur, previously during queuing the + // commands were successfully applied to the pending tree. + // Abort the entire round. + throw new IOException( + KetchText.get().queuedProposalFailedToApply); + } + + ObjectId treeId = tree.writeTree(inserter); + if (treeId.equals(oldTreeId)) { + continue; + } + + CommitBuilder b = new CommitBuilder(); + b.setTreeId(treeId); + if (!ObjectId.zeroId().equals(lastIndex)) { + b.setParentId(lastIndex); + } + b.setAuthor(p.getAuthor() != null ? p.getAuthor() : committer); + b.setCommitter(committer); + b.setMessage(message(p)); + lastIndex = inserter.insert(b); + } + if (lastIndex.equals(acceptedOldIndex)) { + throw new NoOp(); + } + return lastIndex; + } + + private String message(Proposal p) { + StringBuilder m = new StringBuilder(); + String msg = p.getMessage(); + if (msg != null && !msg.isEmpty()) { + m.append(msg); + while (m.length() < 2 || m.charAt(m.length() - 2) != '\n' + || m.charAt(m.length() - 1) != '\n') { + m.append('\n'); + } + } + m.append(KetchConstants.TERM.getName()) + .append(": ") //$NON-NLS-1$ + .append(leader.getTerm()); + return m.toString(); + } + + void abort() { + for (Proposal p : todo) { + p.abort(); + } + } + + @Override + void success() { + for (Proposal p : todo) { + p.success(); + } + } + + private List makeStageList(Repository git, + ObjectInserter inserter) throws IOException { + // For each branch, collapse consecutive updates to only most recent, + // avoiding sending multiple objects in a rapid fast-forward chain, or + // rewritten content. + Map byRef = new HashMap<>(); + for (Proposal p : todo) { + for (Command c : p.getCommands()) { + Ref n = c.getNewRef(); + if (n != null && !n.isSymbolic()) { + byRef.put(n.getName(), n.getObjectId()); + } + } + } + if (byRef.isEmpty()) { + return Collections.emptyList(); + } + + Set newObjs = new HashSet<>(byRef.values()); + StageBuilder b = new StageBuilder( + leader.getSystem().getTxnStage(), + acceptedNewIndex); + return b.makeStageList(newObjs, git, inserter); + } + + private void blockUntil(ProposedTimestamp ts) + throws TimeIsUncertainException { + List times = todo.stream() + .flatMap(p -> p.getProposedTimestamps().stream()) + .collect(Collectors.toCollection(ArrayList::new)); + times.add(ts); + + try { + Duration maxWait = getSystem().getMaxWaitForMonotonicClock(); + ProposedTimestamp.blockUntil(times, maxWait); + } catch (InterruptedException | TimeoutException e) { + throw new TimeIsUncertainException(e); + } + } + + private static class NoOp extends Exception { + private static final long serialVersionUID = 1L; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.ALL_REFS; +import static org.eclipse.jgit.lib.Ref.Storage.NETWORK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NODELETE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.PushConnection; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.URIish; + +/** + * Representation of a Git repository on a remote replica system. + *

+ * {@link org.eclipse.jgit.internal.ketch.KetchLeader} will contact the replica + * using the Git wire protocol. + *

+ * The remote replica may be fully Ketch-aware, or a standard Git server. + */ +public class RemoteGitReplica extends KetchReplica { + private final URIish uri; + private final RemoteConfig remoteConfig; + + /** + * Configure a new remote. + * + * @param leader + * instance this replica follows. + * @param name + * unique-ish name identifying this remote for debugging. + * @param uri + * URI to connect to the follower's repository. + * @param cfg + * how Ketch should treat the remote system. + * @param rc + * optional remote configuration describing how to contact the + * peer repository. + */ + public RemoteGitReplica(KetchLeader leader, String name, URIish uri, + ReplicaConfig cfg, @Nullable RemoteConfig rc) { + super(leader, name, cfg); + this.uri = uri; + this.remoteConfig = rc; + } + + /** + * Get URI to contact the remote peer repository. + * + * @return URI to contact the remote peer repository. + */ + public URIish getURI() { + return uri; + } + + /** + * Get optional configuration describing how to contact the peer. + * + * @return optional configuration describing how to contact the peer. + */ + @Nullable + protected RemoteConfig getRemoteConfig() { + return remoteConfig; + } + + /** {@inheritDoc} */ + @Override + protected String describeForLog() { + return String.format("%s @ %s", getName(), getURI()); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + protected void startPush(final ReplicaPushRequest req) { + getSystem().getExecutor().execute(new Runnable() { + @Override + public void run() { + try (Repository git = getLeader().openRepository()) { + try { + push(git, req); + req.done(git); + } catch (Throwable err) { + req.setException(git, err); + } + } catch (IOException err) { + req.setException(null, err); + } + } + }); + } + + private void push(Repository repo, ReplicaPushRequest req) + throws NotSupportedException, TransportException, IOException { + Map adv; + List cmds = asUpdateList(req.getCommands()); + try (Transport transport = Transport.open(repo, uri)) { + RemoteConfig rc = getRemoteConfig(); + if (rc != null) { + transport.applyConfig(rc); + } + transport.setPushAtomic(true); + adv = push(repo, transport, cmds); + } + for (RemoteCommand c : cmds) { + c.copyStatusToResult(); + } + req.setRefs(adv); + } + + private Map push(Repository git, Transport transport, + List cmds) throws IOException { + Map updates = asUpdateMap(cmds); + try (PushConnection connection = transport.openPush()) { + Map adv = connection.getRefsMap(); + RemoteRefUpdate accepted = updates.get(getSystem().getTxnAccepted()); + if (accepted != null && !isExpectedValue(adv, accepted)) { + abort(cmds); + return adv; + } + + RemoteRefUpdate committed = updates.get(getSystem().getTxnCommitted()); + if (committed != null && !isExpectedValue(adv, committed)) { + abort(cmds); + return adv; + } + if (committed != null && getCommitMethod() == ALL_REFS) { + prepareCommit(git, cmds, updates, adv, + committed.getNewObjectId()); + } + + connection.push(NullProgressMonitor.INSTANCE, updates); + return adv; + } + } + + private static boolean isExpectedValue(Map adv, + RemoteRefUpdate u) { + Ref r = adv.get(u.getRemoteName()); + if (!AnyObjectId.equals(getId(r), u.getExpectedOldObjectId())) { + ((RemoteCommand) u).cmd.setResult(LOCK_FAILURE); + return false; + } + return true; + } + + private void prepareCommit(Repository git, List cmds, + Map updates, Map adv, + ObjectId committed) throws IOException { + for (ReceiveCommand cmd : prepareCommit(git, adv, committed)) { + RemoteCommand c = new RemoteCommand(cmd); + cmds.add(c); + updates.put(c.getRemoteName(), c); + } + } + + private static List asUpdateList( + Collection cmds) { + try { + List toPush = new ArrayList<>(cmds.size()); + for (ReceiveCommand cmd : cmds) { + toPush.add(new RemoteCommand(cmd)); + } + return toPush; + } catch (IOException e) { + // Cannot occur as no IO was required to build the command. + throw new IllegalStateException(e); + } + } + + private static Map asUpdateMap( + List cmds) { + Map m = new LinkedHashMap<>(); + for (RemoteCommand cmd : cmds) { + m.put(cmd.getRemoteName(), cmd); + } + return m; + } + + private static void abort(List cmds) { + List tmp = new ArrayList<>(cmds.size()); + for (RemoteCommand cmd : cmds) { + tmp.add(cmd.cmd); + } + ReceiveCommand.abort(tmp); + } + + /** {@inheritDoc} */ + @Override + protected void blockingFetch(Repository repo, ReplicaFetchRequest req) + throws NotSupportedException, TransportException { + try (Transport transport = Transport.open(repo, uri)) { + RemoteConfig rc = getRemoteConfig(); + if (rc != null) { + transport.applyConfig(rc); + } + fetch(transport, req); + } + } + + private void fetch(Transport transport, ReplicaFetchRequest req) + throws NotSupportedException, TransportException { + try (FetchConnection conn = transport.openFetch()) { + Map remoteRefs = conn.getRefsMap(); + req.setRefs(remoteRefs); + + List want = new ArrayList<>(); + for (String name : req.getWantRefs()) { + Ref ref = remoteRefs.get(name); + if (ref != null && ref.getObjectId() != null) { + want.add(ref); + } + } + for (ObjectId id : req.getWantObjects()) { + want.add(new ObjectIdRef.Unpeeled(NETWORK, id.name(), id)); + } + + conn.fetch(NullProgressMonitor.INSTANCE, want, + Collections. emptySet()); + } + } + + static class RemoteCommand extends RemoteRefUpdate { + final ReceiveCommand cmd; + + RemoteCommand(ReceiveCommand cmd) throws IOException { + super(null, null, + cmd.getNewId(), cmd.getRefName(), + true /* force update */, + null /* no local tracking ref */, + cmd.getOldId()); + this.cmd = cmd; + } + + void copyStatusToResult() { + if (cmd.getResult() == NOT_ATTEMPTED) { + switch (getStatus()) { + case OK: + case UP_TO_DATE: + case NON_EXISTING: + cmd.setResult(OK); + break; + + case REJECTED_NODELETE: + cmd.setResult(REJECTED_NODELETE); + break; + + case REJECTED_NONFASTFORWARD: + cmd.setResult(REJECTED_NONFASTFORWARD); + break; + + case REJECTED_OTHER_REASON: + cmd.setResult(REJECTED_OTHER_REASON, getMessage()); + break; + + default: + cmd.setResult(REJECTED_OTHER_REASON, getStatus().name()); + break; + } + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_COMMIT; +import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_SPEED; +import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_TYPE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REMOTE; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod; +import org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed; +import org.eclipse.jgit.internal.ketch.KetchReplica.Participation; +import org.eclipse.jgit.lib.Config; + +/** + * Configures a {@link org.eclipse.jgit.internal.ketch.KetchReplica}. + */ +public class ReplicaConfig { + /** + * Read a configuration from a config block. + * + * @param cfg + * configuration to read. + * @param name + * of the replica being configured. + * @return replica configuration for {@code name}. + */ + public static ReplicaConfig newFromConfig(Config cfg, String name) { + return new ReplicaConfig().fromConfig(cfg, name); + } + + private Participation participation = Participation.FULL; + private CommitMethod commitMethod = CommitMethod.ALL_REFS; + private CommitSpeed commitSpeed = CommitSpeed.BATCHED; + private long minRetry = SECONDS.toMillis(5); + private long maxRetry = MINUTES.toMillis(1); + + /** + * Get participation of the replica in the system. + * + * @return participation of the replica in the system. + */ + public Participation getParticipation() { + return participation; + } + + /** + * Get how Ketch should apply committed changes. + * + * @return how Ketch should apply committed changes. + */ + public CommitMethod getCommitMethod() { + return commitMethod; + } + + /** + * Get how quickly should Ketch commit. + * + * @return how quickly should Ketch commit. + */ + public CommitSpeed getCommitSpeed() { + return commitSpeed; + } + + /** + * Returns the minimum wait delay before retrying a failure. + * + * @param unit + * to get retry delay in. + * @return minimum delay before retrying a failure. + */ + public long getMinRetry(TimeUnit unit) { + return unit.convert(minRetry, MILLISECONDS); + } + + /** + * Returns the maximum wait delay before retrying a failure. + * + * @param unit + * to get retry delay in. + * @return maximum delay before retrying a failure. + */ + public long getMaxRetry(TimeUnit unit) { + return unit.convert(maxRetry, MILLISECONDS); + } + + /** + * Update the configuration from a config block. + * + * @param cfg + * configuration to read. + * @param name + * of the replica being configured. + * @return {@code this} + */ + public ReplicaConfig fromConfig(Config cfg, String name) { + participation = cfg.getEnum( + CONFIG_KEY_REMOTE, name, CONFIG_KEY_TYPE, + participation); + commitMethod = cfg.getEnum( + CONFIG_KEY_REMOTE, name, CONFIG_KEY_COMMIT, + commitMethod); + commitSpeed = cfg.getEnum( + CONFIG_KEY_REMOTE, name, CONFIG_KEY_SPEED, + commitSpeed); + minRetry = getMillis(cfg, name, "ketch-minRetry", minRetry); //$NON-NLS-1$ + maxRetry = getMillis(cfg, name, "ketch-maxRetry", maxRetry); //$NON-NLS-1$ + return this; + } + + private static long getMillis(Config cfg, String name, String key, + long defaultValue) { + String valStr = cfg.getString(CONFIG_KEY_REMOTE, name, key); + if (valStr == null) { + return defaultValue; + } + + valStr = valStr.trim(); + if (valStr.isEmpty()) { + return defaultValue; + } + + Matcher m = UnitMap.PATTERN.matcher(valStr); + if (!m.matches()) { + return defaultValue; + } + + String digits = m.group(1); + String unitName = m.group(2).trim(); + TimeUnit unit = UnitMap.UNITS.get(unitName); + if (unit == null) { + return defaultValue; + } + + try { + if (digits.indexOf('.') == -1) { + return unit.toMillis(Long.parseLong(digits)); + } + + double val = Double.parseDouble(digits); + return (long) (val * unit.toMillis(1)); + } catch (NumberFormatException nfe) { + return defaultValue; + } + } + + static class UnitMap { + static final Pattern PATTERN = Pattern + .compile("^([1-9][0-9]*(?:\\.[0-9]*)?)\\s*(.*)$"); //$NON-NLS-1$ + + static final Map UNITS; + + static { + Map m = new HashMap<>(); + TimeUnit u = MILLISECONDS; + m.put("", u); //$NON-NLS-1$ + m.put("ms", u); //$NON-NLS-1$ + m.put("millis", u); //$NON-NLS-1$ + m.put("millisecond", u); //$NON-NLS-1$ + m.put("milliseconds", u); //$NON-NLS-1$ + + u = SECONDS; + m.put("s", u); //$NON-NLS-1$ + m.put("sec", u); //$NON-NLS-1$ + m.put("secs", u); //$NON-NLS-1$ + m.put("second", u); //$NON-NLS-1$ + m.put("seconds", u); //$NON-NLS-1$ + + u = MINUTES; + m.put("m", u); //$NON-NLS-1$ + m.put("min", u); //$NON-NLS-1$ + m.put("mins", u); //$NON-NLS-1$ + m.put("minute", u); //$NON-NLS-1$ + m.put("minutes", u); //$NON-NLS-1$ + + u = HOURS; + m.put("h", u); //$NON-NLS-1$ + m.put("hr", u); //$NON-NLS-1$ + m.put("hrs", u); //$NON-NLS-1$ + m.put("hour", u); //$NON-NLS-1$ + m.put("hours", u); //$NON-NLS-1$ + + u = DAYS; + m.put("d", u); //$NON-NLS-1$ + m.put("day", u); //$NON-NLS-1$ + m.put("days", u); //$NON-NLS-1$ + + UNITS = Collections.unmodifiableMap(m); + } + + private UnitMap() { + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; + +/** + * A fetch request to obtain objects from a replica, and its result. + */ +public class ReplicaFetchRequest { + private final Set wantRefs; + private final Set wantObjects; + private Map refs; + + /** + * Construct a new fetch request for a replica. + * + * @param wantRefs + * named references to be fetched. + * @param wantObjects + * specific objects to be fetched. + */ + public ReplicaFetchRequest(Set wantRefs, + Set wantObjects) { + this.wantRefs = wantRefs; + this.wantObjects = wantObjects; + } + + /** + * Get references to be fetched. + * + * @return references to be fetched. + */ + public Set getWantRefs() { + return wantRefs; + } + + /** + * Get objects to be fetched. + * + * @return objects to be fetched. + */ + public Set getWantObjects() { + return wantObjects; + } + + /** + * Get remote references, usually from the advertisement. + * + * @return remote references, usually from the advertisement. + */ + @Nullable + public Map getRefs() { + return refs; + } + + /** + * Set references observed from the replica. + * + * @param refs + * references observed from the replica. + */ + public void setRefs(Map refs) { + this.refs = refs; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.util.Collection; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * A push request sending objects to a replica, and its result. + *

+ * Implementors of {@link org.eclipse.jgit.internal.ketch.KetchReplica} must + * populate the command result fields, {@link #setRefs(Map)}, and call one of + * {@link #setException(Repository, Throwable)} or {@link #done(Repository)} to + * finish processing. + */ +public class ReplicaPushRequest { + private final KetchReplica replica; + private final Collection commands; + private Map refs; + private Throwable exception; + private boolean notified; + + /** + * Construct a new push request for a replica. + * + * @param replica + * the replica being pushed to. + * @param commands + * commands to be executed. + */ + public ReplicaPushRequest(KetchReplica replica, + Collection commands) { + this.replica = replica; + this.commands = commands; + } + + /** + * Get commands to be executed, and their results. + * + * @return commands to be executed, and their results. + */ + public Collection getCommands() { + return commands; + } + + /** + * Get remote references, usually from the advertisement. + * + * @return remote references, usually from the advertisement. + */ + @Nullable + public Map getRefs() { + return refs; + } + + /** + * Set references observed from the replica. + * + * @param refs + * references observed from the replica. + */ + public void setRefs(Map refs) { + this.refs = refs; + } + + /** + * Get exception thrown, if any. + * + * @return exception thrown, if any. + */ + @Nullable + public Throwable getException() { + return exception; + } + + /** + * Mark the request as crashing with a communication error. + *

+ * This method may take significant time acquiring the leader lock and + * updating the Ketch state machine with the failure. + * + * @param repo + * local repository reference used by the push attempt. + * @param err + * exception thrown during communication. + */ + public void setException(@Nullable Repository repo, Throwable err) { + if (KetchReplica.log.isErrorEnabled()) { + KetchReplica.log.error(describe("failed"), err); //$NON-NLS-1$ + } + if (!notified) { + notified = true; + exception = err; + replica.afterPush(repo, this); + } + } + + /** + * Mark the request as completed without exception. + *

+ * This method may take significant time acquiring the leader lock and + * updating the Ketch state machine with results from this replica. + * + * @param repo + * local repository reference used by the push attempt. + */ + public void done(Repository repo) { + if (KetchReplica.log.isDebugEnabled()) { + KetchReplica.log.debug(describe("completed")); //$NON-NLS-1$ + } + if (!notified) { + notified = true; + replica.afterPush(repo, this); + } + } + + private String describe(String heading) { + StringBuilder b = new StringBuilder(); + b.append("push to "); //$NON-NLS-1$ + b.append(replica.describeForLog()); + b.append(' ').append(heading).append(":\n"); //$NON-NLS-1$ + for (ReceiveCommand cmd : commands) { + b.append(String.format( + " %-12s %-12s %s %s", //$NON-NLS-1$ + LeaderSnapshot.str(cmd.getOldId()), + LeaderSnapshot.str(cmd.getNewId()), + cmd.getRefName(), + cmd.getResult())); + if (cmd.getMessage() != null) { + b.append(' ').append(cmd.getMessage()); + } + b.append('\n'); + } + return b.toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.util.Date; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.ObjectId; + +/** + * A snapshot of a replica. + * + * @see LeaderSnapshot + */ +public class ReplicaSnapshot { + final KetchReplica replica; + ObjectId accepted; + ObjectId committed; + KetchReplica.State state; + String error; + long retryAtMillis; + + ReplicaSnapshot(KetchReplica replica) { + this.replica = replica; + } + + /** + * Get the replica this snapshot describes the state of + * + * @return the replica this snapshot describes the state of + */ + public KetchReplica getReplica() { + return replica; + } + + /** + * Get current state of the replica + * + * @return current state of the replica + */ + public KetchReplica.State getState() { + return state; + } + + /** + * Get last known Git commit at {@code refs/txn/accepted} + * + * @return last known Git commit at {@code refs/txn/accepted} + */ + @Nullable + public ObjectId getAccepted() { + return accepted; + } + + /** + * Get last known Git commit at {@code refs/txn/committed} + * + * @return last known Git commit at {@code refs/txn/committed} + */ + @Nullable + public ObjectId getCommitted() { + return committed; + } + + /** + * Get error message + * + * @return if {@link #getState()} == + * {@link org.eclipse.jgit.internal.ketch.KetchReplica.State#OFFLINE} + * an optional human-readable message from the transport system + * explaining the failure. + */ + @Nullable + public String getErrorMessage() { + return error; + } + + /** + * Get when the leader will retry communication with the offline or lagging + * replica + * + * @return time (usually in the future) when the leader will retry + * communication with the offline or lagging replica; null if no + * retry is scheduled or necessary. + */ + @Nullable + public Date getRetryAt() { + return retryAtMillis > 0 ? new Date(retryAtMillis) : null; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * One round-trip to all replicas proposing a log entry. + *

+ * In Raft a log entry represents a state transition at a specific index in the + * replicated log. The leader can only append log entries to the log. + *

+ * In Ketch a log entry is recorded under the {@code refs/txn} namespace. This + * occurs when: + *

    + *
  • a replica wants to establish itself as a new leader by proposing a new + * term (see {@link ElectionRound}) + *
  • an established leader wants to gain consensus on new {@link Proposal}s + * (see {@link ProposalRound}) + *
+ */ +abstract class Round { + final KetchLeader leader; + final LogIndex acceptedOldIndex; + LogIndex acceptedNewIndex; + List stageCommands; + + Round(KetchLeader leader, LogIndex head) { + this.leader = leader; + this.acceptedOldIndex = head; + } + + KetchSystem getSystem() { + return leader.getSystem(); + } + + /** + * Creates a commit for {@code refs/txn/accepted} and calls + * {@link #runAsync(AnyObjectId)} to begin execution of the round across + * the system. + *

+ * If references are being updated (such as in a {@link ProposalRound}) the + * RefTree may be modified. + *

+ * Invoked without {@link KetchLeader#lock} to build objects. + * + * @throws IOException + * the round cannot build new objects within the leader's + * repository. The leader may be unable to execute. + */ + abstract void start() throws IOException; + + /** + * Asynchronously distribute the round's new value for + * {@code refs/txn/accepted} to all replicas. + *

+ * Invoked by {@link #start()} after new commits have been created for the + * log. The method passes {@code newId} to {@link KetchLeader} to be + * distributed to all known replicas. + * + * @param newId + * new value for {@code refs/txn/accepted}. + */ + void runAsync(AnyObjectId newId) { + acceptedNewIndex = acceptedOldIndex.nextIndex(newId); + leader.runAsync(this); + } + + /** + * Notify the round it was accepted by a majority of the system. + *

+ * Invoked by the leader with {@link KetchLeader#lock} held by the caller. + */ + abstract void success(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.treewalk.EmptyTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * Constructs a set of commands to stage content during a proposal. + */ +public class StageBuilder { + /** + * Acceptable number of references to send in a single stage transaction. + *

+ * If the number of unique objects exceeds this amount the builder will + * attempt to decrease the reference count by chaining commits.. + */ + private static final int SMALL_BATCH_SIZE = 5; + + /** + * Acceptable number of commits to chain together using parent pointers. + *

+ * When staging many unique commits the {@link StageBuilder} batches + * together unrelated commits as parents of a temporary commit. After the + * proposal completes the temporary commit is discarded and can be garbage + * collected by all replicas. + */ + private static final int TEMP_PARENT_BATCH_SIZE = 128; + + private static final byte[] PEEL = { ' ', '^' }; + + private final String txnStage; + private final String txnId; + + /** + * Construct a stage builder for a transaction. + * + * @param txnStageNamespace + * namespace for transaction references to build + * {@code "txnStageNamespace/txnId.n"} style names. + * @param txnId + * identifier used to name temporary staging refs. + */ + public StageBuilder(String txnStageNamespace, ObjectId txnId) { + this.txnStage = txnStageNamespace; + this.txnId = txnId.name(); + } + + /** + * Compare two RefTrees and return commands to stage new objects. + *

+ * This method ignores the lineage between the two RefTrees and does a + * straight diff on the two trees. New objects will be staged. The diff + * strategy is useful to catch-up a lagging replica, without sending every + * intermediate step. This may mean the replica does not have the same + * object set as other replicas if there are rewinds or branch deletes. + * + * @param git + * source repository to read {@code oldTree} and {@code newTree} + * from. + * @param oldTree + * accepted RefTree on the replica ({@code refs/txn/accepted}). + * Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} if the + * remote does not have any ref tree, e.g. a new replica catching + * up. + * @param newTree + * RefTree being sent to the replica. The trees will be compared. + * @return list of commands to create {@code "refs/txn/stage/..."} + * references on replicas anchoring new objects into the repository + * while a transaction gains consensus. + * @throws java.io.IOException + * {@code git} cannot be accessed to compare {@code oldTree} and + * {@code newTree} to build the object set. + */ + public List makeStageList(Repository git, ObjectId oldTree, + ObjectId newTree) throws IOException { + try (RevWalk rw = new RevWalk(git); + TreeWalk tw = new TreeWalk(rw.getObjectReader()); + ObjectInserter ins = git.newObjectInserter()) { + if (AnyObjectId.equals(oldTree, ObjectId.zeroId())) { + tw.addTree(new EmptyTreeIterator()); + } else { + tw.addTree(rw.parseTree(oldTree)); + } + tw.addTree(rw.parseTree(newTree)); + tw.setFilter(TreeFilter.ANY_DIFF); + tw.setRecursive(true); + + Set newObjs = new HashSet<>(); + while (tw.next()) { + if (tw.getRawMode(1) == TYPE_GITLINK + && !tw.isPathSuffix(PEEL, 2)) { + newObjs.add(tw.getObjectId(1)); + } + } + + List cmds = makeStageList(newObjs, git, ins); + ins.flush(); + return cmds; + } + } + + /** + * Construct a set of commands to stage objects on a replica. + * + * @param newObjs + * objects to send to a replica. + * @param git + * local repository to read source objects from. Required to + * perform minification of {@code newObjs}. + * @param inserter + * inserter to write temporary commit objects during minification + * if many new branches are created by {@code newObjs}. + * @return list of commands to create {@code "refs/txn/stage/..."} + * references on replicas anchoring {@code newObjs} into the + * repository while a transaction gains consensus. + * @throws java.io.IOException + * {@code git} cannot be accessed to perform minification of + * {@code newObjs}. + */ + public List makeStageList(Set newObjs, + @Nullable Repository git, @Nullable ObjectInserter inserter) + throws IOException { + if (git == null || newObjs.size() <= SMALL_BATCH_SIZE) { + // Without a source repository can only construct unique set. + List cmds = new ArrayList<>(newObjs.size()); + for (ObjectId id : newObjs) { + stage(cmds, id); + } + return cmds; + } + + List cmds = new ArrayList<>(); + List commits = new ArrayList<>(); + reduceObjects(cmds, commits, git, newObjs); + + if (inserter == null || commits.size() <= 1 + || (cmds.size() + commits.size()) <= SMALL_BATCH_SIZE) { + // Without an inserter to aggregate commits, or for a small set of + // commits just send one stage ref per commit. + for (RevCommit c : commits) { + stage(cmds, c.copy()); + } + return cmds; + } + + // 'commits' is sorted most recent to least recent commit. + // Group batches of commits and build a chain. + // TODO(sop) Cluster by restricted graphs to support filtering. + ObjectId tip = null; + for (int end = commits.size(); end > 0;) { + int start = Math.max(0, end - TEMP_PARENT_BATCH_SIZE); + List batch = commits.subList(start, end); + List parents = new ArrayList<>(1 + batch.size()); + if (tip != null) { + parents.add(tip); + } + parents.addAll(batch); + + CommitBuilder b = new CommitBuilder(); + b.setTreeId(batch.get(0).getTree()); + b.setParentIds(parents); + b.setAuthor(tmpAuthor(batch)); + b.setCommitter(b.getAuthor()); + tip = inserter.insert(b); + end = start; + } + stage(cmds, tip); + return cmds; + } + + private static PersonIdent tmpAuthor(List commits) { + // Construct a predictable author using most recent commit time. + int t = 0; + for (int i = 0; i < commits.size();) { + t = Math.max(t, commits.get(i).getCommitTime()); + } + String name = "Ketch Stage"; //$NON-NLS-1$ + String email = "tmp@tmp"; //$NON-NLS-1$ + return new PersonIdent(name, email, t * 1000L, 0); + } + + private void reduceObjects(List cmds, + List commits, Repository git, + Set newObjs) throws IOException { + try (RevWalk rw = new RevWalk(git)) { + rw.setRetainBody(false); + + for (ObjectId id : newObjs) { + RevObject obj = rw.parseAny(id); + if (obj instanceof RevCommit) { + rw.markStart((RevCommit) obj); + } else { + stage(cmds, id); + } + } + + for (RevCommit c; (c = rw.next()) != null;) { + commits.add(c); + rw.markUninteresting(c); + } + } + } + + private void stage(List cmds, ObjectId id) { + int estLen = txnStage.length() + txnId.length() + 5; + StringBuilder n = new StringBuilder(estLen); + n.append(txnStage).append(txnId).append('.'); + n.append(Integer.toHexString(cmds.size())); + cmds.add(new ReceiveCommand(ObjectId.zeroId(), id, n.toString())); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/TimeIsUncertainException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/TimeIsUncertainException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/TimeIsUncertainException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/TimeIsUncertainException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016, 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.internal.ketch; + +import java.io.IOException; + +import org.eclipse.jgit.internal.JGitText; + +class TimeIsUncertainException extends IOException { + private static final long serialVersionUID = 1L; + + TimeIsUncertainException() { + super(JGitText.get().timeIsUncertain); + } + + TimeIsUncertainException(Exception e) { + super(JGitText.get().timeIsUncertain, e); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017, 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.internal.revwalk; + +import org.eclipse.jgit.lib.BitmapIndex.Bitmap; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevFlag; + +/** + * A RevFilter that adds the visited commits to {@code bitmap} as a side + * effect. + *

+ * When the walk hits a commit that is part of {@code bitmap}'s + * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the + * commit and its parents are marked as SEEN so that the walk does not + * have to visit its ancestors. This ensures the walk is very short if + * there is good bitmap coverage. + */ +public class AddToBitmapFilter extends RevFilter { + private final BitmapBuilder bitmap; + + /** + * Create a filter that adds visited commits to the given bitmap. + * + * @param bitmap bitmap to write visited commits to + */ + public AddToBitmapFilter(BitmapBuilder bitmap) { + this.bitmap = bitmap; + } + + /** {@inheritDoc} */ + @Override + public final boolean include(RevWalk walker, RevCommit cmit) { + Bitmap visitedBitmap; + + if (bitmap.contains(cmit)) { + // already included + } else if ((visitedBitmap = bitmap.getBitmapIndex() + .getBitmap(cmit)) != null) { + bitmap.or(visitedBitmap); + } else { + bitmap.addObject(cmit, Constants.OBJ_COMMIT); + return true; + } + + for (RevCommit p : cmit.getParents()) { + p.add(RevFlag.SEEN); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public final RevFilter clone() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public final boolean requiresCommitBody() { + return false; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017, 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.internal.revwalk; + +import org.eclipse.jgit.lib.BitmapIndex.Bitmap; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevFlag; + +/** + * A RevFilter that adds the visited commits to {@code bitmap} as a side + * effect. + *

+ * When the walk hits a commit that is part of {@code bitmap}'s + * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the + * commit and its parents are marked as SEEN so that the walk does not + * have to visit its ancestors. This ensures the walk is very short if + * there is good bitmap coverage. + *

+ * Commits named in {@code seen} are considered already seen. If one is + * encountered, that commit and its parents will be marked with the SEEN + * flag to prevent the walk from visiting its ancestors. + */ +public class AddUnseenToBitmapFilter extends RevFilter { + private final BitmapBuilder seen; + private final BitmapBuilder bitmap; + + /** + * Create a filter that adds visited commits to the given bitmap, but does not walk + * through the objects in {@code seen}. + * + * @param seen objects that are already seen + * @param bitmap bitmap to write visited commits to + */ + public AddUnseenToBitmapFilter(BitmapBuilder seen, BitmapBuilder bitmap) { + this.seen = seen; + this.bitmap = bitmap; + } + + /** {@inheritDoc} */ + @Override + public final boolean include(RevWalk walker, RevCommit cmit) { + Bitmap visitedBitmap; + + if (seen.contains(cmit) || bitmap.contains(cmit)) { + // already seen or included + } else if ((visitedBitmap = bitmap.getBitmapIndex() + .getBitmap(cmit)) != null) { + bitmap.or(visitedBitmap); + } else { + bitmap.addObject(cmit, Constants.OBJ_COMMIT); + return true; + } + + for (RevCommit p : cmit.getParents()) { + p.add(RevFlag.SEEN); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public final RevFilter clone() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public final boolean requiresCommitBody() { + return false; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,10 +46,10 @@ import org.eclipse.jgit.events.RepositoryEvent; /** - * Describes the {@link DfsPackFile} just before its index is loaded. Currently, - * DfsPackFile directly dispatches the event on - * {@link org.eclipse.jgit.lib.Repository#getGlobalListenerList}. Which means - * the call to {@link #getRepository} will always return null. + * Describes the {@link org.eclipse.jgit.internal.storage.dfs.DfsPackFile} just + * before its index is loaded. Currently, DfsPackFile directly dispatches the + * event on {@link org.eclipse.jgit.lib.Repository#getGlobalListenerList}. Which + * means the call to {@link #getRepository} will always return null. */ public class BeforeDfsPackIndexLoadedEvent extends RepositoryEvent { @@ -65,16 +65,22 @@ this.pack = pack; } - /** @return the PackFile containing the index that will be loaded. */ + /** + * Get the PackFile containing the index that will be loaded. + * + * @return the PackFile containing the index that will be loaded. + */ public DfsPackFile getPackFile() { return pack; } + /** {@inheritDoc} */ @Override public Class getListenerType() { return BeforeDfsPackIndexLoadedListener.class; } + /** {@inheritDoc} */ @Override public void dispatch(BeforeDfsPackIndexLoadedListener listener) { listener.onBeforeDfsPackIndexLoaded(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,9 +46,8 @@ import org.eclipse.jgit.events.RepositoryListener; /** - * Receives {@link BeforeDfsPackIndexLoadedEvent}s. - * - * @since 2.2 + * Receives + * {@link org.eclipse.jgit.internal.storage.dfs.BeforeDfsPackIndexLoadedEvent}s. */ public interface BeforeDfsPackIndexLoadedListener extends RepositoryListener { /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.PackInvalidException; +import org.eclipse.jgit.internal.storage.pack.PackExt; + +/** Block based file stored in {@link DfsBlockCache}. */ +abstract class BlockBasedFile { + /** Cache that owns this file and its data. */ + final DfsBlockCache cache; + + /** Unique identity of this file while in-memory. */ + final DfsStreamKey key; + + /** Description of the associated pack file's storage. */ + final DfsPackDescription desc; + final PackExt ext; + + /** + * Preferred alignment for loading blocks from the backing file. + *

+ * It is initialized to 0 and filled in on the first read made from the + * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS + * storing 4091 user bytes and 5 bytes block metadata into a lower level + * 4096 byte block on disk. + */ + volatile int blockSize; + + /** + * Total number of bytes in this pack file. + *

+ * This field initializes to -1 and gets populated when a block is loaded. + */ + volatile long length; + + /** True once corruption has been detected that cannot be worked around. */ + volatile boolean invalid; + + /** Exception that caused the packfile to be flagged as invalid */ + protected volatile Exception invalidatingCause; + + BlockBasedFile(DfsBlockCache cache, DfsPackDescription desc, PackExt ext) { + this.cache = cache; + this.key = desc.getStreamKey(ext); + this.desc = desc; + this.ext = ext; + } + + String getFileName() { + return desc.getFileName(ext); + } + + boolean invalid() { + return invalid; + } + + void setInvalid() { + invalid = true; + } + + void setBlockSize(int newSize) { + blockSize = newSize; + } + + long alignToBlock(long pos) { + int size = blockSize; + if (size == 0) + size = cache.getBlockSize(); + return (pos / size) * size; + } + + int blockSize(ReadableChannel rc) { + // If the block alignment is not yet known, discover it. Prefer the + // larger size from either the cache or the file itself. + int size = blockSize; + if (size == 0) { + size = rc.blockSize(); + if (size <= 0) + size = cache.getBlockSize(); + else if (size < cache.getBlockSize()) + size = (cache.getBlockSize() / size) * size; + blockSize = size; + } + return size; + } + + DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException { + return cache.getOrLoad(this, pos, ctx, null); + } + + DfsBlock readOneBlock(long pos, DfsReader ctx, + @Nullable ReadableChannel fileChannel) throws IOException { + if (invalid) { + throw new PackInvalidException(getFileName(), invalidatingCause); + } + + ctx.stats.readBlock++; + long start = System.nanoTime(); + ReadableChannel rc = fileChannel != null ? fileChannel + : ctx.db.openFile(desc, ext); + try { + int size = blockSize(rc); + pos = (pos / size) * size; + + // If the size of the file is not yet known, try to discover it. + // Channels may choose to return -1 to indicate they don't + // know the length yet, in this case read up to the size unit + // given by the caller, then recheck the length. + long len = length; + if (len < 0) { + len = rc.size(); + if (0 <= len) + length = len; + } + + if (0 <= len && len < pos + size) + size = (int) (len - pos); + if (size <= 0) + throw new EOFException(MessageFormat.format( + DfsText.get().shortReadOfBlock, Long.valueOf(pos), + getFileName(), Long.valueOf(0), Long.valueOf(0))); + + byte[] buf = new byte[size]; + rc.position(pos); + int cnt = read(rc, ByteBuffer.wrap(buf, 0, size)); + ctx.stats.readBlockBytes += cnt; + if (cnt != size) { + if (0 <= len) { + throw new EOFException(MessageFormat.format( + DfsText.get().shortReadOfBlock, Long.valueOf(pos), + getFileName(), Integer.valueOf(size), + Integer.valueOf(cnt))); + } + + // Assume the entire thing was read in a single shot, compact + // the buffer to only the space required. + byte[] n = new byte[cnt]; + System.arraycopy(buf, 0, n, 0, n.length); + buf = n; + } else if (len < 0) { + // With no length at the start of the read, the channel should + // have the length available at the end. + length = len = rc.size(); + } + + return new DfsBlock(key, pos, buf); + } finally { + if (rc != fileChannel) { + rc.close(); + } + ctx.stats.readBlockMicros += elapsedMicros(start); + } + } + + static int read(ReadableChannel rc, ByteBuffer buf) throws IOException { + int n; + do { + n = rc.read(buf); + } while (0 < n && buf.hasRemaining()); + return buf.position(); + } + + static long elapsedMicros(long start) { + return (System.nanoTime() - start) / 1000L; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,7 +75,7 @@ table = new Entry[1 << TABLE_BITS]; } - Entry get(DfsPackKey key, long position) { + Entry get(DfsStreamKey key, long position) { Entry e = table[hash(position)]; for (; e != null; e = e.tableNext) { if (e.offset == position && key.equals(e.pack)) { @@ -86,7 +86,7 @@ return null; } - void put(DfsPackKey key, long offset, int objectType, byte[] data) { + void put(DfsStreamKey key, long offset, int objectType, byte[] data) { if (data.length > maxByteCount) return; // Too large to cache. @@ -189,7 +189,7 @@ } static class Entry { - final DfsPackKey pack; + final DfsStreamKey pack; final long offset; final int type; final byte[] data; @@ -198,7 +198,7 @@ Entry lruPrev; Entry lruNext; - Entry(DfsPackKey key, long offset, int type, byte[] data) { + Entry(DfsStreamKey key, long offset, int type, byte[] data) { this.pack = key; this.offset = offset; this.type = type; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONCURRENCY_LEVEL; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO; import java.text.MessageFormat; @@ -54,7 +55,10 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; -/** Configuration parameters for {@link DfsBlockCache}. */ +/** + * Configuration parameters for + * {@link org.eclipse.jgit.internal.storage.dfs.DfsBlockCache}. + */ public class DfsBlockCacheConfig { /** 1024 (number of bytes in one kibibyte/kilobyte) */ public static final int KB = 1024; @@ -65,15 +69,22 @@ private long blockLimit; private int blockSize; private double streamRatio; + private int concurrencyLevel; - /** Create a default configuration. */ + /** + * Create a default configuration. + */ public DfsBlockCacheConfig() { setBlockLimit(32 * MB); setBlockSize(64 * KB); setStreamRatio(0.30); + setConcurrencyLevel(32); } /** + * Get maximum number bytes of heap memory to dedicate to caching pack file + * data. + * * @return maximum number bytes of heap memory to dedicate to caching pack * file data. Default is 32 MB. */ @@ -82,17 +93,33 @@ } /** + * Set maximum number bytes of heap memory to dedicate to caching pack file + * data. + *

+ * It is strongly recommended to set the block limit to be an integer multiple + * of the block size. This constraint is not enforced by this method (since + * it may be called before {@link #setBlockSize(int)}), but it is enforced by + * {@link #fromConfig(Config)}. + * * @param newLimit * maximum number bytes of heap memory to dedicate to caching - * pack file data. + * pack file data; must be positive. * @return {@code this} */ public DfsBlockCacheConfig setBlockLimit(final long newLimit) { + if (newLimit <= 0) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().blockLimitNotPositive, + Long.valueOf(newLimit))); + } blockLimit = newLimit; return this; } /** + * Get size in bytes of a single window mapped or read in from the pack + * file. + * * @return size in bytes of a single window mapped or read in from the pack * file. Default is 64 KB. */ @@ -101,30 +128,65 @@ } /** + * Set size in bytes of a single window read in from the pack file. + * * @param newSize * size in bytes of a single window read in from the pack file. + * The value must be a power of 2. * @return {@code this} */ public DfsBlockCacheConfig setBlockSize(final int newSize) { - blockSize = Math.max(512, newSize); + int size = Math.max(512, newSize); + if ((size & (size - 1)) != 0) { + throw new IllegalArgumentException( + JGitText.get().blockSizeNotPowerOf2); + } + blockSize = size; return this; } /** + * Get the estimated number of threads concurrently accessing the cache. + * + * @return the estimated number of threads concurrently accessing the cache. + * Default is 32. + */ + public int getConcurrencyLevel() { + return concurrencyLevel; + } + + /** + * Set the estimated number of threads concurrently accessing the cache. + * + * @param newConcurrencyLevel + * the estimated number of threads concurrently accessing the + * cache. + * @return {@code this} + */ + public DfsBlockCacheConfig setConcurrencyLevel( + final int newConcurrencyLevel) { + concurrencyLevel = newConcurrencyLevel; + return this; + } + + /** + * Get highest percentage of {@link #getBlockLimit()} a single pack can + * occupy while being copied by the pack reuse strategy. + * * @return highest percentage of {@link #getBlockLimit()} a single pack can * occupy while being copied by the pack reuse strategy. Default * is 0.30, or 30%. - * @since 4.0 */ public double getStreamRatio() { return streamRatio; } /** + * Set percentage of cache to occupy with a copied pack. + * * @param ratio * percentage of cache to occupy with a copied pack. * @return {@code this} - * @since 4.0 */ public DfsBlockCacheConfig setStreamRatio(double ratio) { streamRatio = Math.max(0, Math.min(ratio, 1.0)); @@ -136,23 +198,40 @@ *

* If a property is not defined in the configuration, then it is left * unmodified. + *

+ * Enforces certain constraints on the combination of settings in the config, + * for example that the block limit is a multiple of the block size. * * @param rc * configuration to read properties from. * @return {@code this} */ public DfsBlockCacheConfig fromConfig(final Config rc) { - setBlockLimit(rc.getLong( + long cfgBlockLimit = rc.getLong( CONFIG_CORE_SECTION, CONFIG_DFS_SECTION, CONFIG_KEY_BLOCK_LIMIT, - getBlockLimit())); - - setBlockSize(rc.getInt( + getBlockLimit()); + int cfgBlockSize = rc.getInt( CONFIG_CORE_SECTION, CONFIG_DFS_SECTION, CONFIG_KEY_BLOCK_SIZE, - getBlockSize())); + getBlockSize()); + if (cfgBlockLimit % cfgBlockSize != 0) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().blockLimitNotMultipleOfBlockSize, + Long.valueOf(cfgBlockLimit), + Long.valueOf(cfgBlockSize))); + } + + setBlockLimit(cfgBlockLimit); + setBlockSize(cfgBlockSize); + + setConcurrencyLevel(rc.getInt( + CONFIG_CORE_SECTION, + CONFIG_DFS_SECTION, + CONFIG_KEY_CONCURRENCY_LEVEL, + getConcurrencyLevel())); String v = rc.getString( CONFIG_CORE_SECTION, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,27 +45,29 @@ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.LongStream; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.pack.PackExt; /** - * Caches slices of a {@link DfsPackFile} in memory for faster read access. + * Caches slices of a + * {@link org.eclipse.jgit.internal.storage.dfs.BlockBasedFile} in memory for + * faster read access. *

* The DfsBlockCache serves as a Java based "buffer cache", loading segments of - * a DfsPackFile into the JVM heap prior to use. As JGit often wants to do reads - * of only tiny slices of a file, the DfsBlockCache tries to smooth out these - * tiny reads into larger block-sized IO operations. + * a BlockBasedFile into the JVM heap prior to use. As JGit often wants to do + * reads of only tiny slices of a file, the DfsBlockCache tries to smooth out + * these tiny reads into larger block-sized IO operations. *

* Whenever a cache miss occurs, loading is invoked by exactly one thread for - * the given (DfsPackKey,position) key tuple. This is ensured by an - * array of locks, with the tuple hashed to a lock instance. + * the given (DfsStreamKey,position) key tuple. This is ensured by + * an array of locks, with the tuple hashed to a lock instance. *

* Its too expensive during object access to be accurate with a least recently * used (LRU) algorithm. Strictly ordering every read is a lot of overhead that @@ -103,22 +105,19 @@ * * @param cfg * the new window cache configuration. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the cache configuration contains one or more invalid * settings, usually too low of a limit. */ public static void reconfigure(DfsBlockCacheConfig cfg) { - DfsBlockCache nc = new DfsBlockCache(cfg); - DfsBlockCache oc = cache; - cache = nc; - - if (oc != null) { - for (DfsPackFile pack : oc.getPackFiles()) - pack.key.cachedSize.set(0); - } + cache = new DfsBlockCache(cfg); } - /** @return the currently active DfsBlockCache. */ + /** + * Get the currently active DfsBlockCache. + * + * @return the currently active DfsBlockCache. + */ public static DfsBlockCache getInstance() { return cache; } @@ -145,26 +144,35 @@ *

* If a pack file has a native size, a whole multiple of the native size * will be used until it matches this size. + *

+ * The value for blockSize must be a power of 2. */ private final int blockSize; /** As {@link #blockSize} is a power of 2, bits to shift for a / blockSize. */ private final int blockSizeShift; - /** Cache of pack files, indexed by description. */ - private final Map packCache; - - /** View of pack files in the pack cache. */ - private final Collection packFiles; + /** + * Number of times a block was found in the cache, per pack file extension. + */ + private final AtomicReference statHit; - /** Number of times a block was found in the cache. */ - private final AtomicLong statHit; + /** + * Number of times a block was not found, and had to be loaded, per pack + * file extension. + */ + private final AtomicReference statMiss; - /** Number of times a block was not found, and had to be loaded. */ - private final AtomicLong statMiss; + /** + * Number of blocks evicted due to cache being full, per pack file + * extension. + */ + private final AtomicReference statEvict; - /** Number of blocks evicted due to cache being full. */ - private volatile long statEvict; + /** + * Number of bytes currently loaded in the cache, per pack file extension. + */ + private final AtomicReference liveBytes; /** Protects the clock and its related data. */ private final ReentrantLock clockLock; @@ -172,116 +180,146 @@ /** Current position of the clock. */ private Ref clockHand; - /** Number of bytes currently loaded in the cache. */ - private volatile long liveBytes; - + @SuppressWarnings("unchecked") private DfsBlockCache(final DfsBlockCacheConfig cfg) { tableSize = tableSize(cfg); if (tableSize < 1) throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); - table = new AtomicReferenceArray(tableSize); - loadLocks = new ReentrantLock[32]; + table = new AtomicReferenceArray<>(tableSize); + loadLocks = new ReentrantLock[cfg.getConcurrencyLevel()]; for (int i = 0; i < loadLocks.length; i++) loadLocks[i] = new ReentrantLock(true /* fair */); - int eb = (int) (tableSize * .1); - if (64 < eb) - eb = 64; - else if (eb < 4) - eb = 4; - if (tableSize < eb) - eb = tableSize; - maxBytes = cfg.getBlockLimit(); maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio()); blockSize = cfg.getBlockSize(); blockSizeShift = Integer.numberOfTrailingZeros(blockSize); clockLock = new ReentrantLock(true /* fair */); - clockHand = new Ref(new DfsPackKey(), -1, 0, null); + String none = ""; //$NON-NLS-1$ + clockHand = new Ref<>( + DfsStreamKey.of(new DfsRepositoryDescription(none), none, null), + -1, 0, null); clockHand.next = clockHand; - packCache = new ConcurrentHashMap( - 16, 0.75f, 1); - packFiles = Collections.unmodifiableCollection(packCache.values()); - - statHit = new AtomicLong(); - statMiss = new AtomicLong(); + statHit = new AtomicReference<>(newCounters()); + statMiss = new AtomicReference<>(newCounters()); + statEvict = new AtomicReference<>(newCounters()); + liveBytes = new AtomicReference<>(newCounters()); } boolean shouldCopyThroughCache(long length) { return length <= maxStreamThroughCache; } - /** @return total number of bytes in the cache. */ - public long getCurrentSize() { - return liveBytes; + /** + * Get total number of bytes in the cache, per pack file extension. + * + * @return total number of bytes in the cache, per pack file extension. + */ + public long[] getCurrentSize() { + return getStatVals(liveBytes); } - /** @return 0..100, defining how full the cache is. */ + /** + * Get 0..100, defining how full the cache is. + * + * @return 0..100, defining how full the cache is. + */ public long getFillPercentage() { - return getCurrentSize() * 100 / maxBytes; - } - - /** @return number of requests for items in the cache. */ - public long getHitCount() { - return statHit.get(); + return LongStream.of(getCurrentSize()).sum() * 100 / maxBytes; } - /** @return number of requests for items not in the cache. */ - public long getMissCount() { - return statMiss.get(); + /** + * Get number of requests for items in the cache, per pack file extension. + * + * @return number of requests for items in the cache, per pack file + * extension. + */ + public long[] getHitCount() { + return getStatVals(statHit); } - /** @return total number of requests (hit + miss). */ - public long getTotalRequestCount() { - return getHitCount() + getMissCount(); + /** + * Get number of requests for items not in the cache, per pack file + * extension. + * + * @return number of requests for items not in the cache, per pack file + * extension. + */ + public long[] getMissCount() { + return getStatVals(statMiss); } - /** @return 0..100, defining number of cache hits. */ - public long getHitRatio() { - long hits = statHit.get(); - long miss = statMiss.get(); - long total = hits + miss; - if (total == 0) - return 0; - return hits * 100 / total; + /** + * Get total number of requests (hit + miss), per pack file extension. + * + * @return total number of requests (hit + miss), per pack file extension. + */ + public long[] getTotalRequestCount() { + AtomicLong[] hit = statHit.get(); + AtomicLong[] miss = statMiss.get(); + long[] cnt = new long[Math.max(hit.length, miss.length)]; + for (int i = 0; i < hit.length; i++) { + cnt[i] += hit[i].get(); + } + for (int i = 0; i < miss.length; i++) { + cnt[i] += miss[i].get(); + } + return cnt; } - /** @return number of evictions performed due to cache being full. */ - public long getEvictions() { - return statEvict; + /** + * Get hit ratios + * + * @return hit ratios + */ + public long[] getHitRatio() { + AtomicLong[] hit = statHit.get(); + AtomicLong[] miss = statMiss.get(); + long[] ratio = new long[Math.max(hit.length, miss.length)]; + for (int i = 0; i < ratio.length; i++) { + if (i >= hit.length) { + ratio[i] = 0; + } else if (i >= miss.length) { + ratio[i] = 100; + } else { + long hitVal = hit[i].get(); + long missVal = miss[i].get(); + long total = hitVal + missVal; + ratio[i] = total == 0 ? 0 : hitVal * 100 / total; + } + } + return ratio; } /** - * Get the pack files stored in this cache. + * Get number of evictions performed due to cache being full, per pack file + * extension. * - * @return a collection of pack files, some of which may not actually be - * present; the caller should check the pack's cached size. + * @return number of evictions performed due to cache being full, per pack + * file extension. */ - public Collection getPackFiles() { - return packFiles; + public long[] getEvictions() { + return getStatVals(statEvict); } - DfsPackFile getOrCreate(DfsPackDescription dsc, DfsPackKey key) { - // TODO This table grows without bound. It needs to clean up - // entries that aren't in cache anymore, and aren't being used - // by a live DfsObjDatabase reference. - synchronized (packCache) { - DfsPackFile pack = packCache.get(dsc); - if (pack != null && pack.invalid()) { - packCache.remove(dsc); - pack = null; - } - if (pack == null) { - if (key == null) - key = new DfsPackKey(); - pack = new DfsPackFile(this, dsc, key); - packCache.put(dsc, pack); - } - return pack; - } + /** + * Quickly check if the cache contains block 0 of the given stream. + *

+ * This can be useful for sophisticated pre-read algorithms to quickly + * determine if a file is likely already in cache, especially small + * reftables which may be smaller than a typical DFS block size. + * + * @param key + * the file to check. + * @return true if block 0 (the first block) is in the cache. + */ + public boolean hasBlock0(DfsStreamKey key) { + HashEntry e1 = table.get(slot(key, 0)); + DfsBlock v = scan(e1, key, 0); + return v != null && v.contains(key, 0); } private int hash(int packHash, long off) { @@ -305,31 +343,34 @@ /** * Lookup a cached object, creating and loading it if it doesn't exist. * - * @param pack + * @param file * the pack that "contains" the cached object. * @param position * offset within pack of the object. * @param ctx * current thread's reader. + * @param fileChannel + * optional channel to read {@code pack}. * @return the object reference. * @throws IOException * the reference was not in the cache and could not be loaded. */ - DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx) - throws IOException { + DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx, + @Nullable ReadableChannel fileChannel) throws IOException { final long requestedPosition = position; - position = pack.alignToBlock(position); + position = file.alignToBlock(position); - DfsPackKey key = pack.key; + DfsStreamKey key = file.key; int slot = slot(key, position); HashEntry e1 = table.get(slot); DfsBlock v = scan(e1, key, position); - if (v != null) { - statHit.incrementAndGet(); + if (v != null && v.contains(key, requestedPosition)) { + ctx.stats.blockCacheHit++; + getStat(statHit, key).incrementAndGet(); return v; } - reserveSpace(blockSize); + reserveSpace(blockSize, key); ReentrantLock regionLock = lockFor(key, position); regionLock.lock(); try { @@ -337,20 +378,21 @@ if (e2 != e1) { v = scan(e2, key, position); if (v != null) { - statHit.incrementAndGet(); - creditSpace(blockSize); + ctx.stats.blockCacheHit++; + getStat(statHit, key).incrementAndGet(); + creditSpace(blockSize, key); return v; } } - statMiss.incrementAndGet(); + getStat(statMiss, key).incrementAndGet(); boolean credit = true; try { - v = pack.readOneBlock(position, ctx); + v = file.readOneBlock(requestedPosition, ctx, fileChannel); credit = false; } finally { if (credit) - creditSpace(blockSize); + creditSpace(blockSize, key); } if (position != v.start) { // The file discovered its blockSize and adjusted. @@ -359,8 +401,7 @@ e2 = table.get(slot); } - key.cachedSize.addAndGet(v.size()); - Ref ref = new Ref(key, position, v.size(), v); + Ref ref = new Ref<>(key, position, v.size(), v); ref.hot = true; for (;;) { HashEntry n = new HashEntry(clean(e2), ref); @@ -375,16 +416,16 @@ // If the block size changed from the default, it is possible the block // that was loaded is the wrong block for the requested position. - if (v.contains(pack.key, requestedPosition)) + if (v.contains(file.key, requestedPosition)) return v; - return getOrLoad(pack, requestedPosition, ctx); + return getOrLoad(file, requestedPosition, ctx, fileChannel); } @SuppressWarnings("unchecked") - private void reserveSpace(int reserve) { + private void reserveSpace(int reserve, DfsStreamKey key) { clockLock.lock(); try { - long live = liveBytes + reserve; + long live = LongStream.of(getCurrentSize()).sum() + reserve; if (maxBytes < live) { Ref prev = clockHand; Ref hand = clockHand.next; @@ -407,28 +448,33 @@ dead.next = null; dead.value = null; live -= dead.size; - dead.pack.cachedSize.addAndGet(-dead.size); - statEvict++; + getStat(liveBytes, dead.key).addAndGet(-dead.size); + getStat(statEvict, dead.key).incrementAndGet(); } while (maxBytes < live); clockHand = prev; } - liveBytes = live; + getStat(liveBytes, key).addAndGet(reserve); } finally { clockLock.unlock(); } } - private void creditSpace(int credit) { + private void creditSpace(int credit, DfsStreamKey key) { clockLock.lock(); - liveBytes -= credit; - clockLock.unlock(); + try { + getStat(liveBytes, key).addAndGet(-credit); + } finally { + clockLock.unlock(); + } } + @SuppressWarnings("unchecked") private void addToClock(Ref ref, int credit) { clockLock.lock(); try { - if (credit != 0) - liveBytes -= credit; + if (credit != 0) { + getStat(liveBytes, ref.key).addAndGet(-credit); + } Ref ptr = clockHand; ref.next = ptr.next; ptr.next = ref; @@ -439,17 +485,21 @@ } void put(DfsBlock v) { - put(v.pack, v.start, v.size(), v); + put(v.stream, v.start, v.size(), v); + } + + Ref putRef(DfsStreamKey key, long size, T v) { + return put(key, 0, (int) Math.min(size, Integer.MAX_VALUE), v); } - Ref put(DfsPackKey key, long pos, int size, T v) { + Ref put(DfsStreamKey key, long pos, int size, T v) { int slot = slot(key, pos); HashEntry e1 = table.get(slot); Ref ref = scanRef(e1, key, pos); if (ref != null) return ref; - reserveSpace(size); + reserveSpace(size, key); ReentrantLock regionLock = lockFor(key, pos); regionLock.lock(); try { @@ -457,13 +507,12 @@ if (e2 != e1) { ref = scanRef(e2, key, pos); if (ref != null) { - creditSpace(size); + creditSpace(size, key); return ref; } } - key.cachedSize.addAndGet(size); - ref = new Ref(key, pos, size, v); + ref = new Ref<>(key, pos, size, v); ref.hot = true; for (;;) { HashEntry n = new HashEntry(clean(e2), ref); @@ -478,47 +527,87 @@ return ref; } - boolean contains(DfsPackKey key, long position) { + boolean contains(DfsStreamKey key, long position) { return scan(table.get(slot(key, position)), key, position) != null; } @SuppressWarnings("unchecked") - T get(DfsPackKey key, long position) { + T get(DfsStreamKey key, long position) { T val = (T) scan(table.get(slot(key, position)), key, position); if (val == null) - statMiss.incrementAndGet(); + getStat(statMiss, key).incrementAndGet(); else - statHit.incrementAndGet(); + getStat(statHit, key).incrementAndGet(); return val; } - private T scan(HashEntry n, DfsPackKey pack, long position) { - Ref r = scanRef(n, pack, position); + private T scan(HashEntry n, DfsStreamKey key, long position) { + Ref r = scanRef(n, key, position); return r != null ? r.get() : null; } + Ref getRef(DfsStreamKey key) { + Ref r = scanRef(table.get(slot(key, 0)), key, 0); + if (r != null) + getStat(statHit, key).incrementAndGet(); + else + getStat(statMiss, key).incrementAndGet(); + return r; + } + @SuppressWarnings("unchecked") - private Ref scanRef(HashEntry n, DfsPackKey pack, long position) { + private Ref scanRef(HashEntry n, DfsStreamKey key, long position) { for (; n != null; n = n.next) { Ref r = n.ref; - if (r.pack == pack && r.position == position) + if (r.position == position && r.key.equals(key)) return r.get() != null ? r : null; } return null; } - void remove(DfsPackFile pack) { - synchronized (packCache) { - packCache.remove(pack.getPackDescription()); + private int slot(DfsStreamKey key, long position) { + return (hash(key.hash, position) >>> 1) % tableSize; + } + + private ReentrantLock lockFor(DfsStreamKey key, long position) { + return loadLocks[(hash(key.hash, position) >>> 1) % loadLocks.length]; + } + + private static AtomicLong[] newCounters() { + AtomicLong[] ret = new AtomicLong[PackExt.values().length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = new AtomicLong(); } + return ret; } - private int slot(DfsPackKey pack, long position) { - return (hash(pack.hash, position) >>> 1) % tableSize; + private static AtomicLong getStat(AtomicReference stats, + DfsStreamKey key) { + int pos = key.packExtPos; + while (true) { + AtomicLong[] vals = stats.get(); + if (pos < vals.length) { + return vals[pos]; + } + AtomicLong[] expect = vals; + vals = new AtomicLong[Math.max(pos + 1, PackExt.values().length)]; + System.arraycopy(expect, 0, vals, 0, expect.length); + for (int i = expect.length; i < vals.length; i++) { + vals[i] = new AtomicLong(); + } + if (stats.compareAndSet(expect, vals)) { + return vals[pos]; + } + } } - private ReentrantLock lockFor(DfsPackKey pack, long position) { - return loadLocks[(hash(pack.hash, position) >>> 1) % loadLocks.length]; + private static long[] getStatVals(AtomicReference stat) { + AtomicLong[] stats = stat.get(); + long[] cnt = new long[stats.length]; + for (int i = 0; i < stats.length; i++) { + cnt[i] = stats[i].get(); + } + return cnt; } private static HashEntry clean(HashEntry top) { @@ -544,15 +633,15 @@ } static final class Ref { - final DfsPackKey pack; + final DfsStreamKey key; final long position; final int size; volatile T value; Ref next; volatile boolean hot; - Ref(DfsPackKey pack, long position, int size, T v) { - this.pack = pack; + Ref(DfsStreamKey key, long position, int size, T v) { + this.key = key; this.position = position; this.size = size; this.value = v; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,24 +46,22 @@ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.eclipse.jgit.internal.storage.pack.PackOutputStream; -/** A cached slice of a {@link DfsPackFile}. */ +/** A cached slice of a {@link BlockBasedFile}. */ final class DfsBlock { - final DfsPackKey pack; - + final DfsStreamKey stream; final long start; - final long end; - private final byte[] block; - DfsBlock(DfsPackKey p, long pos, byte[] buf) { - pack = p; + DfsBlock(DfsStreamKey p, long pos, byte[] buf) { + stream = p; start = pos; end = pos + buf.length; block = buf; @@ -73,8 +71,14 @@ return block.length; } - boolean contains(DfsPackKey want, long pos) { - return pack == want && start <= pos && pos < end; + ByteBuffer zeroCopyByteBuffer(int n) { + ByteBuffer b = ByteBuffer.wrap(block); + b.position(n); + return b; + } + + boolean contains(DfsStreamKey want, long pos) { + return stream.equals(want) && start <= pos && pos < end; } int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { @@ -88,9 +92,16 @@ return n; } - int setInput(long pos, Inflater inf) { + int setInput(long pos, Inflater inf) throws DataFormatException { int ptr = (int) (pos - start); int cnt = block.length - ptr; + if (cnt <= 0) { + throw new DataFormatException(cnt + " bytes to inflate:" //$NON-NLS-1$ + + " at pos=" + pos //$NON-NLS-1$ + + "; block.start=" + start //$NON-NLS-1$ + + "; ptr=" + ptr //$NON-NLS-1$ + + "; block.length=" + block.length); //$NON-NLS-1$ + } inf.setInput(block, ptr, cnt); return cnt; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.internal.storage.pack.PackOutputStream; import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation; -/** A DfsPackFile available for reuse as-is. */ +/** + * A DfsPackFile available for reuse as-is. + */ public class DfsCachedPack extends CachedPack { private final DfsPackFile pack; @@ -58,21 +60,28 @@ this.pack = pack; } - /** @return the description of the pack. */ + /** + * Get the description of the pack. + * + * @return the description of the pack. + */ public DfsPackDescription getPackDescription() { return pack.getPackDescription(); } + /** {@inheritDoc} */ @Override public long getObjectCount() throws IOException { return getPackDescription().getObjectCount(); } + /** {@inheritDoc} */ @Override public long getDeltaCount() throws IOException { return getPackDescription().getDeltaCount(); } + /** {@inheritDoc} */ @Override public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) { return ((DfsObjectRepresentation) rep).pack == pack; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,11 +49,13 @@ import org.eclipse.jgit.lib.StoredConfig; final class DfsConfig extends StoredConfig { + /** {@inheritDoc} */ @Override public void load() throws IOException, ConfigInvalidException { clear(); } + /** {@inheritDoc} */ @Override public void save() throws IOException { // TODO actually store this configuration. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; +import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.eclipse.jgit.errors.CorruptPackIndexException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.fsck.FsckError; +import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex; +import org.eclipse.jgit.internal.fsck.FsckPackParser; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.ObjectWalk; +import org.eclipse.jgit.revwalk.RevObject; + +/** + * Verify the validity and connectivity of a DFS repository. + */ +public class DfsFsck { + private final DfsRepository repo; + private final DfsObjDatabase objdb; + private ObjectChecker objChecker = new ObjectChecker(); + private boolean connectivityOnly; + + /** + * Initialize DFS fsck. + * + * @param repository + * the dfs repository to check. + */ + public DfsFsck(DfsRepository repository) { + repo = repository; + objdb = repo.getObjectDatabase(); + } + + /** + * Verify the integrity and connectivity of all objects in the object + * database. + * + * @param pm + * callback to provide progress feedback during the check. + * @return all errors about the repository. + * @throws java.io.IOException + * if encounters IO errors during the process. + */ + public FsckError check(ProgressMonitor pm) throws IOException { + if (pm == null) { + pm = NullProgressMonitor.INSTANCE; + } + + FsckError errors = new FsckError(); + if (!connectivityOnly) { + checkPacks(pm, errors); + } + checkConnectivity(pm, errors); + return errors; + } + + private void checkPacks(ProgressMonitor pm, FsckError errors) + throws IOException, FileNotFoundException { + try (DfsReader ctx = objdb.newReader()) { + for (DfsPackFile pack : objdb.getPacks()) { + DfsPackDescription packDesc = pack.getPackDescription(); + if (packDesc.getPackSource() + == PackSource.UNREACHABLE_GARBAGE) { + continue; + } + try (ReadableChannel rc = objdb.openFile(packDesc, PACK)) { + verifyPack(pm, errors, ctx, pack, rc); + } catch (MissingObjectException e) { + errors.getMissingObjects().add(e.getObjectId()); + } catch (CorruptPackIndexException e) { + errors.getCorruptIndices().add(new CorruptIndex( + pack.getPackDescription().getFileName(INDEX), + e.getErrorType())); + } + } + } + } + + private void verifyPack(ProgressMonitor pm, FsckError errors, DfsReader ctx, + DfsPackFile pack, ReadableChannel ch) + throws IOException, CorruptPackIndexException { + FsckPackParser fpp = new FsckPackParser(objdb, ch); + fpp.setObjectChecker(objChecker); + fpp.overwriteObjectCount(pack.getPackDescription().getObjectCount()); + fpp.parse(pm); + errors.getCorruptObjects().addAll(fpp.getCorruptObjects()); + + fpp.verifyIndex(pack.getPackIndex(ctx)); + } + + private void checkConnectivity(ProgressMonitor pm, FsckError errors) + throws IOException { + pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN); + try (ObjectWalk ow = new ObjectWalk(repo)) { + for (Ref r : repo.getAllRefs().values()) { + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + // skip unborn branch + continue; + } + RevObject tip; + try { + tip = ow.parseAny(objectId); + if (r.getLeaf().getName().startsWith(Constants.R_HEADS) + && tip.getType() != Constants.OBJ_COMMIT) { + // heads should only point to a commit object + errors.getNonCommitHeads().add(r.getLeaf().getName()); + } + ow.markStart(tip); + } catch (MissingObjectException e) { + errors.getMissingObjects().add(e.getObjectId()); + continue; + } + } + try { + ow.checkConnectivity(); + } catch (MissingObjectException e) { + errors.getMissingObjects().add(e.getObjectId()); + } + } + pm.endTask(); + } + + /** + * Use a customized object checker instead of the default one. Caller can + * specify a skip list to ignore some errors. + * + * @param objChecker + * A customized object checker. + */ + public void setObjectChecker(ObjectChecker objChecker) { + this.objChecker = objChecker; + } + + /** + * Whether fsck should bypass object validity and integrity checks and only + * check connectivity. + * + * @param connectivityOnly + * whether fsck should bypass object validity and integrity + * checks and only check connectivity. The default is + * {@code false}, meaning to run all checks. + */ + public void setConnectivityOnly(boolean connectivityOnly) { + this.connectivityOnly = connectivityOnly; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,66 +43,92 @@ package org.eclipse.jgit.internal.storage.dfs; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT; import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE; import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE; +import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable; import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; -import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; +import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.EnumSet; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.file.PackIndex; +import org.eclipse.jgit.internal.storage.file.PackReverseIndex; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter; +import org.eclipse.jgit.internal.storage.reftree.RefTreeNames; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; 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.SystemReader; import org.eclipse.jgit.util.io.CountingOutputStream; -/** Repack and garbage collect a repository. */ +/** + * Repack and garbage collect a repository. + */ public class DfsGarbageCollector { private final DfsRepository repo; - - private final DfsRefDatabase refdb; - + private final RefDatabase refdb; private final DfsObjDatabase objdb; private final List newPackDesc; - private final List newPackStats; - - private final List newPackObj; + private final List newPackObj; private DfsReader ctx; private PackConfig packConfig; + private ReftableConfig reftableConfig; + private boolean convertToReftable = true; + private boolean includeDeletes; + private long reftableInitialMinUpdateIndex = 1; + private long reftableInitialMaxUpdateIndex = 1; + // See packIsCoalesceableGarbage(), below, for how these two variables + // interact. private long coalesceGarbageLimit = 50 << 20; + private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1); - private Map refsBefore; - + private long startTimeMillis; private List packsBefore; + private List reftablesBefore; + private List expiredGarbagePacks; - private Set allHeads; - + private Collection refsBefore; + private Set allHeadsAndTags; + private Set allTags; private Set nonHeads; - + private Set txnHeads; private Set tagTargets; /** @@ -115,20 +141,26 @@ repo = repository; refdb = repo.getRefDatabase(); objdb = repo.getObjectDatabase(); - newPackDesc = new ArrayList(4); - newPackStats = new ArrayList(4); - newPackObj = new ArrayList(4); + newPackDesc = new ArrayList<>(4); + newPackStats = new ArrayList<>(4); + newPackObj = new ArrayList<>(4); packConfig = new PackConfig(repo); packConfig.setIndexVersion(2); } - /** @return configuration used to generate the new pack file. */ + /** + * Get configuration used to generate the new pack file. + * + * @return configuration used to generate the new pack file. + */ public PackConfig getPackConfig() { return packConfig; } /** + * Set the new configuration to use when creating the pack file. + * * @param newConfig * the new configuration to use when creating the pack file. * @return {@code this} @@ -138,7 +170,88 @@ return this; } - /** @return garbage packs smaller than this size will be repacked. */ + /** + * Set configuration to write a reftable. + * + * @param cfg + * configuration to write a reftable. Reftable writing is + * disabled (default) when {@code cfg} is {@code null}. + * @return {@code this} + */ + public DfsGarbageCollector setReftableConfig(ReftableConfig cfg) { + reftableConfig = cfg; + return this; + } + + /** + * Whether the garbage collector should convert references to reftable. + * + * @param convert + * if {@code true}, {@link #setReftableConfig(ReftableConfig)} + * has been set non-null, and a GC reftable doesn't yet exist, + * the garbage collector will make one by scanning the existing + * references, and writing a new reftable. Default is + * {@code true}. + * @return {@code this} + */ + public DfsGarbageCollector setConvertToReftable(boolean convert) { + convertToReftable = convert; + return this; + } + + /** + * Whether the garbage collector will include tombstones for deleted + * references in the reftable. + * + * @param include + * if {@code true}, the garbage collector will include tombstones + * for deleted references in the reftable. Default is + * {@code false}. + * @return {@code this} + */ + public DfsGarbageCollector setIncludeDeletes(boolean include) { + includeDeletes = include; + return this; + } + + /** + * Set minUpdateIndex for the initial reftable created during conversion. + * + * @param u + * minUpdateIndex for the initial reftable created by scanning + * {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}. + * Ignored unless caller has also set + * {@link #setReftableConfig(ReftableConfig)}. Defaults to + * {@code 1}. Must be {@code u >= 0}. + * @return {@code this} + */ + public DfsGarbageCollector setReftableInitialMinUpdateIndex(long u) { + reftableInitialMinUpdateIndex = Math.max(u, 0); + return this; + } + + /** + * Set maxUpdateIndex for the initial reftable created during conversion. + * + * @param u + * maxUpdateIndex for the initial reftable created by scanning + * {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}. + * Ignored unless caller has also set + * {@link #setReftableConfig(ReftableConfig)}. Defaults to + * {@code 1}. Must be {@code u >= 0}. + * @return {@code this} + */ + public DfsGarbageCollector setReftableInitialMaxUpdateIndex(long u) { + reftableInitialMaxUpdateIndex = Math.max(0, u); + return this; + } + + /** + * Get coalesce garbage limit + * + * @return coalesce garbage limit, packs smaller than this size will be + * repacked. + */ public long getCoalesceGarbageLimit() { return coalesceGarbageLimit; } @@ -155,7 +268,8 @@ * reading and copying the objects. *

* If limit is set to 0 the UNREACHABLE_GARBAGE coalesce is disabled.
- * If limit is set to {@link Long#MAX_VALUE}, everything is coalesced. + * If limit is set to {@link java.lang.Long#MAX_VALUE}, everything is + * coalesced. *

* Keeping unreachable garbage prevents race conditions with repository * changes that may suddenly need an object whose only copy was stored in @@ -171,6 +285,36 @@ } /** + * Get time to live for garbage packs. + * + * @return garbage packs older than this limit (in milliseconds) will be + * pruned as part of the garbage collection process if the value is + * > 0, otherwise garbage packs are retained. + */ + public long getGarbageTtlMillis() { + return garbageTtlMillis; + } + + /** + * Set the time to live for garbage objects. + *

+ * Any UNREACHABLE_GARBAGE older than this limit will be pruned at the end + * of the run. + *

+ * If timeToLiveMillis is set to 0, UNREACHABLE_GARBAGE purging is disabled. + * + * @param ttl + * Time to live whatever unit is specified. + * @param unit + * The specified time unit. + * @return {@code this} + */ + public DfsGarbageCollector setGarbageTtl(long ttl, TimeUnit unit) { + garbageTtlMillis = unit.toMillis(ttl); + return this; + } + + /** * Create a single new pack file containing all of the live objects. *

* This method safely decides which packs can be expired after the new pack @@ -183,7 +327,7 @@ * @return true if the repack was successful without race conditions. False * if a race condition was detected and the repack should be run * again later. - * @throws IOException + * @throws java.io.IOException * a new pack cannot be created. */ public boolean pack(ProgressMonitor pm) throws IOException { @@ -193,35 +337,58 @@ throw new IllegalStateException( JGitText.get().supportOnlyPackIndexVersion2); - ctx = (DfsReader) objdb.newReader(); + startTimeMillis = SystemReader.getInstance().getCurrentTime(); + ctx = objdb.newReader(); try { - refdb.clearCache(); + refdb.refresh(); objdb.clearCache(); - refsBefore = refdb.getRefs(ALL); - packsBefore = packsToRebuild(); - if (packsBefore.isEmpty()) - return true; - - allHeads = new HashSet(); - nonHeads = new HashSet(); - tagTargets = new HashSet(); - for (Ref ref : refsBefore.values()) { - if (ref.isSymbolic() || ref.getObjectId() == null) + refsBefore = getAllRefs(); + readPacksBefore(); + readReftablesBefore(); + + Set allHeads = new HashSet<>(); + allHeadsAndTags = new HashSet<>(); + allTags = new HashSet<>(); + nonHeads = new HashSet<>(); + txnHeads = new HashSet<>(); + tagTargets = new HashSet<>(); + for (Ref ref : refsBefore) { + if (ref.isSymbolic() || ref.getObjectId() == null) { continue; - if (isHead(ref)) + } + if (isHead(ref)) { allHeads.add(ref.getObjectId()); - else + } else if (isTag(ref)) { + allTags.add(ref.getObjectId()); + } else if (RefTreeNames.isRefTree(refdb, ref.getName())) { + txnHeads.add(ref.getObjectId()); + } else { nonHeads.add(ref.getObjectId()); - if (ref.getPeeledObjectId() != null) + } + if (ref.getPeeledObjectId() != null) { tagTargets.add(ref.getPeeledObjectId()); + } + } + // Don't exclude tags that are also branch tips. + allTags.removeAll(allHeads); + allHeadsAndTags.addAll(allHeads); + allHeadsAndTags.addAll(allTags); + + // Hoist all branch tips and tags earlier in the pack file + tagTargets.addAll(allHeadsAndTags); + + // Combine the GC_REST objects into the GC pack if requested + if (packConfig.getSinglePack()) { + allHeadsAndTags.addAll(nonHeads); + nonHeads.clear(); } - tagTargets.addAll(allHeads); boolean rollback = true; try { packHeads(pm); packRest(pm); + packRefTreeGraph(pm); packGarbage(pm); objdb.commitPack(newPackDesc, toPrune()); rollback = false; @@ -235,51 +402,179 @@ } } - private List packsToRebuild() throws IOException { + private Collection getAllRefs() throws IOException { + Collection refs = refdb.getRefs(RefDatabase.ALL).values(); + List addl = refdb.getAdditionalRefs(); + if (!addl.isEmpty()) { + List all = new ArrayList<>(refs.size() + addl.size()); + all.addAll(refs); + // add additional refs which start with refs/ + for (Ref r : addl) { + if (r.getName().startsWith(Constants.R_REFS)) { + all.add(r); + } + } + return all; + } + return refs; + } + + private void readPacksBefore() throws IOException { DfsPackFile[] packs = objdb.getPacks(); - List out = new ArrayList(packs.length); + packsBefore = new ArrayList<>(packs.length); + expiredGarbagePacks = new ArrayList<>(packs.length); + + long now = SystemReader.getInstance().getCurrentTime(); for (DfsPackFile p : packs) { DfsPackDescription d = p.getPackDescription(); - if (d.getPackSource() != UNREACHABLE_GARBAGE) - out.add(p); - else if (d.getFileSize(PackExt.PACK) < coalesceGarbageLimit) - out.add(p); + if (d.getPackSource() != UNREACHABLE_GARBAGE) { + packsBefore.add(p); + } else if (packIsExpiredGarbage(d, now)) { + expiredGarbagePacks.add(p); + } else if (packIsCoalesceableGarbage(d, now)) { + packsBefore.add(p); + } + } + } + + private void readReftablesBefore() throws IOException { + DfsReftable[] tables = objdb.getReftables(); + reftablesBefore = new ArrayList<>(Arrays.asList(tables)); + } + + private boolean packIsExpiredGarbage(DfsPackDescription d, long now) { + // Consider the garbage pack as expired when it's older than + // garbagePackTtl. This check gives concurrent inserter threads + // sufficient time to identify an object is not in the graph and should + // have a new copy written, rather than relying on something from an + // UNREACHABLE_GARBAGE pack. + return d.getPackSource() == UNREACHABLE_GARBAGE + && garbageTtlMillis > 0 + && now - d.getLastModified() >= garbageTtlMillis; + } + + private boolean packIsCoalesceableGarbage(DfsPackDescription d, long now) { + // An UNREACHABLE_GARBAGE pack can be coalesced if its size is less than + // the coalesceGarbageLimit and either garbageTtl is zero or if the pack + // is created in a close time interval (on a single calendar day when + // the garbageTtl is more than one day or one third of the garbageTtl). + // + // When the garbageTtl is more than 24 hours, garbage packs that are + // created within a single calendar day are coalesced together. This + // would make the effective ttl of the garbage pack as garbageTtl+23:59 + // and limit the number of garbage to a maximum number of + // garbageTtl_in_days + 1 (assuming all of them are less than the size + // of coalesceGarbageLimit). + // + // When the garbageTtl is less than or equal to 24 hours, garbage packs + // that are created within a one third of garbageTtl are coalesced + // together. This would make the effective ttl of the garbage packs as + // garbageTtl + (garbageTtl / 3) and would limit the number of garbage + // packs to a maximum number of 4 (assuming all of them are less than + // the size of coalesceGarbageLimit). + + if (d.getPackSource() != UNREACHABLE_GARBAGE + || d.getFileSize(PackExt.PACK) >= coalesceGarbageLimit) { + return false; + } + + if (garbageTtlMillis == 0) { + return true; + } + + long lastModified = d.getLastModified(); + long dayStartLastModified = dayStartInMillis(lastModified); + long dayStartToday = dayStartInMillis(now); + + if (dayStartLastModified != dayStartToday) { + return false; // this pack is not created today. + } + + if (garbageTtlMillis > TimeUnit.DAYS.toMillis(1)) { + return true; // ttl is more than one day and pack is created today. + } + + long timeInterval = garbageTtlMillis / 3; + if (timeInterval == 0) { + return false; // ttl is too small, don't try to coalesce. } - return out; + + long modifiedTimeSlot = (lastModified - dayStartLastModified) / timeInterval; + long presentTimeSlot = (now - dayStartToday) / timeInterval; + return modifiedTimeSlot == presentTimeSlot; + } + + private static long dayStartInMillis(long timeInMillis) { + Calendar cal = new GregorianCalendar( + SystemReader.getInstance().getTimeZone()); + cal.setTimeInMillis(timeInMillis); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); } - /** @return all of the source packs that fed into this compaction. */ - public List getSourcePacks() { + /** + * Get all of the source packs that fed into this compaction. + * + * @return all of the source packs that fed into this compaction. + */ + public Set getSourcePacks() { return toPrune(); } - /** @return new packs created by this compaction. */ + /** + * Get new packs created by this compaction. + * + * @return new packs created by this compaction. + */ public List getNewPacks() { return newPackDesc; } - /** @return statistics corresponding to the {@link #getNewPacks()}. */ + /** + * Get statistics corresponding to the {@link #getNewPacks()}. + *

+ * The elements can be null if the stat is not available for the pack file. + * + * @return statistics corresponding to the {@link #getNewPacks()}. + */ public List getNewPackStatistics() { return newPackStats; } - private List toPrune() { - int cnt = packsBefore.size(); - List all = new ArrayList(cnt); - for (DfsPackFile pack : packsBefore) - all.add(pack.getPackDescription()); - return all; + private Set toPrune() { + Set toPrune = new HashSet<>(); + for (DfsPackFile pack : packsBefore) { + toPrune.add(pack.getPackDescription()); + } + if (reftableConfig != null) { + for (DfsReftable table : reftablesBefore) { + toPrune.add(table.getPackDescription()); + } + } + for (DfsPackFile pack : expiredGarbagePacks) { + toPrune.add(pack.getPackDescription()); + } + return toPrune; } private void packHeads(ProgressMonitor pm) throws IOException { - if (allHeads.isEmpty()) + if (allHeadsAndTags.isEmpty()) { + writeReftable(); return; + } try (PackWriter pw = newPackWriter()) { pw.setTagTargets(tagTargets); - pw.preparePack(pm, allHeads, Collections. emptySet()); - if (0 < pw.getObjectCount()) - writePack(GC, pw, pm); + pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags); + if (0 < pw.getObjectCount()) { + long estSize = estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC); + writePack(GC, pw, pm, estSize); + } else { + writeReftable(); + } } } @@ -288,16 +583,29 @@ return; try (PackWriter pw = newPackWriter()) { - for (PackWriter.ObjectIdSet packedObjs : newPackObj) + for (ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); - pw.preparePack(pm, nonHeads, allHeads); + pw.preparePack(pm, nonHeads, allHeadsAndTags); if (0 < pw.getObjectCount()) - writePack(GC, pw, pm); + writePack(GC_REST, pw, pm, + estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST)); + } + } + + private void packRefTreeGraph(ProgressMonitor pm) throws IOException { + if (txnHeads.isEmpty()) + return; + + try (PackWriter pw = newPackWriter()) { + for (ObjectIdSet packedObjs : newPackObj) + pw.excludeObjects(packedObjs); + pw.preparePack(pm, txnHeads, NONE); + if (0 < pw.getObjectCount()) + writePack(GC_TXN, pw, pm, 0 /* unknown pack size */); } } private void packGarbage(ProgressMonitor pm) throws IOException { - // TODO(sop) This is ugly. The garbage pack needs to be deleted. PackConfig cfg = new PackConfig(packConfig); cfg.setReuseDeltas(true); cfg.setReuseObjects(true); @@ -309,26 +617,34 @@ pw.setDeltaBaseAsOffset(true); pw.setReuseDeltaCommits(true); pm.beginTask(JGitText.get().findingGarbage, objectsBefore()); + long estimatedPackSize = 12 + 20; // header and trailer sizes. for (DfsPackFile oldPack : packsBefore) { PackIndex oldIdx = oldPack.getPackIndex(ctx); + PackReverseIndex oldRevIdx = oldPack.getReverseIdx(ctx); + long maxOffset = oldPack.getPackDescription().getFileSize(PACK) + - 20; // pack size - trailer size. for (PackIndex.MutableEntry ent : oldIdx) { pm.update(1); ObjectId id = ent.toObjectId(); if (pool.lookupOrNull(id) != null || anyPackHas(id)) continue; - int type = oldPack.getObjectType(ctx, ent.getOffset()); + long offset = ent.getOffset(); + int type = oldPack.getObjectType(ctx, offset); pw.addObject(pool.lookupAny(id, type)); + long objSize = oldRevIdx.findNextOffset(offset, maxOffset) + - offset; + estimatedPackSize += objSize; } } pm.endTask(); if (0 < pw.getObjectCount()) - writePack(UNREACHABLE_GARBAGE, pw, pm); + writePack(UNREACHABLE_GARBAGE, pw, pm, estimatedPackSize); } } private boolean anyPackHas(AnyObjectId id) { - for (PackWriter.ObjectIdSet packedObjs : newPackObj) + for (ObjectIdSet packedObjs : newPackObj) if (packedObjs.contains(id)) return true; return false; @@ -338,6 +654,10 @@ return ref.getName().startsWith(Constants.R_HEADS); } + private static boolean isTag(Ref ref) { + return ref.getName().startsWith(Constants.R_TAGS); + } + private int objectsBefore() { int cnt = 0; for (DfsPackFile p : packsBefore) @@ -352,56 +672,117 @@ return pw; } + private long estimateGcPackSize(PackSource first, PackSource... rest) { + EnumSet sourceSet = EnumSet.of(first, rest); + // Every pack file contains 12 bytes of header and 20 bytes of trailer. + // Include the final pack file header and trailer size here and ignore + // the same from individual pack files. + long size = 32; + for (DfsPackDescription pack : getSourcePacks()) { + if (sourceSet.contains(pack.getPackSource())) { + size += pack.getFileSize(PACK) - 32; + } + } + return size; + } + private DfsPackDescription writePack(PackSource source, PackWriter pw, - ProgressMonitor pm) throws IOException { - DfsOutputStream out; - DfsPackDescription pack = repo.getObjectDatabase().newPack(source); - newPackDesc.add(pack); + ProgressMonitor pm, long estimatedPackSize) throws IOException { + DfsPackDescription pack = repo.getObjectDatabase().newPack(source, + estimatedPackSize); - out = objdb.writeFile(pack, PACK); - try { + if (source == GC && reftableConfig != null) { + writeReftable(pack); + } + + try (DfsOutputStream out = objdb.writeFile(pack, PACK)) { pw.writePack(pm, pm, out); pack.addFileExt(PACK); - } finally { - out.close(); + pack.setBlockSize(PACK, out.blockSize()); } - out = objdb.writeFile(pack, INDEX); - try { + try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) { CountingOutputStream cnt = new CountingOutputStream(out); pw.writeIndex(cnt); pack.addFileExt(INDEX); pack.setFileSize(INDEX, cnt.getCount()); + pack.setBlockSize(INDEX, out.blockSize()); pack.setIndexVersion(pw.getIndexVersion()); - } finally { - out.close(); } if (pw.prepareBitmapIndex(pm)) { - out = objdb.writeFile(pack, BITMAP_INDEX); - try { + try (DfsOutputStream out = objdb.writeFile(pack, BITMAP_INDEX)) { CountingOutputStream cnt = new CountingOutputStream(out); pw.writeBitmapIndex(cnt); pack.addFileExt(BITMAP_INDEX); pack.setFileSize(BITMAP_INDEX, cnt.getCount()); - } finally { - out.close(); + pack.setBlockSize(BITMAP_INDEX, out.blockSize()); } } - final ObjectIdOwnerMap packedObjs = pw - .getObjectSet(); - newPackObj.add(new PackWriter.ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return packedObjs.contains(objectId); - } - }); - PackStatistics stats = pw.getStatistics(); pack.setPackStats(stats); + pack.setLastModified(startTimeMillis); + newPackDesc.add(pack); newPackStats.add(stats); - - DfsBlockCache.getInstance().getOrCreate(pack, null); + newPackObj.add(pw.getObjectSet()); return pack; } + + private void writeReftable() throws IOException { + if (reftableConfig != null) { + DfsPackDescription pack = objdb.newPack(GC); + newPackDesc.add(pack); + newPackStats.add(null); + writeReftable(pack); + } + } + + private void writeReftable(DfsPackDescription pack) throws IOException { + if (convertToReftable && !hasGcReftable()) { + writeReftable(pack, refsBefore); + return; + } + + try (ReftableStack stack = ReftableStack.open(ctx, reftablesBefore)) { + ReftableCompactor compact = new ReftableCompactor(); + compact.addAll(stack.readers()); + compact.setIncludeDeletes(includeDeletes); + compactReftable(pack, compact); + } + } + + private boolean hasGcReftable() { + for (DfsReftable table : reftablesBefore) { + if (table.getPackDescription().getPackSource() == GC) { + return true; + } + } + return false; + } + + private void writeReftable(DfsPackDescription pack, Collection refs) + throws IOException { + try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) { + ReftableConfig cfg = configureReftable(reftableConfig, out); + ReftableWriter writer = new ReftableWriter(cfg) + .setMinUpdateIndex(reftableInitialMinUpdateIndex) + .setMaxUpdateIndex(reftableInitialMaxUpdateIndex) + .begin(out) + .sortAndWriteRefs(refs) + .finish(); + pack.addFileExt(REFTABLE); + pack.setReftableStats(writer.getStats()); + } + } + + private void compactReftable(DfsPackDescription pack, + ReftableCompactor compact) throws IOException { + try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) { + compact.setConfig(configureReftable(reftableConfig, out)); + compact.compact(out); + pack.addFileExt(REFTABLE); + pack.setReftableStats(compact.getStats()); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,6 +69,7 @@ import java.util.zip.InflaterInputStream; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.PackIndex; @@ -89,23 +90,27 @@ import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.io.CountingOutputStream; +import org.eclipse.jgit.util.sha1.SHA1; -/** Inserts objects into the DFS. */ +/** + * Inserts objects into the DFS. + */ public class DfsInserter extends ObjectInserter { /** Always produce version 2 indexes, to get CRC data. */ private static final int INDEX_VERSION = 2; - private final DfsObjDatabase db; - private int compression = Deflater.BEST_COMPRESSION; + final DfsObjDatabase db; + int compression = Deflater.BEST_COMPRESSION; - private List objectList; - private ObjectIdOwnerMap objectMap; + List objectList; + ObjectIdOwnerMap objectMap; - private DfsBlockCache cache; - private DfsPackKey packKey; - private DfsPackDescription packDsc; - private PackStream packOut; + DfsBlockCache cache; + DfsStreamKey packKey; + DfsPackDescription packDsc; + PackStream packOut; private boolean rollback; + private boolean checkExisting = true; /** * Initialize a new inserter. @@ -117,27 +122,43 @@ this.db = db; } + /** + * Check existence + * + * @param check + * if {@code false}, will write out possibly-duplicate objects + * without first checking whether they exist in the repo; default + * is true. + */ + public void checkExisting(boolean check) { + checkExisting = check; + } + void setCompressionLevel(int compression) { this.compression = compression; } + /** {@inheritDoc} */ @Override public DfsPackParser newPackParser(InputStream in) throws IOException { return new DfsPackParser(db, this, in); } + /** {@inheritDoc} */ @Override public ObjectReader newReader() { return new Reader(); } + /** {@inheritDoc} */ @Override public ObjectId insert(int type, byte[] data, int off, int len) throws IOException { ObjectId id = idFor(type, data, off, len); if (objectMap != null && objectMap.contains(id)) return id; - if (db.has(id)) + // Ignore unreachable (garbage) objects here. + if (checkExisting && db.has(id, true)) return id; long offset = beginObject(type, len); @@ -146,6 +167,7 @@ return endObject(id, offset); } + /** {@inheritDoc} */ @Override public ObjectId insert(int type, long len, InputStream in) throws IOException { @@ -156,7 +178,7 @@ } long offset = beginObject(type, len); - MessageDigest md = digest(); + SHA1 md = digest(); md.update(Constants.encodedTypeString(type)); md.update((byte) ' '); md.update(Constants.encodeASCII(len)); @@ -171,7 +193,7 @@ len -= n; } packOut.compress.finish(); - return endObject(ObjectId.fromRaw(md.digest()), offset); + return endObject(md.toObjectId(), offset); } private byte[] insertBuffer(long len) { @@ -188,6 +210,7 @@ return buf; } + /** {@inheritDoc} */ @Override public void flush() throws IOException { if (packDsc == null) @@ -208,13 +231,14 @@ db.commitPack(Collections.singletonList(packDsc), null); rollback = false; - DfsPackFile p = cache.getOrCreate(packDsc, packKey); + DfsPackFile p = new DfsPackFile(cache, packDsc); if (index != null) p.setPackIndex(index); db.addPack(p); clear(); } + /** {@inheritDoc} */ @Override public void close() { if (packOut != null) { @@ -262,14 +286,16 @@ } private void beginPack() throws IOException { - objectList = new BlockList(); - objectMap = new ObjectIdOwnerMap(); + objectList = new BlockList<>(); + objectMap = new ObjectIdOwnerMap<>(); cache = DfsBlockCache.getInstance(); rollback = true; packDsc = db.newPack(DfsObjDatabase.PackSource.INSERT); - packOut = new PackStream(db.writeFile(packDsc, PACK)); - packKey = new DfsPackKey(); + DfsOutputStream dfsOut = db.writeFile(packDsc, PACK); + packDsc.setBlockSize(PACK, dfsOut.blockSize()); + packOut = new PackStream(dfsOut); + packKey = packDsc.getStreamKey(PACK); // Write the header as though it were a single object pack. byte[] buf = packOut.hdrBuf; @@ -299,17 +325,19 @@ packIndex = PackIndex.read(buf.openInputStream()); } - DfsOutputStream os = db.writeFile(pack, INDEX); - try { + try (DfsOutputStream os = db.writeFile(pack, INDEX)) { CountingOutputStream cnt = new CountingOutputStream(os); if (buf != null) buf.writeTo(cnt, null); else index(cnt, packHash, list); pack.addFileExt(INDEX); + pack.setBlockSize(INDEX, os.blockSize()); pack.setFileSize(INDEX, cnt.getCount()); } finally { - os.close(); + if (buf != null) { + buf.close(); + } } return packIndex; } @@ -322,7 +350,7 @@ private class PackStream extends OutputStream { private final DfsOutputStream out; private final MessageDigest md; - private final byte[] hdrBuf; + final byte[] hdrBuf; private final Deflater deflater; private final int blockSize; @@ -473,7 +501,8 @@ } } - private int setInput(long pos, Inflater inf) throws IOException { + private int setInput(long pos, Inflater inf) + throws IOException, DataFormatException { if (pos < currPos) return getOrLoadBlock(pos).setInput(pos, inf); if (pos < currPos + currPtr) { @@ -482,7 +511,7 @@ inf.setInput(currBuf, s, n); return n; } - throw new EOFException(DfsText.get().unexpectedEofInPack); + throw new EOFException(JGitText.get().unexpectedEofInPack); } private DfsBlock getOrLoadBlock(long pos) throws IOException { @@ -495,7 +524,7 @@ for (int p = 0; p < blockSize;) { int n = out.read(s + p, ByteBuffer.wrap(d, p, blockSize - p)); if (n <= 0) - throw new EOFException(DfsText.get().unexpectedEofInPack); + throw new EOFException(JGitText.get().unexpectedEofInPack); p += n; } b = new DfsBlock(packKey, s, d); @@ -515,7 +544,7 @@ } private class Reader extends ObjectReader { - private final DfsReader ctx = new DfsReader(db); + private final DfsReader ctx = db.newReader(); @Override public ObjectReader newReader() { @@ -529,7 +558,7 @@ if (objectList == null) return stored; - Set r = new HashSet(stored.size() + 2); + Set r = new HashSet<>(stored.size() + 2); r.addAll(stored); for (PackedObjectInfo obj : objectList) { if (id.prefixCompare(obj) == 0) @@ -551,20 +580,23 @@ byte[] buf = buffer(); int cnt = packOut.read(obj.getOffset(), buf, 0, 20); if (cnt <= 0) - throw new EOFException(DfsText.get().unexpectedEofInPack); + throw new EOFException(JGitText.get().unexpectedEofInPack); int c = buf[0] & 0xff; int type = (c >> 4) & 7; if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) throw new IOException(MessageFormat.format( - DfsText.get().cannotReadBackDelta, Integer.toString(type))); + JGitText.get().cannotReadBackDelta, Integer.toString(type))); + if (typeHint != OBJ_ANY && type != typeHint) { + throw new IncorrectObjectTypeException(objectId.copy(), typeHint); + } long sz = c & 0x0f; int ptr = 1; int shift = 4; while ((c & 0x80) != 0) { if (ptr >= cnt) - throw new EOFException(DfsText.get().unexpectedEofInPack); + throw new EOFException(JGitText.get().unexpectedEofInPack); c = buf[ptr++] & 0xff; sz += ((long) (c & 0x7f)) << shift; shift += 7; @@ -584,22 +616,32 @@ try { return packOut.inflate(ctx, zpos, sz); } catch (DataFormatException dfe) { - CorruptObjectException coe = new CorruptObjectException( + throw new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, Long.valueOf(obj.getOffset()), - packDsc.getFileName(PackExt.PACK))); - coe.initCause(dfe); - throw coe; + packDsc.getFileName(PackExt.PACK)), + dfe); } } @Override + public boolean has(AnyObjectId objectId) throws IOException { + return (objectMap != null && objectMap.contains(objectId)) + || ctx.has(objectId); + } + + @Override public Set getShallowCommits() throws IOException { return ctx.getShallowCommits(); } @Override + public ObjectInserter getCreatedFromInserter() { + return DfsInserter.this; + } + + @Override public void close() { ctx.close(); } @@ -610,11 +652,11 @@ private final int type; private final long size; - private final DfsPackKey srcPack; + private final DfsStreamKey srcPack; private final long pos; StreamLoader(ObjectId id, int type, long sz, - DfsPackKey key, long pos) { + DfsStreamKey key, long pos) { this.id = id; this.type = type; this.size = sz; @@ -624,7 +666,7 @@ @Override public ObjectStream openStream() throws IOException { - final DfsReader ctx = new DfsReader(db); + final DfsReader ctx = db.newReader(); if (srcPack != packKey) { try { // Post DfsInserter.flush() use the normal code path. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,19 +48,43 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; -/** Manages objects stored in {@link DfsPackFile} on a storage system. */ +/** + * Manages objects stored in + * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackFile} on a storage + * system. + */ public abstract class DfsObjDatabase extends ObjectDatabase { - private static final PackList NO_PACKS = new PackList(new DfsPackFile[0]); + private static final PackList NO_PACKS = new PackList( + new DfsPackFile[0], + new DfsReftable[0]) { + @Override + boolean dirty() { + return true; + } + + @Override + void clearDirty() { + // Always dirty. + } + + @Override + public void markDirty() { + // Always dirty. + } + }; /** Sources for a pack file. */ public static enum PackSource { @@ -79,6 +103,17 @@ RECEIVE(0), /** + * The pack was created by compacting multiple packs together. + *

+ * Packs created by compacting multiple packs together aren't nearly as + * efficient as a fully garbage collected repository, but may save disk + * space by reducing redundant copies of base objects. + * + * @see DfsPackCompactor + */ + COMPACT(1), + + /** * Pack was created by Git garbage collection by this implementation. *

* This source is only used by the {@link DfsGarbageCollector} when it @@ -87,18 +122,17 @@ * * @see DfsGarbageCollector */ - GC(1), + GC(2), + + /** Created from non-heads by {@link DfsGarbageCollector}. */ + GC_REST(3), /** - * The pack was created by compacting multiple packs together. - *

- * Packs created by compacting multiple packs together aren't nearly as - * efficient as a fully garbage collected repository, but may save disk - * space by reducing redundant copies of base objects. + * RefTreeGraph pack was created by Git garbage collection. * - * @see DfsPackCompactor + * @see DfsGarbageCollector */ - COMPACT(1), + GC_TXN(4), /** * Pack was created by Git garbage collection. @@ -107,7 +141,7 @@ * last GC pass. It is retained in a new pack until it is safe to prune * these objects from the repository. */ - UNREACHABLE_GARBAGE(2); + UNREACHABLE_GARBAGE(5); final int category; @@ -127,27 +161,32 @@ * * @param repository * repository owning this object database. - * * @param options * how readers should access the object database. */ protected DfsObjDatabase(DfsRepository repository, DfsReaderOptions options) { this.repository = repository; - this.packList = new AtomicReference(NO_PACKS); + this.packList = new AtomicReference<>(NO_PACKS); this.readerOptions = options; } - /** @return configured reader options, such as read-ahead. */ + /** + * Get configured reader options, such as read-ahead. + * + * @return configured reader options, such as read-ahead. + */ public DfsReaderOptions getReaderOptions() { return readerOptions; } + /** {@inheritDoc} */ @Override - public ObjectReader newReader() { + public DfsReader newReader() { return new DfsReader(this); } + /** {@inheritDoc} */ @Override public ObjectInserter newInserter() { return new DfsInserter(this); @@ -158,14 +197,43 @@ * * @return list of available packs. The returned array is shared with the * implementation and must not be modified by the caller. - * @throws IOException + * @throws java.io.IOException * the pack list cannot be initialized. */ public DfsPackFile[] getPacks() throws IOException { - return scanPacks(NO_PACKS).packs; + return getPackList().packs; } - /** @return repository owning this object database. */ + /** + * Scan and list all available reftable files in the repository. + * + * @return list of available reftables. The returned array is shared with + * the implementation and must not be modified by the caller. + * @throws java.io.IOException + * the pack list cannot be initialized. + */ + public DfsReftable[] getReftables() throws IOException { + return getPackList().reftables; + } + + /** + * Scan and list all available pack files in the repository. + * + * @return list of available packs, with some additional metadata. The + * returned array is shared with the implementation and must not be + * modified by the caller. + * @throws java.io.IOException + * the pack list cannot be initialized. + */ + public PackList getPackList() throws IOException { + return scanPacks(NO_PACKS); + } + + /** + * Get repository owning this object database. + * + * @return repository owning this object database. + */ protected DfsRepository getRepository() { return repository; } @@ -177,7 +245,50 @@ * implementation and must not be modified by the caller. */ public DfsPackFile[] getCurrentPacks() { - return packList.get().packs; + return getCurrentPackList().packs; + } + + /** + * List currently known reftable files in the repository, without scanning. + * + * @return list of available reftables. The returned array is shared with + * the implementation and must not be modified by the caller. + */ + public DfsReftable[] getCurrentReftables() { + return getCurrentPackList().reftables; + } + + /** + * List currently known pack files in the repository, without scanning. + * + * @return list of available packs, with some additional metadata. The + * returned array is shared with the implementation and must not be + * modified by the caller. + */ + public PackList getCurrentPackList() { + return packList.get(); + } + + /** + * Does the requested object exist in this database? + *

+ * This differs from ObjectDatabase's implementation in that we can selectively + * ignore unreachable (garbage) objects. + * + * @param objectId + * identity of the object to test for existence of. + * @param avoidUnreachableObjects + * if true, ignore objects that are unreachable. + * @return true if the specified object is stored in this database. + * @throws java.io.IOException + * the object store cannot be accessed. + */ + public boolean has(AnyObjectId objectId, boolean avoidUnreachableObjects) + throws IOException { + try (ObjectReader or = newReader()) { + or.setAvoidUnreachableObjects(avoidUnreachableObjects); + return or.has(objectId); + } } /** @@ -187,13 +298,40 @@ * where the pack stream is created. * @return a unique name for the pack file. Must not collide with any other * pack file name in the same DFS. - * @throws IOException + * @throws java.io.IOException * a new unique pack description cannot be generated. */ protected abstract DfsPackDescription newPack(PackSource source) throws IOException; /** + * Generate a new unique name for a pack file. + * + *

+ * Default implementation of this method would be equivalent to + * {@code newPack(source).setEstimatedPackSize(estimatedPackSize)}. But the + * clients can override this method to use the given + * {@code estomatedPackSize} value more efficiently in the process of + * creating a new + * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription} object. + * + * @param source + * where the pack stream is created. + * @param estimatedPackSize + * the estimated size of the pack. + * @return a unique name for the pack file. Must not collide with any other + * pack file name in the same DFS. + * @throws java.io.IOException + * a new unique pack description cannot be generated. + */ + protected DfsPackDescription newPack(PackSource source, + long estimatedPackSize) throws IOException { + DfsPackDescription pack = newPack(source); + pack.setEstimatedPackSize(estimatedPackSize); + return pack; + } + + /** * Commit a pack and index pair that was written to the DFS. *

* Committing the pack/index pair makes them visible to readers. The JGit @@ -214,7 +352,7 @@ * description of the new packs. * @param replaces * if not null, list of packs to remove. - * @throws IOException + * @throws java.io.IOException * the packs cannot be committed. On failure a rollback must * also be attempted by the caller. */ @@ -228,12 +366,11 @@ * Implementation of pack commit. * * @see #commitPack(Collection, Collection) - * * @param desc * description of the new packs. * @param replaces * if not null, list of packs to remove. - * @throws IOException + * @throws java.io.IOException * the packs cannot be committed. */ protected abstract void commitPackImpl(Collection desc, @@ -264,7 +401,7 @@ * DfsPackDescription objects. * * @return available packs. May be empty if there are no packs. - * @throws IOException + * @throws java.io.IOException * the packs cannot be listed and the object database is not * functional to the caller. */ @@ -281,9 +418,9 @@ * @param ext * file extension that will be read i.e "pack" or "idx". * @return channel to read the file. - * @throws FileNotFoundException + * @throws java.io.FileNotFoundException * the file does not exist. - * @throws IOException + * @throws java.io.IOException * the file cannot be opened. */ protected abstract ReadableChannel openFile( @@ -300,7 +437,7 @@ * @param ext * file extension that will be written i.e "pack" or "idx". * @return channel to write the file. - * @throws IOException + * @throws java.io.IOException * the file cannot be opened. */ protected abstract DfsOutputStream writeFile( @@ -322,19 +459,45 @@ // add, as the pack was already committed via commitPack(). // If this is the case return without changing the list. for (DfsPackFile p : o.packs) { - if (p == newPack) + if (p.key.equals(newPack.key)) { return; + } } } DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length]; packs[0] = newPack; System.arraycopy(o.packs, 0, packs, 1, o.packs.length); - n = new PackList(packs); + n = new PackListImpl(packs, o.reftables); + } while (!packList.compareAndSet(o, n)); + } + + void addReftable(DfsPackDescription add, Set remove) + throws IOException { + PackList o, n; + do { + o = packList.get(); + if (o == NO_PACKS) { + o = scanPacks(o); + for (DfsReftable t : o.reftables) { + if (t.getPackDescription().equals(add)) { + return; + } + } + } + + List tables = new ArrayList<>(1 + o.reftables.length); + for (DfsReftable t : o.reftables) { + if (!remove.contains(t.getPackDescription())) { + tables.add(t); + } + } + tables.add(new DfsReftable(add)); + n = new PackListImpl(o.packs, tables.toArray(new DfsReftable[0])); } while (!packList.compareAndSet(o, n)); } - private PackList scanPacks(final PackList original) throws IOException { + PackList scanPacks(final PackList original) throws IOException { PackList o, n; synchronized (packList) { do { @@ -356,79 +519,170 @@ private PackList scanPacksImpl(PackList old) throws IOException { DfsBlockCache cache = DfsBlockCache.getInstance(); - Map forReuse = reuseMap(old); + Map packs = packMap(old); + Map reftables = reftableMap(old); + List scanned = listPacks(); Collections.sort(scanned); - List list = new ArrayList(scanned.size()); + List newPacks = new ArrayList<>(scanned.size()); + List newReftables = new ArrayList<>(scanned.size()); boolean foundNew = false; for (DfsPackDescription dsc : scanned) { - DfsPackFile oldPack = forReuse.remove(dsc); + DfsPackFile oldPack = packs.remove(dsc); if (oldPack != null) { - list.add(oldPack); - } else { - list.add(cache.getOrCreate(dsc, null)); + newPacks.add(oldPack); + } else if (dsc.hasFileExt(PackExt.PACK)) { + newPacks.add(new DfsPackFile(cache, dsc)); + foundNew = true; + } + + DfsReftable oldReftable = reftables.remove(dsc); + if (oldReftable != null) { + newReftables.add(oldReftable); + } else if (dsc.hasFileExt(PackExt.REFTABLE)) { + newReftables.add(new DfsReftable(cache, dsc)); foundNew = true; } } - for (DfsPackFile p : forReuse.values()) - p.close(); - if (list.isEmpty()) - return new PackList(NO_PACKS.packs); - if (!foundNew) + if (newPacks.isEmpty()) + return new PackListImpl(NO_PACKS.packs, NO_PACKS.reftables); + if (!foundNew) { + old.clearDirty(); return old; - return new PackList(list.toArray(new DfsPackFile[list.size()])); + } + Collections.sort(newReftables, reftableComparator()); + return new PackListImpl( + newPacks.toArray(new DfsPackFile[0]), + newReftables.toArray(new DfsReftable[0])); } - private static Map reuseMap(PackList old) { - Map forReuse - = new HashMap(); + private static Map packMap(PackList old) { + Map forReuse = new HashMap<>(); for (DfsPackFile p : old.packs) { - if (p.invalid()) { - // The pack instance is corrupted, and cannot be safely used - // again. Do not include it in our reuse map. - // - p.close(); - continue; + if (!p.invalid()) { + forReuse.put(p.desc, p); } + } + return forReuse; + } - DfsPackFile prior = forReuse.put(p.getPackDescription(), p); - if (prior != null) { - // This should never occur. It should be impossible for us - // to have two pack files with the same name, as all of them - // came out of the same directory. If it does, we promised to - // close any PackFiles we did not reuse, so close the second, - // readers are likely to be actively using the first. - // - forReuse.put(prior.getPackDescription(), prior); - p.close(); + private static Map reftableMap(PackList old) { + Map forReuse = new HashMap<>(); + for (DfsReftable p : old.reftables) { + if (!p.invalid()) { + forReuse.put(p.desc, p); } } return forReuse; } - /** Clears the cached list of packs, forcing them to be scanned again. */ + /** + * Get comparator to sort {@link DfsReftable} by priority. + * + * @return comparator to sort {@link DfsReftable} by priority. + */ + protected Comparator reftableComparator() { + return (fa, fb) -> { + DfsPackDescription a = fa.getPackDescription(); + DfsPackDescription b = fb.getPackDescription(); + + // GC, COMPACT reftables first by higher category. + int c = category(b) - category(a); + if (c != 0) { + return c; + } + + // Lower maxUpdateIndex first. + c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex()); + if (c != 0) { + return c; + } + + // Older reftable first. + return Long.signum(a.getLastModified() - b.getLastModified()); + }; + } + + static int category(DfsPackDescription d) { + PackSource s = d.getPackSource(); + return s != null ? s.category : 0; + } + + /** + * Clears the cached list of packs, forcing them to be scanned again. + */ protected void clearCache() { packList.set(NO_PACKS); } + /** {@inheritDoc} */ @Override public void close() { - // PackList packs = packList.get(); packList.set(NO_PACKS); - - // TODO Close packs if they aren't cached. - // for (DfsPackFile p : packs.packs) - // p.close(); } - private static final class PackList { + /** Snapshot of packs scanned in a single pass. */ + public static abstract class PackList { /** All known packs, sorted. */ - final DfsPackFile[] packs; + public final DfsPackFile[] packs; + + /** All known reftables, sorted. */ + public final DfsReftable[] reftables; - PackList(final DfsPackFile[] packs) { + private long lastModified = -1; + + PackList(DfsPackFile[] packs, DfsReftable[] reftables) { this.packs = packs; + this.reftables = reftables; + } + + /** @return last modified time of all packs, in milliseconds. */ + public long getLastModified() { + if (lastModified < 0) { + long max = 0; + for (DfsPackFile pack : packs) { + max = Math.max(max, pack.getPackDescription().getLastModified()); + } + lastModified = max; + } + return lastModified; + } + + abstract boolean dirty(); + abstract void clearDirty(); + + /** + * Mark pack list as dirty. + *

+ * Used when the caller knows that new data might have been written to the + * repository that could invalidate open readers depending on this pack list, + * for example if refs are newly scanned. + */ + public abstract void markDirty(); + } + + private static final class PackListImpl extends PackList { + private volatile boolean dirty; + + PackListImpl(DfsPackFile[] packs, DfsReftable[] reftables) { + super(packs, reftables); + } + + @Override + boolean dirty() { + return dirty; + } + + @Override + void clearDirty() { + dirty = false; + } + + @Override + public void markDirty() { + dirty = true; } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,8 +43,6 @@ package org.eclipse.jgit.internal.storage.dfs; -import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC; - import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation; import org.eclipse.jgit.lib.ObjectId; @@ -59,23 +57,34 @@ this.pack = pack; } + /** {@inheritDoc} */ @Override public int getFormat() { return format; } + /** {@inheritDoc} */ @Override public int getWeight() { return (int) Math.min(length, Integer.MAX_VALUE); } + /** {@inheritDoc} */ @Override public ObjectId getDeltaBase() { return baseId; } + /** {@inheritDoc} */ @Override public boolean wasDeltaAttempted() { - return pack.getPackDescription().getPackSource() == GC; + switch (pack.getPackDescription().getPackSource()) { + case GC: + case GC_REST: + case GC_TXN: + return true; + default: + return false; + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,12 +72,14 @@ setExtendedFlag(FLAG_FOUND); } + /** {@inheritDoc} */ @Override protected void clearReuseAsIs() { super.clearReuseAsIs(); pack = null; } + /** {@inheritDoc} */ @Override public void select(StoredObjectRepresentation ref) { DfsObjectRepresentation ptr = (DfsObjectRepresentation) ref; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,11 +72,13 @@ return 0; } + /** {@inheritDoc} */ @Override public void write(int b) throws IOException { write(new byte[] { (byte) b }); } + /** {@inheritDoc} */ @Override public abstract void write(byte[] buf, int off, int len) throws IOException; @@ -91,7 +93,7 @@ * buffer to populate. Up to {@code buf.remaining()} bytes will * be read from {@code position}. * @return number of bytes actually read. - * @throws IOException + * @throws java.io.IOException * reading is not supported, or the read cannot be performed due * to DFS errors. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,24 +44,33 @@ package org.eclipse.jgit.internal.storage.dfs; import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.file.PackReverseIndex; import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; @@ -88,16 +97,15 @@ */ public class DfsPackCompactor { private final DfsRepository repo; - private final List srcPacks; + private final List srcReftables; + private final List exclude; - private final List exclude; - - private final List newPacks; - - private final List newStats; + private PackStatistics newStats; + private DfsPackDescription outDesc; private int autoAddSize; + private ReftableConfig reftableConfig; private RevWalk rw; private RevFlag added; @@ -112,10 +120,22 @@ public DfsPackCompactor(DfsRepository repository) { repo = repository; autoAddSize = 5 * 1024 * 1024; // 5 MiB - srcPacks = new ArrayList(); - exclude = new ArrayList(4); - newPacks = new ArrayList(1); - newStats = new ArrayList(1); + srcPacks = new ArrayList<>(); + srcReftables = new ArrayList<>(); + exclude = new ArrayList<>(4); + } + + /** + * Set configuration to write a reftable. + * + * @param cfg + * configuration to write a reftable. Reftable compacting is + * disabled (default) when {@code cfg} is {@code null}. + * @return {@code this} + */ + public DfsPackCompactor setReftableConfig(ReftableConfig cfg) { + reftableConfig = cfg; + return this; } /** @@ -136,13 +156,25 @@ } /** - * Automatically select packs to be included, and add them. + * Add a reftable to be compacted. + * + * @param table + * a reftable to combine. + * @return {@code this} + */ + public DfsPackCompactor add(DfsReftable table) { + srcReftables.add(table); + return this; + } + + /** + * Automatically select pack and reftables to be included, and add them. *

* Packs are selected based on size, smaller packs get included while bigger * ones are omitted. * * @return {@code this} - * @throws IOException + * @throws java.io.IOException * existing packs cannot be read. */ public DfsPackCompactor autoAdd() throws IOException { @@ -154,6 +186,16 @@ else exclude(pack); } + + if (reftableConfig != null) { + for (DfsReftable table : objdb.getReftables()) { + DfsPackDescription d = table.getPackDescription(); + if (d.getPackSource() != GC + && d.getFileSize(REFTABLE) < autoAddSize) { + add(table); + } + } + } return this; } @@ -164,7 +206,7 @@ * objects to not include. * @return {@code this}. */ - public DfsPackCompactor exclude(PackWriter.ObjectIdSet set) { + public DfsPackCompactor exclude(ObjectIdSet set) { exclude.add(set); return this; } @@ -175,7 +217,7 @@ * @param pack * objects to not include. * @return {@code this}. - * @throws IOException + * @throws java.io.IOException * pack index cannot be loaded. */ public DfsPackCompactor exclude(DfsPackFile pack) throws IOException { @@ -183,11 +225,7 @@ try (DfsReader ctx = (DfsReader) repo.newObjectReader()) { idx = pack.getPackIndex(ctx); } - return exclude(new PackWriter.ObjectIdSet() { - public boolean contains(AnyObjectId id) { - return idx.hasObject(id); - } - }); + return exclude(idx); } /** @@ -196,85 +234,175 @@ * @param pm * progress monitor to receive updates on as packing may take a * while, depending on the size of the repository. - * @throws IOException + * @throws java.io.IOException * the packs cannot be compacted. */ public void compact(ProgressMonitor pm) throws IOException { - if (pm == null) + if (pm == null) { pm = NullProgressMonitor.INSTANCE; + } DfsObjDatabase objdb = repo.getObjectDatabase(); - try (DfsReader ctx = (DfsReader) objdb.newReader()) { - PackConfig pc = new PackConfig(repo); - pc.setIndexVersion(2); - pc.setDeltaCompress(false); - pc.setReuseDeltas(true); - pc.setReuseObjects(true); + try (DfsReader ctx = objdb.newReader()) { + if (reftableConfig != null && !srcReftables.isEmpty()) { + compactReftables(ctx); + } + compactPacks(ctx, pm); - PackWriter pw = new PackWriter(pc, ctx); - try { - pw.setDeltaBaseAsOffset(true); - pw.setReuseDeltaCommits(false); + List commit = getNewPacks(); + Collection remove = toPrune(); + if (!commit.isEmpty() || !remove.isEmpty()) { + objdb.commitPack(commit, remove); + } + } finally { + rw = null; + } + } - addObjectsToPack(pw, ctx, pm); - if (pw.getObjectCount() == 0) { - List remove = toPrune(); - if (remove.size() > 0) - objdb.commitPack( - Collections.emptyList(), - remove); - return; - } + private void compactPacks(DfsReader ctx, ProgressMonitor pm) + throws IOException, IncorrectObjectTypeException { + DfsObjDatabase objdb = repo.getObjectDatabase(); + PackConfig pc = new PackConfig(repo); + pc.setIndexVersion(2); + pc.setDeltaCompress(false); + pc.setReuseDeltas(true); + pc.setReuseObjects(true); - boolean rollback = true; - DfsPackDescription pack = objdb.newPack(COMPACT); - try { - writePack(objdb, pack, pw, pm); - writeIndex(objdb, pack, pw); - - PackStatistics stats = pw.getStatistics(); - pw.close(); - pw = null; - - pack.setPackStats(stats); - objdb.commitPack(Collections.singletonList(pack), toPrune()); - newPacks.add(pack); - newStats.add(stats); - rollback = false; - } finally { - if (rollback) - objdb.rollbackPack(Collections.singletonList(pack)); - } + PackWriter pw = new PackWriter(pc, ctx); + try { + pw.setDeltaBaseAsOffset(true); + pw.setReuseDeltaCommits(false); + + addObjectsToPack(pw, ctx, pm); + if (pw.getObjectCount() == 0) { + return; + } + + boolean rollback = true; + initOutDesc(objdb); + try { + writePack(objdb, outDesc, pw, pm); + writeIndex(objdb, outDesc, pw); + + PackStatistics stats = pw.getStatistics(); + pw.close(); + pw = null; + + outDesc.setPackStats(stats); + newStats = stats; + rollback = false; } finally { - if (pw != null) - pw.close(); + if (rollback) { + objdb.rollbackPack(Collections.singletonList(outDesc)); + } } } finally { - rw = null; + if (pw != null) { + pw.close(); + } } } - /** @return all of the source packs that fed into this compaction. */ - public List getSourcePacks() { - return toPrune(); + private long estimatePackSize() { + // Every pack file contains 12 bytes of header and 20 bytes of trailer. + // Include the final pack file header and trailer size here and ignore + // the same from individual pack files. + long size = 32; + for (DfsPackFile pack : srcPacks) { + size += pack.getPackDescription().getFileSize(PACK) - 32; + } + return size; + } + + private void compactReftables(DfsReader ctx) throws IOException { + DfsObjDatabase objdb = repo.getObjectDatabase(); + Collections.sort(srcReftables, objdb.reftableComparator()); + + try (ReftableStack stack = ReftableStack.open(ctx, srcReftables)) { + initOutDesc(objdb); + ReftableCompactor compact = new ReftableCompactor(); + compact.addAll(stack.readers()); + compact.setIncludeDeletes(true); + writeReftable(objdb, outDesc, compact); + } + } + + private void initOutDesc(DfsObjDatabase objdb) throws IOException { + if (outDesc == null) { + outDesc = objdb.newPack(COMPACT, estimatePackSize()); + } + } + + /** + * Get all of the source packs that fed into this compaction. + * + * @return all of the source packs that fed into this compaction. + */ + public Collection getSourcePacks() { + Set src = new HashSet<>(); + for (DfsPackFile pack : srcPacks) { + src.add(pack.getPackDescription()); + } + for (DfsReftable table : srcReftables) { + src.add(table.getPackDescription()); + } + return src; } - /** @return new packs created by this compaction. */ + /** + * Get new packs created by this compaction. + * + * @return new packs created by this compaction. + */ public List getNewPacks() { - return newPacks; + return outDesc != null + ? Collections.singletonList(outDesc) + : Collections.emptyList(); } - /** @return statistics corresponding to the {@link #getNewPacks()}. */ + /** + * Get statistics corresponding to the {@link #getNewPacks()}. + * May be null if statistics are not available. + * + * @return statistics corresponding to the {@link #getNewPacks()}. + * + */ public List getNewPackStatistics() { - return newStats; + return outDesc != null + ? Collections.singletonList(newStats) + : Collections.emptyList(); } - private List toPrune() { - int cnt = srcPacks.size(); - List all = new ArrayList(cnt); - for (DfsPackFile pack : srcPacks) - all.add(pack.getPackDescription()); - return all; + private Collection toPrune() { + Set packs = new HashSet<>(); + for (DfsPackFile pack : srcPacks) { + packs.add(pack.getPackDescription()); + } + + Set reftables = new HashSet<>(); + for (DfsReftable table : srcReftables) { + reftables.add(table.getPackDescription()); + } + + for (Iterator i = packs.iterator(); i.hasNext();) { + DfsPackDescription d = i.next(); + if (d.hasFileExt(REFTABLE) && !reftables.contains(d)) { + i.remove(); + } + } + + for (Iterator i = reftables.iterator(); + i.hasNext();) { + DfsPackDescription d = i.next(); + if (d.hasFileExt(PACK) && !packs.contains(d)) { + i.remove(); + } + } + + Set toPrune = new HashSet<>(); + toPrune.addAll(packs); + toPrune.addAll(reftables); + return toPrune; } private void addObjectsToPack(PackWriter pw, DfsReader ctx, @@ -284,6 +412,7 @@ // older packs, allowing the PackWriter to be handed newer objects // first and older objects last. Collections.sort(srcPacks, new Comparator() { + @Override public int compare(DfsPackFile a, DfsPackFile b) { return a.getPackDescription().compareTo(b.getPackDescription()); } @@ -292,7 +421,7 @@ rw = new RevWalk(ctx); added = rw.newFlag("ADDED"); //$NON-NLS-1$ isBase = rw.newFlag("IS_BASE"); //$NON-NLS-1$ - List baseObjects = new BlockList(); + List baseObjects = new BlockList<>(); pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN); for (DfsPackFile src : srcPacks) { @@ -336,19 +465,20 @@ private List toInclude(DfsPackFile src, DfsReader ctx) throws IOException { PackIndex srcIdx = src.getPackIndex(ctx); - List want = new BlockList( + List want = new BlockList<>( (int) srcIdx.getObjectCount()); SCAN: for (PackIndex.MutableEntry ent : srcIdx) { ObjectId id = ent.toObjectId(); RevObject obj = rw.lookupOrNull(id); if (obj != null && (obj.has(added) || obj.has(isBase))) continue; - for (PackWriter.ObjectIdSet e : exclude) + for (ObjectIdSet e : exclude) if (e.contains(id)) continue SCAN; want.add(new ObjectIdWithOffset(id, ent.getOffset())); } Collections.sort(want, new Comparator() { + @Override public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) { return Long.signum(a.offset - b.offset); } @@ -359,30 +489,47 @@ private static void writePack(DfsObjDatabase objdb, DfsPackDescription pack, PackWriter pw, ProgressMonitor pm) throws IOException { - DfsOutputStream out = objdb.writeFile(pack, PACK); - try { + try (DfsOutputStream out = objdb.writeFile(pack, PACK)) { pw.writePack(pm, pm, out); pack.addFileExt(PACK); - } finally { - out.close(); + pack.setBlockSize(PACK, out.blockSize()); } } private static void writeIndex(DfsObjDatabase objdb, DfsPackDescription pack, PackWriter pw) throws IOException { - DfsOutputStream out = objdb.writeFile(pack, INDEX); - try { + try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) { CountingOutputStream cnt = new CountingOutputStream(out); pw.writeIndex(cnt); pack.addFileExt(INDEX); pack.setFileSize(INDEX, cnt.getCount()); + pack.setBlockSize(INDEX, out.blockSize()); pack.setIndexVersion(pw.getIndexVersion()); - } finally { - out.close(); } } + private void writeReftable(DfsObjDatabase objdb, DfsPackDescription pack, + ReftableCompactor compact) throws IOException { + try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) { + compact.setConfig(configureReftable(reftableConfig, out)); + compact.compact(out); + pack.addFileExt(REFTABLE); + pack.setReftableStats(compact.getStats()); + } + } + + static ReftableConfig configureReftable(ReftableConfig cfg, + DfsOutputStream out) { + int bs = out.blockSize(); + if (bs > 0) { + cfg = new ReftableConfig(cfg); + cfg.setRefBlockSize(bs); + cfg.setAlignBlocks(true); + } + return cfg; + } + private static class ObjectIdWithOffset extends ObjectId { final long offset; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,12 +44,13 @@ package org.eclipse.jgit.internal.storage.dfs; import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; -import java.util.HashMap; -import java.util.Map; +import java.util.Arrays; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter; import org.eclipse.jgit.storage.pack.PackStatistics; /** @@ -62,24 +63,21 @@ */ public class DfsPackDescription implements Comparable { private final DfsRepositoryDescription repoDesc; - private final String packName; - private PackSource packSource; - private long lastModified; - - private final Map sizeMap; - + private long[] sizeMap; + private int[] blockSizeMap; private long objectCount; - private long deltaCount; + private long minUpdateIndex; + private long maxUpdateIndex; - private PackStatistics stats; - + private PackStatistics packStats; + private ReftableWriter.Stats refStats; private int extensions; - private int indexVersion; + private long estimatedPackSize; /** * Initialize a description by pack name and repository. @@ -100,10 +98,17 @@ this.repoDesc = repoDesc; int dot = name.lastIndexOf('.'); this.packName = (dot < 0) ? name : name.substring(0, dot); - this.sizeMap = new HashMap(PackExt.values().length * 2); + + int extCnt = PackExt.values().length; + sizeMap = new long[extCnt]; + blockSizeMap = new int[extCnt]; } - /** @return description of the repository. */ + /** + * Get description of the repository. + * + * @return description of the repository. + */ public DfsRepositoryDescription getRepositoryDescription() { return repoDesc; } @@ -119,15 +124,19 @@ } /** + * Whether the pack file extension is known to exist. + * * @param ext * the file extension - * @return whether the pack file extensions is known to exist. + * @return whether the pack file extension is known to exist. */ public boolean hasFileExt(PackExt ext) { return (extensions & ext.getBit()) != 0; } /** + * Get file name + * * @param ext * the file extension * @return name of the file. @@ -136,12 +145,30 @@ return packName + '.' + ext.getExtension(); } - /** @return the source of the pack. */ + /** + * Get cache key for use by the block cache. + * + * @param ext + * the file extension. + * @return cache key for use by the block cache. + */ + public DfsStreamKey getStreamKey(PackExt ext) { + return DfsStreamKey.of(getRepositoryDescription(), getFileName(ext), + ext); + } + + /** + * Get the source of the pack. + * + * @return the source of the pack. + */ public PackSource getPackSource() { return packSource; } /** + * Set the source of the pack. + * * @param source * the source of the pack. * @return {@code this} @@ -151,12 +178,18 @@ return this; } - /** @return time the pack was created, in milliseconds. */ + /** + * Get time the pack was created, in milliseconds. + * + * @return time the pack was created, in milliseconds. + */ public long getLastModified() { return lastModified; } /** + * Set time the pack was created, in milliseconds. + * * @param timeMillis * time the pack was created, in milliseconds. 0 if not known. * @return {@code this} @@ -167,6 +200,50 @@ } /** + * Get minUpdateIndex for the reftable, if present. + * + * @return minUpdateIndex for the reftable, if present. + */ + public long getMinUpdateIndex() { + return minUpdateIndex; + } + + /** + * Set minUpdateIndex for the reftable. + * + * @param min + * minUpdateIndex for the reftable. + * @return {@code this} + */ + public DfsPackDescription setMinUpdateIndex(long min) { + minUpdateIndex = min; + return this; + } + + /** + * Get maxUpdateIndex for the reftable, if present. + * + * @return maxUpdateIndex for the reftable, if present. + */ + public long getMaxUpdateIndex() { + return maxUpdateIndex; + } + + /** + * Set maxUpdateIndex for the reftable. + * + * @param max + * maxUpdateIndex for the reftable. + * @return {@code this} + */ + public DfsPackDescription setMaxUpdateIndex(long max) { + maxUpdateIndex = max; + return this; + } + + /** + * Set size of the file in bytes. + * * @param ext * the file extension. * @param bytes @@ -175,26 +252,93 @@ * @return {@code this} */ public DfsPackDescription setFileSize(PackExt ext, long bytes) { - sizeMap.put(ext, Long.valueOf(Math.max(0, bytes))); + int i = ext.getPosition(); + if (i >= sizeMap.length) { + sizeMap = Arrays.copyOf(sizeMap, i + 1); + } + sizeMap[i] = Math.max(0, bytes); return this; } /** + * Get size of the file, in bytes. + * * @param ext * the file extension. * @return size of the file, in bytes. If 0 the file size is not yet known. */ public long getFileSize(PackExt ext) { - Long size = sizeMap.get(ext); - return size == null ? 0 : size.longValue(); + int i = ext.getPosition(); + return i < sizeMap.length ? sizeMap[i] : 0; + } + + /** + * Get blockSize of the file, in bytes. + * + * @param ext + * the file extension. + * @return blockSize of the file, in bytes. If 0 the blockSize size is not + * yet known and may be discovered when opening the file. + */ + public int getBlockSize(PackExt ext) { + int i = ext.getPosition(); + return i < blockSizeMap.length ? blockSizeMap[i] : 0; } - /** @return number of objects in the pack. */ + /** + * Set blockSize of the file, in bytes. + * + * @param ext + * the file extension. + * @param blockSize + * blockSize of the file, in bytes. If 0 the blockSize is not + * known and will be determined on first read. + * @return {@code this} + */ + public DfsPackDescription setBlockSize(PackExt ext, int blockSize) { + int i = ext.getPosition(); + if (i >= blockSizeMap.length) { + blockSizeMap = Arrays.copyOf(blockSizeMap, i + 1); + } + blockSizeMap[i] = Math.max(0, blockSize); + return this; + } + + /** + * Set estimated size of the .pack file in bytes. + * + * @param estimatedPackSize + * estimated size of the .pack file in bytes. If 0 the pack file + * size is unknown. + * @return {@code this} + */ + public DfsPackDescription setEstimatedPackSize(long estimatedPackSize) { + this.estimatedPackSize = Math.max(0, estimatedPackSize); + return this; + } + + /** + * Get estimated size of the .pack file in bytes. + * + * @return estimated size of the .pack file in bytes. If 0 the pack file + * size is unknown. + */ + public long getEstimatedPackSize() { + return estimatedPackSize; + } + + /** + * Get number of objects in the pack. + * + * @return number of objects in the pack. + */ public long getObjectCount() { return objectCount; } /** + * Set number of objects in the pack. + * * @param cnt * number of objects in the pack. * @return {@code this} @@ -204,12 +348,18 @@ return this; } - /** @return number of delta compressed objects in the pack. */ + /** + * Get number of delta compressed objects in the pack. + * + * @return number of delta compressed objects in the pack. + */ public long getDeltaCount() { return deltaCount; } /** + * Set number of delta compressed objects in the pack. + * * @param cnt * number of delta compressed objects in the pack. * @return {@code this} @@ -220,17 +370,19 @@ } /** + * Get statistics from PackWriter, if the pack was built with it. + * * @return statistics from PackWriter, if the pack was built with it. * Generally this is only available for packs created by * DfsGarbageCollector or DfsPackCompactor, and only when the pack * is being committed to the repository. */ public PackStatistics getPackStats() { - return stats; + return packStats; } DfsPackDescription setPackStats(PackStatistics stats) { - this.stats = stats; + this.packStats = stats; setFileSize(PACK, stats.getTotalBytes()); setObjectCount(stats.getTotalObjects()); setDeltaCount(stats.getTotalDeltas()); @@ -238,21 +390,45 @@ } /** + * Get stats from the sibling reftable, if created. + * + * @return stats from the sibling reftable, if created. + */ + public ReftableWriter.Stats getReftableStats() { + return refStats; + } + + void setReftableStats(ReftableWriter.Stats stats) { + this.refStats = stats; + setMinUpdateIndex(stats.minUpdateIndex()); + setMaxUpdateIndex(stats.maxUpdateIndex()); + setFileSize(REFTABLE, stats.totalBytes()); + setBlockSize(REFTABLE, stats.refBlockSize()); + } + + /** * Discard the pack statistics, if it was populated. * * @return {@code this} */ public DfsPackDescription clearPackStats() { - stats = null; + packStats = null; + refStats = null; return this; } - /** @return the version of the index file written. */ + /** + * Get the version of the index file written. + * + * @return the version of the index file written. + */ public int getIndexVersion() { return indexVersion; } /** + * Set the version of the index file written. + * * @param version * the version of the index file written. * @return {@code this} @@ -262,11 +438,13 @@ return this; } + /** {@inheritDoc} */ @Override public int hashCode() { return packName.hashCode(); } + /** {@inheritDoc} */ @Override public boolean equals(Object b) { if (b instanceof DfsPackDescription) { @@ -278,16 +456,16 @@ } /** + * {@inheritDoc} + *

* Sort packs according to the optimal lookup ordering. *

* This method tries to position packs in the order readers should examine * them when looking for objects by SHA-1. The default tries to sort packs * with more recent modification dates before older packs, and packs with * fewer objects before packs with more objects. - * - * @param b - * the other pack. */ + @Override public int compareTo(DfsPackDescription b) { // Cluster by PackSource, pushing UNREACHABLE_GARBAGE to the end. PackSource as = getPackSource(); @@ -298,6 +476,17 @@ return cmp; } + // Tie break GC type packs by smallest first. There should be at most + // one of each source, but when multiple exist concurrent GCs may have + // run. Preferring the smaller file selects higher quality delta + // compression, placing less demand on the DfsBlockCache. + if (as != null && as == bs && isGC(as)) { + int cmp = Long.signum(getFileSize(PACK) - b.getFileSize(PACK)); + if (cmp != 0) { + return cmp; + } + } + // Newer packs should sort first. int cmp = Long.signum(b.getLastModified() - getLastModified()); if (cmp != 0) @@ -309,6 +498,18 @@ return Long.signum(getObjectCount() - b.getObjectCount()); } + static boolean isGC(PackSource s) { + switch (s) { + case GC: + case GC_REST: + case GC_TXN: + return true; + default: + return false; + } + } + + /** {@inheritDoc} */ @Override public String toString() { return getFileName(PackExt.PACK); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,7 +72,6 @@ import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.file.PackReverseIndex; import org.eclipse.jgit.internal.storage.pack.BinaryDelta; -import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackOutputStream; import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation; import org.eclipse.jgit.lib.AbbreviatedObjectId; @@ -88,53 +87,7 @@ * delta packed format yielding high compression of lots of object where some * objects are similar. */ -public final class DfsPackFile { - /** - * File offset used to cache {@link #index} in {@link DfsBlockCache}. - *

- * To better manage memory, the forward index is stored as a single block in - * the block cache under this file position. A negative value is used - * because it cannot occur in a normal pack file, and it is less likely to - * collide with a valid data block from the file as the high bits will all - * be set when treated as an unsigned long by the cache code. - */ - private static final long POS_INDEX = -1; - - /** Offset used to cache {@link #reverseIndex}. See {@link #POS_INDEX}. */ - private static final long POS_REVERSE_INDEX = -2; - - /** Offset used to cache {@link #bitmapIndex}. See {@link #POS_INDEX}. */ - private static final long POS_BITMAP_INDEX = -3; - - /** Cache that owns this pack file and its data. */ - private final DfsBlockCache cache; - - /** Description of the pack file's storage. */ - private final DfsPackDescription packDesc; - - /** Unique identity of this pack while in-memory. */ - final DfsPackKey key; - - /** - * Total number of bytes in this pack file. - *

- * This field initializes to -1 and gets populated when a block is loaded. - */ - volatile long length; - - /** - * Preferred alignment for loading blocks from the backing file. - *

- * It is initialized to 0 and filled in on the first read made from the - * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS - * storing 4091 user bytes and 5 bytes block metadata into a lower level - * 4096 byte block on disk. - */ - private volatile int blockSize; - - /** True once corruption has been detected that cannot be worked around. */ - private volatile boolean invalid; - +public final class DfsPackFile extends BlockBasedFile { /** * Lock for initialization of {@link #index} and {@link #corruptObjects}. *

@@ -167,51 +120,43 @@ * cache that owns the pack data. * @param desc * description of the pack within the DFS. - * @param key - * interned key used to identify blocks in the block cache. */ - DfsPackFile(DfsBlockCache cache, DfsPackDescription desc, DfsPackKey key) { - this.cache = cache; - this.packDesc = desc; - this.key = key; + DfsPackFile(DfsBlockCache cache, DfsPackDescription desc) { + super(cache, desc, PACK); + + int bs = desc.getBlockSize(PACK); + if (bs > 0) { + setBlockSize(bs); + } - length = desc.getFileSize(PACK); - if (length <= 0) - length = -1; + long sz = desc.getFileSize(PACK); + length = sz > 0 ? sz : -1; } - /** @return description that was originally used to configure this pack file. */ + /** + * Get description that was originally used to configure this pack file. + * + * @return description that was originally used to configure this pack file. + */ public DfsPackDescription getPackDescription() { - return packDesc; + return desc; } /** + * Whether the pack index file is loaded and cached in memory. + * * @return whether the pack index file is loaded and cached in memory. - * @since 2.2 */ public boolean isIndexLoaded() { DfsBlockCache.Ref idxref = index; return idxref != null && idxref.has(); } - /** @return bytes cached in memory for this pack, excluding the index. */ - public long getCachedSize() { - return key.cachedSize.get(); - } - - String getPackName() { - return packDesc.getFileName(PACK); - } - - void setBlockSize(int newSize) { - blockSize = newSize; - } - void setPackIndex(PackIndex idx) { long objCnt = idx.getObjectCount(); int recSize = Constants.OBJECT_ID_LENGTH + 8; - int sz = (int) Math.min(objCnt * recSize, Integer.MAX_VALUE); - index = cache.put(key, POS_INDEX, sz, idx); + long sz = objCnt * recSize; + index = cache.putRef(desc.getStreamKey(INDEX), sz, idx); } /** @@ -221,7 +166,7 @@ * reader context to support reading from the backing store if * the index is not already loaded in memory. * @return the PackIndex. - * @throws IOException + * @throws java.io.IOException * the pack index is not available, or is corrupt. */ public PackIndex getPackIndex(DfsReader ctx) throws IOException { @@ -236,8 +181,9 @@ return idx; } - if (invalid) - throw new PackInvalidException(getPackName()); + if (invalid) { + throw new PackInvalidException(getFileName(), invalidatingCause); + } Repository.getGlobalListenerList() .dispatch(new BeforeDfsPackIndexLoadedEvent(this)); @@ -250,9 +196,21 @@ return idx; } + DfsStreamKey idxKey = desc.getStreamKey(INDEX); + idxref = cache.getRef(idxKey); + if (idxref != null) { + PackIndex idx = idxref.get(); + if (idx != null) { + index = idxref; + return idx; + } + } + PackIndex idx; try { - ReadableChannel rc = ctx.db.openFile(packDesc, INDEX); + ctx.stats.readIdx++; + long start = System.nanoTime(); + ReadableChannel rc = ctx.db.openFile(desc, INDEX); try { InputStream in = Channels.newInputStream(rc); int wantSize = 8192; @@ -261,25 +219,24 @@ bs = (wantSize / bs) * bs; else if (bs <= 0) bs = wantSize; - in = new BufferedInputStream(in, bs); - idx = PackIndex.read(in); + idx = PackIndex.read(new BufferedInputStream(in, bs)); + ctx.stats.readIdxBytes += rc.position(); } finally { rc.close(); + ctx.stats.readIdxMicros += elapsedMicros(start); } } catch (EOFException e) { invalid = true; - IOException e2 = new IOException(MessageFormat.format( + invalidatingCause = e; + throw new IOException(MessageFormat.format( DfsText.get().shortReadOfIndex, - packDesc.getFileName(INDEX))); - e2.initCause(e); - throw e2; + desc.getFileName(INDEX)), e); } catch (IOException e) { invalid = true; - IOException e2 = new IOException(MessageFormat.format( + invalidatingCause = e; + throw new IOException(MessageFormat.format( DfsText.get().cannotReadIndex, - packDesc.getFileName(INDEX))); - e2.initCause(e); - throw e2; + desc.getFileName(INDEX)), e); } setPackIndex(idx); @@ -288,12 +245,13 @@ } final boolean isGarbage() { - return packDesc.getPackSource() == UNREACHABLE_GARBAGE; + return desc.getPackSource() == UNREACHABLE_GARBAGE; } PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException { - if (invalid || isGarbage()) + if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX)) return null; + DfsBlockCache.Ref idxref = bitmapIndex; if (idxref != null) { PackBitmapIndex idx = idxref.get(); @@ -301,9 +259,6 @@ return idx; } - if (!packDesc.hasFileExt(PackExt.BITMAP_INDEX)) - return null; - synchronized (initLock) { idxref = bitmapIndex; if (idxref != null) { @@ -312,10 +267,22 @@ return idx; } + DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX); + idxref = cache.getRef(bitmapKey); + if (idxref != null) { + PackBitmapIndex idx = idxref.get(); + if (idx != null) { + bitmapIndex = idxref; + return idx; + } + } + long size; PackBitmapIndex idx; try { - ReadableChannel rc = ctx.db.openFile(packDesc, BITMAP_INDEX); + ctx.stats.readBitmap++; + long start = System.nanoTime(); + ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX); try { InputStream in = Channels.newInputStream(rc); int wantSize = 8192; @@ -330,23 +297,20 @@ } finally { size = rc.position(); rc.close(); + ctx.stats.readIdxBytes += size; + ctx.stats.readIdxMicros += elapsedMicros(start); } } catch (EOFException e) { - IOException e2 = new IOException(MessageFormat.format( + throw new IOException(MessageFormat.format( DfsText.get().shortReadOfIndex, - packDesc.getFileName(BITMAP_INDEX))); - e2.initCause(e); - throw e2; + desc.getFileName(BITMAP_INDEX)), e); } catch (IOException e) { - IOException e2 = new IOException(MessageFormat.format( + throw new IOException(MessageFormat.format( DfsText.get().cannotReadIndex, - packDesc.getFileName(BITMAP_INDEX))); - e2.initCause(e); - throw e2; + desc.getFileName(BITMAP_INDEX)), e); } - bitmapIndex = cache.put(key, POS_BITMAP_INDEX, - (int) Math.min(size, Integer.MAX_VALUE), idx); + bitmapIndex = cache.putRef(bitmapKey, size, idx); return idx; } } @@ -367,11 +331,21 @@ return revidx; } + DfsStreamKey revKey = + new DfsStreamKey.ForReverseIndex(desc.getStreamKey(INDEX)); + revref = cache.getRef(revKey); + if (revref != null) { + PackReverseIndex idx = revref.get(); + if (idx != null) { + reverseIndex = revref; + return idx; + } + } + PackIndex idx = idx(ctx); PackReverseIndex revidx = new PackReverseIndex(idx); - int sz = (int) Math.min( - idx.getObjectCount() * 8, Integer.MAX_VALUE); - reverseIndex = cache.put(key, POS_REVERSE_INDEX, sz, revidx); + long cnt = idx.getObjectCount(); + reverseIndex = cache.putRef(revKey, cnt * 8, revidx); return revidx; } } @@ -385,7 +359,7 @@ * @param id * object to be located. * @return true if the object exists in this pack; false if it does not. - * @throws IOException + * @throws java.io.IOException * the pack index is not available, or is corrupt. */ public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException { @@ -420,13 +394,6 @@ idx(ctx).resolve(matches, id, matchLimit); } - /** Release all memory used by this DfsPackFile instance. */ - public void close() { - cache.remove(this); - index = null; - reverseIndex = null; - } - /** * Obtain the total number of objects available in this pack. This method * relies on pack index, giving number of effectively available objects. @@ -479,26 +446,48 @@ private void copyPackThroughCache(PackOutputStream out, DfsReader ctx) throws IOException { - long position = 12; - long remaining = length - (12 + 20); - while (0 < remaining) { - DfsBlock b = cache.getOrLoad(this, position, ctx); - int ptr = (int) (position - b.start); - int n = (int) Math.min(b.size() - ptr, remaining); - b.write(out, position, n); - position += n; - remaining -= n; + ReadableChannel rc = null; + try { + long position = 12; + long remaining = length - (12 + 20); + while (0 < remaining) { + DfsBlock b; + if (rc != null) { + b = cache.getOrLoad(this, position, ctx, rc); + } else { + b = cache.get(key, alignToBlock(position)); + if (b == null) { + rc = ctx.db.openFile(desc, PACK); + int sz = ctx.getOptions().getStreamPackBufferSize(); + if (sz > 0) { + rc.setReadAheadBytes(sz); + } + b = cache.getOrLoad(this, position, ctx, rc); + } + } + + int ptr = (int) (position - b.start); + int n = (int) Math.min(b.size() - ptr, remaining); + b.write(out, position, n); + position += n; + remaining -= n; + } + } finally { + if (rc != null) { + rc.close(); + } } } private long copyPackBypassCache(PackOutputStream out, DfsReader ctx) throws IOException { - try (ReadableChannel rc = ctx.db.openFile(packDesc, PACK)) { + try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) { ByteBuffer buf = newCopyBuffer(out, rc); if (ctx.getOptions().getStreamPackBufferSize() > 0) rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize()); long position = 12; long remaining = length - (12 + 20); + boolean packHeadSkipped = false; while (0 < remaining) { DfsBlock b = cache.get(key, alignToBlock(position)); if (b != null) { @@ -508,6 +497,7 @@ position += n; remaining -= n; rc.position(position); + packHeadSkipped = true; continue; } @@ -517,7 +507,14 @@ throw packfileIsTruncated(); else if (n > remaining) n = (int) remaining; - out.write(buf.array(), 0, n); + + if (!packHeadSkipped) { + // Need skip the 'PACK' header for the first read + out.write(buf.array(), 12, n - 12); + packHeadSkipped = true; + } else { + out.write(buf.array(), 0, n); + } position += n; remaining -= n; } @@ -545,10 +542,8 @@ try { readFully(src.offset, buf, 0, 20, ctx); } catch (IOException ioError) { - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(ioError); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(src, + ioError); } int c = buf[0] & 0xff; final int typeCode = (c >> 4) & 7; @@ -623,7 +618,7 @@ setCorrupt(src.offset); throw new CorruptObjectException(MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackName())); + Long.valueOf(src.offset), getFileName())); } } else if (validate) { assert(crc1 != null); @@ -665,19 +660,15 @@ CorruptObjectException corruptObject = new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackName())); - corruptObject.initCause(dataFormat); + Long.valueOf(src.offset), getFileName()), + dataFormat); - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(corruptObject); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(src, + corruptObject); } catch (IOException ioError) { - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(ioError); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(src, + ioError); } if (quickCopy != null) { @@ -727,24 +718,18 @@ if (crc2.getValue() != expectedCRC) { throw new CorruptObjectException(MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackName())); + Long.valueOf(src.offset), getFileName())); } } } } - boolean invalid() { - return invalid; - } - - void setInvalid() { - invalid = true; - } - private IOException packfileIsTruncated() { invalid = true; - return new IOException(MessageFormat.format( - JGitText.get().packfileIsTruncated, getPackName())); + IOException exc = new IOException(MessageFormat.format( + JGitText.get().packfileIsTruncated, getFileName())); + invalidatingCause = exc; + return exc; } private void readFully(long position, byte[] dstbuf, int dstoff, int cnt, @@ -753,100 +738,6 @@ throw new EOFException(); } - long alignToBlock(long pos) { - int size = blockSize; - if (size == 0) - size = cache.getBlockSize(); - return (pos / size) * size; - } - - DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException { - return cache.getOrLoad(this, pos, ctx); - } - - DfsBlock readOneBlock(long pos, DfsReader ctx) - throws IOException { - if (invalid) - throw new PackInvalidException(getPackName()); - - ReadableChannel rc = ctx.db.openFile(packDesc, PACK); - try { - int size = blockSize(rc); - pos = (pos / size) * size; - - // If the size of the file is not yet known, try to discover it. - // Channels may choose to return -1 to indicate they don't - // know the length yet, in this case read up to the size unit - // given by the caller, then recheck the length. - long len = length; - if (len < 0) { - len = rc.size(); - if (0 <= len) - length = len; - } - - if (0 <= len && len < pos + size) - size = (int) (len - pos); - if (size <= 0) - throw new EOFException(MessageFormat.format( - DfsText.get().shortReadOfBlock, Long.valueOf(pos), - getPackName(), Long.valueOf(0), Long.valueOf(0))); - - byte[] buf = new byte[size]; - rc.position(pos); - int cnt = read(rc, ByteBuffer.wrap(buf, 0, size)); - if (cnt != size) { - if (0 <= len) { - throw new EOFException(MessageFormat.format( - DfsText.get().shortReadOfBlock, - Long.valueOf(pos), - getPackName(), - Integer.valueOf(size), - Integer.valueOf(cnt))); - } - - // Assume the entire thing was read in a single shot, compact - // the buffer to only the space required. - byte[] n = new byte[cnt]; - System.arraycopy(buf, 0, n, 0, n.length); - buf = n; - } else if (len < 0) { - // With no length at the start of the read, the channel should - // have the length available at the end. - length = len = rc.size(); - } - - DfsBlock v = new DfsBlock(key, pos, buf); - return v; - } finally { - rc.close(); - } - } - - private int blockSize(ReadableChannel rc) { - // If the block alignment is not yet known, discover it. Prefer the - // larger size from either the cache or the file itself. - int size = blockSize; - if (size == 0) { - size = rc.blockSize(); - if (size <= 0) - size = cache.getBlockSize(); - else if (size < cache.getBlockSize()) - size = (cache.getBlockSize() / size) * size; - blockSize = size; - } - return size; - } - - private static int read(ReadableChannel rc, ByteBuffer buf) - throws IOException { - int n; - do { - n = rc.read(buf); - } while (0 < n && buf.hasRemaining()); - return buf.position(); - } - ObjectLoader load(DfsReader ctx, long pos) throws IOException { try { @@ -980,12 +871,11 @@ return new ObjectLoader.SmallObject(type, data); } catch (DataFormatException dfe) { - CorruptObjectException coe = new CorruptObjectException( + throw new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos), - getPackName())); - coe.initCause(dfe); - throw coe; + getFileName()), + dfe); } } @@ -1128,12 +1018,11 @@ try { return BinaryDelta.getResultSize(getDeltaHeader(ctx, deltaAt)); } catch (DataFormatException dfe) { - CorruptObjectException coe = new CorruptObjectException( + throw new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos), - getPackName())); - coe.initCause(dfe); - throw coe; + getFileName()), + dfe); } } @@ -1189,7 +1078,7 @@ } } - private boolean isCorrupt(long offset) { + boolean isCorrupt(long offset) { LongList list = corruptObjects; if (list == null) return false; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2011, 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.internal.storage.dfs; - -import java.util.concurrent.atomic.AtomicLong; - -final class DfsPackKey { - final int hash; - - final AtomicLong cachedSize; - - DfsPackKey() { - // Multiply by 31 here so we can more directly combine with another - // value without doing the multiply there. - // - hash = System.identityHashCode(this) * 31; - cachedSize = new AtomicLong(); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,7 +63,9 @@ import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackedObjectInfo; -/** Parses a pack stream into the DFS, by creating a new pack and index. */ +/** + * Parses a pack stream into the DFS, by creating a new pack and index. + */ public class DfsPackParser extends PackParser { private final DfsObjDatabase objdb; @@ -94,7 +96,7 @@ private DfsPackDescription packDsc; /** Key used during delta resolution reading delta chains. */ - private DfsPackKey packKey; + private DfsStreamKey packKey; /** If the index was small enough, the entire index after writing. */ private PackIndex packIndex; @@ -132,6 +134,7 @@ this.packDigest = Constants.newMessageDigest(); } + /** {@inheritDoc} */ @Override public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving) throws IOException { @@ -150,12 +153,13 @@ readBlock = null; packDsc.addFileExt(PACK); packDsc.setFileSize(PACK, packEnd); + packDsc.setBlockSize(PACK, blockSize); writePackIndex(); objdb.commitPack(Collections.singletonList(packDsc), null); rollback = false; - DfsPackFile p = blockCache.getOrCreate(packDsc, packKey); + DfsPackFile p = new DfsPackFile(blockCache, packDsc); p.setBlockSize(blockSize); if (packIndex != null) p.setPackIndex(packIndex); @@ -192,11 +196,16 @@ } } - /** @return description of the imported pack, if one was made. */ + /** + * Get description of the imported pack, if one was made. + * + * @return description of the imported pack, if one was made. + */ public DfsPackDescription getPackDescription() { return packDsc; } + /** {@inheritDoc} */ @Override protected void onPackHeader(long objectCount) throws IOException { if (objectCount == 0) { @@ -206,9 +215,9 @@ } packDsc = objdb.newPack(DfsObjDatabase.PackSource.RECEIVE); - packKey = new DfsPackKey(); - out = objdb.writeFile(packDsc, PACK); + packKey = packDsc.getStreamKey(PACK); + int size = out.blockSize(); if (size <= 0) size = blockCache.getBlockSize(); @@ -218,29 +227,34 @@ currBuf = new byte[blockSize]; } + /** {@inheritDoc} */ @Override protected void onBeginWholeObject(long streamPosition, int type, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected void onEndWholeObject(PackedObjectInfo info) throws IOException { info.setCRC((int) crc.getValue()); } + /** {@inheritDoc} */ @Override protected void onBeginOfsDelta(long streamPosition, long baseStreamPosition, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected UnresolvedDelta onEndDelta() throws IOException { UnresolvedDelta delta = new UnresolvedDelta(); @@ -248,24 +262,28 @@ return delta; } + /** {@inheritDoc} */ @Override protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode, byte[] data) throws IOException { // DfsPackParser ignores this event. } + /** {@inheritDoc} */ @Override protected void onObjectHeader(Source src, byte[] raw, int pos, int len) throws IOException { crc.update(raw, pos, len); } + /** {@inheritDoc} */ @Override protected void onObjectData(Source src, byte[] raw, int pos, int len) throws IOException { crc.update(raw, pos, len); } + /** {@inheritDoc} */ @Override protected void onStoreStream(byte[] raw, int pos, int len) throws IOException { @@ -312,6 +330,7 @@ return v; } + /** {@inheritDoc} */ @Override protected void onPackFooter(byte[] hash) throws IOException { // The base class will validate the original hash matches @@ -321,6 +340,7 @@ packHash = hash; } + /** {@inheritDoc} */ @Override protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, ObjectTypeAndSize info) throws IOException { @@ -329,6 +349,7 @@ return readObjectHeader(info); } + /** {@inheritDoc} */ @Override protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, ObjectTypeAndSize info) throws IOException { @@ -337,6 +358,7 @@ return readObjectHeader(info); } + /** {@inheritDoc} */ @Override protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException { if (cnt == 0) @@ -392,11 +414,13 @@ return (pos / blockSize) * blockSize; } + /** {@inheritDoc} */ @Override protected boolean checkCRC(int oldCRC) { return oldCRC == (int) crc.getValue(); } + /** {@inheritDoc} */ @Override protected boolean onAppendBase(final int typeCode, final byte[] data, final PackedObjectInfo info) throws IOException { @@ -436,6 +460,7 @@ return true; } + /** {@inheritDoc} */ @Override protected void onEndThinPack() throws IOException { // Normally when a thin pack is closed the pack header gets diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,14 +45,19 @@ import org.eclipse.jgit.events.RepositoryEvent; -/** Describes a change to the list of packs in a {@link DfsRepository}. */ +/** + * Describes a change to the list of packs in a + * {@link org.eclipse.jgit.internal.storage.dfs.DfsRepository}. + */ public class DfsPacksChangedEvent extends RepositoryEvent { + /** {@inheritDoc} */ @Override public Class getListenerType() { return DfsPacksChangedListener.class; } + /** {@inheritDoc} */ @Override public void dispatch(DfsPacksChangedListener listener) { listener.onPacksChanged(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedListener.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedListener.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedListener.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ import org.eclipse.jgit.events.RepositoryListener; -/** Receives {@link DfsPacksChangedEvent}s. */ +/** + * Receives {@link org.eclipse.jgit.internal.storage.dfs.DfsPacksChangedEvent}s. + */ public interface DfsPacksChangedListener extends RepositoryListener { /** * Invoked when all packs in a repository are listed. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +/** + * IO statistics for a {@link org.eclipse.jgit.internal.storage.dfs.DfsReader}. + */ +public class DfsReaderIoStats { + /** POJO to accumulate IO statistics. */ + public static class Accumulator { + /** Number of times the reader explicitly called scanPacks. */ + long scanPacks; + + /** Total number of complete pack indexes read into memory. */ + long readIdx; + + /** Total number of complete bitmap indexes read into memory. */ + long readBitmap; + + /** Total number of bytes read from indexes. */ + long readIdxBytes; + + /** Total microseconds spent reading pack or bitmap indexes. */ + long readIdxMicros; + + /** Total number of block cache hits. */ + long blockCacheHit; + + /** + * Total number of discrete blocks actually read from pack file(s), that is, + * block cache misses. + */ + long readBlock; + + /** + * Total number of compressed bytes read during cache misses, as block sized + * units. + */ + long readBlockBytes; + + /** Total microseconds spent reading {@link #readBlock} blocks. */ + long readBlockMicros; + + /** Total number of bytes decompressed. */ + long inflatedBytes; + + Accumulator() { + } + } + + private final Accumulator stats; + + DfsReaderIoStats(Accumulator stats) { + this.stats = stats; + } + + /** + * Get number of times the reader explicitly called scanPacks. + * + * @return number of times the reader explicitly called scanPacks. + */ + public long getScanPacks() { + return stats.scanPacks; + } + + /** + * Get total number of complete pack indexes read into memory. + * + * @return total number of complete pack indexes read into memory. + */ + public long getReadPackIndexCount() { + return stats.readIdx; + } + + /** + * Get total number of complete bitmap indexes read into memory. + * + * @return total number of complete bitmap indexes read into memory. + */ + public long getReadBitmapIndexCount() { + return stats.readBitmap; + } + + /** + * Get total number of bytes read from indexes. + * + * @return total number of bytes read from indexes. + */ + public long getReadIndexBytes() { + return stats.readIdxBytes; + } + + /** + * Get total microseconds spent reading pack or bitmap indexes. + * + * @return total microseconds spent reading pack or bitmap indexes. + */ + public long getReadIndexMicros() { + return stats.readIdxMicros; + } + + /** + * Get total number of block cache hits. + * + * @return total number of block cache hits. + */ + public long getBlockCacheHits() { + return stats.blockCacheHit; + } + + /** + * Get total number of discrete blocks actually read from pack file(s), that + * is, block cache misses. + * + * @return total number of discrete blocks read from pack file(s). + */ + public long getReadBlocksCount() { + return stats.readBlock; + } + + /** + * Get total number of compressed bytes read during cache misses, as block + * sized units. + * + * @return total number of compressed bytes read as block sized units. + */ + public long getReadBlocksBytes() { + return stats.readBlockBytes; + } + + /** + * Get total microseconds spent reading blocks during cache misses. + * + * @return total microseconds spent reading blocks. + */ + public long getReadBlocksMicros() { + return stats.readBlockMicros; + } + + /** + * Get total number of bytes decompressed. + * + * @return total number of bytes decompressed. + */ + public long getInflatedBytes() { + return stats.inflatedBytes; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,8 @@ package org.eclipse.jgit.internal.storage.dfs; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE; +import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; import java.io.IOException; @@ -53,6 +55,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.zip.DataFormatException; @@ -62,6 +65,8 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl; import org.eclipse.jgit.internal.storage.file.PackBitmapIndex; import org.eclipse.jgit.internal.storage.file.PackIndex; @@ -87,28 +92,35 @@ /** * Reader to access repository content through. *

- * See the base {@link ObjectReader} documentation for details. Notably, a - * reader is not thread safe. + * See the base {@link org.eclipse.jgit.lib.ObjectReader} documentation for + * details. Notably, a reader is not thread safe. */ -public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { +public class DfsReader extends ObjectReader implements ObjectReuseAsIs { + private static final int MAX_RESOLVE_MATCHES = 256; + /** Temporary buffer large enough for at least one raw object id. */ final byte[] tempId = new byte[OBJECT_ID_LENGTH]; /** Database this reader loads objects from. */ final DfsObjDatabase db; - private Inflater inf; + final DfsReaderIoStats.Accumulator stats = new DfsReaderIoStats.Accumulator(); + private Inflater inf; private DfsBlock block; - private DeltaBaseCache baseCache; - private DfsPackFile last; - private boolean avoidUnreachable; - DfsReader(DfsObjDatabase db) { + /** + * Initialize a new DfsReader + * + * @param db + * parent DfsObjDatabase. + */ + protected DfsReader(DfsObjDatabase db) { this.db = db; + this.streamFileThreshold = db.getReaderOptions().getStreamFileThreshold(); } DfsReaderOptions getOptions() { @@ -121,20 +133,19 @@ return baseCache; } - int getStreamFileThreshold() { - return getOptions().getStreamFileThreshold(); - } - + /** {@inheritDoc} */ @Override public ObjectReader newReader() { - return new DfsReader(db); + return db.newReader(); } + /** {@inheritDoc} */ @Override public void setAvoidUnreachableObjects(boolean avoid) { avoidUnreachable = avoid; } + /** {@inheritDoc} */ @Override public BitmapIndex getBitmapIndex() throws IOException { for (DfsPackFile pack : db.getPacks()) { @@ -145,6 +156,8 @@ return null; } + /** {@inheritDoc} */ + @Override public Collection getCachedPacksAndUpdate( BitmapBuilder needBitmap) throws IOException { for (DfsPackFile pack : db.getPacks()) { @@ -156,30 +169,56 @@ return Collections.emptyList(); } + /** {@inheritDoc} */ @Override public Collection resolve(AbbreviatedObjectId id) throws IOException { if (id.isComplete()) return Collections.singleton(id.toObjectId()); - boolean noGarbage = avoidUnreachable; - HashSet matches = new HashSet(4); - for (DfsPackFile pack : db.getPacks()) { - if (noGarbage && pack.isGarbage()) + HashSet matches = new HashSet<>(4); + PackList packList = db.getPackList(); + resolveImpl(packList, id, matches); + if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) { + stats.scanPacks++; + resolveImpl(db.scanPacks(packList), id, matches); + } + return matches; + } + + private void resolveImpl(PackList packList, AbbreviatedObjectId id, + HashSet matches) throws IOException { + for (DfsPackFile pack : packList.packs) { + if (skipGarbagePack(pack)) { continue; - pack.resolve(this, matches, id, 256); - if (256 <= matches.size()) + } + pack.resolve(this, matches, id, MAX_RESOLVE_MATCHES); + if (matches.size() >= MAX_RESOLVE_MATCHES) { break; + } } - return matches; } + /** {@inheritDoc} */ @Override public boolean has(AnyObjectId objectId) throws IOException { - if (last != null && last.hasObject(this, objectId)) + if (last != null + && !skipGarbagePack(last) + && last.hasObject(this, objectId)) return true; - boolean noGarbage = avoidUnreachable; - for (DfsPackFile pack : db.getPacks()) { - if (pack == last || (noGarbage && pack.isGarbage())) + PackList packList = db.getPackList(); + if (hasImpl(packList, objectId)) { + return true; + } else if (packList.dirty()) { + stats.scanPacks++; + return hasImpl(db.scanPacks(packList), objectId); + } + return false; + } + + private boolean hasImpl(PackList packList, AnyObjectId objectId) + throws IOException { + for (DfsPackFile pack : packList.packs) { + if (pack == last || skipGarbagePack(pack)) continue; if (pack.hasObject(this, objectId)) { last = pack; @@ -189,24 +228,29 @@ return false; } + /** {@inheritDoc} */ @Override public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { - if (last != null) { - ObjectLoader ldr = last.get(this, objectId); - if (ldr != null) - return ldr; + ObjectLoader ldr; + if (last != null && !skipGarbagePack(last)) { + ldr = last.get(this, objectId); + if (ldr != null) { + return checkType(ldr, objectId, typeHint); + } } - boolean noGarbage = avoidUnreachable; - for (DfsPackFile pack : db.getPacks()) { - if (pack == last || (noGarbage && pack.isGarbage())) - continue; - ObjectLoader ldr = pack.get(this, objectId); + PackList packList = db.getPackList(); + ldr = openImpl(packList, objectId); + if (ldr != null) { + return checkType(ldr, objectId, typeHint); + } + if (packList.dirty()) { + stats.scanPacks++; + ldr = openImpl(db.scanPacks(packList), objectId); if (ldr != null) { - last = pack; - return ldr; + return checkType(ldr, objectId, typeHint); } } @@ -216,12 +260,37 @@ throw new MissingObjectException(objectId.copy(), typeHint); } + private static ObjectLoader checkType(ObjectLoader ldr, AnyObjectId id, + int typeHint) throws IncorrectObjectTypeException { + if (typeHint != OBJ_ANY && ldr.getType() != typeHint) { + throw new IncorrectObjectTypeException(id.copy(), typeHint); + } + return ldr; + } + + private ObjectLoader openImpl(PackList packList, AnyObjectId objectId) + throws IOException { + for (DfsPackFile pack : packList.packs) { + if (pack == last || skipGarbagePack(pack)) { + continue; + } + ObjectLoader ldr = pack.get(this, objectId); + if (ldr != null) { + last = pack; + return ldr; + } + } + return null; + } + + /** {@inheritDoc} */ @Override public Set getShallowCommits() { return Collections.emptySet(); } private static final Comparator> FOUND_OBJECT_SORT = new Comparator>() { + @Override public int compare(FoundObject a, FoundObject b) { int cmp = a.packIndex - b.packIndex; if (cmp == 0) @@ -253,39 +322,60 @@ private Iterable> findAll( Iterable objectIds) throws IOException { - ArrayList> r = new ArrayList>(); - DfsPackFile[] packList = db.getPacks(); - if (packList.length == 0) { - for (T t : objectIds) - r.add(new FoundObject(t)); - return r; + Collection pending = new LinkedList<>(); + for (T id : objectIds) { + pending.add(id); } + PackList packList = db.getPackList(); + List> r = new ArrayList<>(); + findAllImpl(packList, pending, r); + if (!pending.isEmpty() && packList.dirty()) { + stats.scanPacks++; + findAllImpl(db.scanPacks(packList), pending, r); + } + for (T t : pending) { + r.add(new FoundObject<>(t)); + } + Collections.sort(r, FOUND_OBJECT_SORT); + return r; + } + + private void findAllImpl(PackList packList, + Collection pending, List> r) { + DfsPackFile[] packs = packList.packs; + if (packs.length == 0) { + return; + } int lastIdx = 0; - DfsPackFile lastPack = packList[lastIdx]; - boolean noGarbage = avoidUnreachable; + DfsPackFile lastPack = packs[lastIdx]; - OBJECT_SCAN: for (T t : objectIds) { - try { - long p = lastPack.findOffset(this, t); - if (0 < p) { - r.add(new FoundObject(t, lastIdx, lastPack, p)); - continue; + OBJECT_SCAN: for (Iterator it = pending.iterator(); it.hasNext();) { + T t = it.next(); + if (!skipGarbagePack(lastPack)) { + try { + long p = lastPack.findOffset(this, t); + if (0 < p) { + r.add(new FoundObject<>(t, lastIdx, lastPack, p)); + it.remove(); + continue; + } + } catch (IOException e) { + // Fall though and try to examine other packs. } - } catch (IOException e) { - // Fall though and try to examine other packs. } - for (int i = 0; i < packList.length; i++) { + for (int i = 0; i < packs.length; i++) { if (i == lastIdx) continue; - DfsPackFile pack = packList[i]; - if (noGarbage && pack.isGarbage()) + DfsPackFile pack = packs[i]; + if (skipGarbagePack(pack)) continue; try { long p = pack.findOffset(this, t); if (0 < p) { - r.add(new FoundObject(t, i, pack, p)); + r.add(new FoundObject<>(t, i, pack, p)); + it.remove(); lastIdx = i; lastPack = pack; continue OBJECT_SCAN; @@ -294,15 +384,16 @@ // Examine other packs. } } - - r.add(new FoundObject(t)); } - Collections.sort(r, FOUND_OBJECT_SORT); last = lastPack; - return r; } + private boolean skipGarbagePack(DfsPackFile pack) { + return avoidUnreachable && pack.isGarbage(); + } + + /** {@inheritDoc} */ @Override public AsyncObjectLoaderQueue open( Iterable objectIds, final boolean reportMissing) { @@ -320,6 +411,7 @@ return new AsyncObjectLoaderQueue() { private FoundObject cur; + @Override public boolean next() throws MissingObjectException, IOException { if (idItr.hasNext()) { cur = idItr.next(); @@ -331,14 +423,17 @@ } } + @Override public T getCurrent() { return cur.id; } + @Override public ObjectId getObjectId() { return cur.id; } + @Override public ObjectLoader open() throws IOException { if (cur.pack == null) throw new MissingObjectException(cur.id, @@ -346,16 +441,19 @@ return cur.pack.load(DfsReader.this, cur.offset); } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public void release() { // Nothing to clean up. } }; } + /** {@inheritDoc} */ @Override public AsyncObjectSizeQueue getObjectSize( Iterable objectIds, final boolean reportMissing) { @@ -372,9 +470,9 @@ final IOException findAllError = error; return new AsyncObjectSizeQueue() { private FoundObject cur; - private long sz; + @Override public boolean next() throws MissingObjectException, IOException { if (idItr.hasNext()) { cur = idItr.next(); @@ -390,69 +488,112 @@ } } + @Override public T getCurrent() { return cur.id; } + @Override public ObjectId getObjectId() { return cur.id; } + @Override public long getSize() { return sz; } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public void release() { // Nothing to clean up. } }; } + /** {@inheritDoc} */ @Override public long getObjectSize(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { - if (last != null) { + if (last != null && !skipGarbagePack(last)) { long sz = last.getObjectSize(this, objectId); - if (0 <= sz) + if (0 <= sz) { return sz; + } } - for (DfsPackFile pack : db.getPacks()) { - if (pack == last) - continue; - long sz = pack.getObjectSize(this, objectId); + PackList packList = db.getPackList(); + long sz = getObjectSizeImpl(packList, objectId); + if (0 <= sz) { + return sz; + } + if (packList.dirty()) { + sz = getObjectSizeImpl(packList, objectId); if (0 <= sz) { - last = pack; return sz; } } - if (typeHint == OBJ_ANY) + if (typeHint == OBJ_ANY) { throw new MissingObjectException(objectId.copy(), JGitText.get().unknownObjectType2); + } throw new MissingObjectException(objectId.copy(), typeHint); } + private long getObjectSizeImpl(PackList packList, AnyObjectId objectId) + throws IOException { + for (DfsPackFile pack : packList.packs) { + if (pack == last || skipGarbagePack(pack)) { + continue; + } + long sz = pack.getObjectSize(this, objectId); + if (0 <= sz) { + last = pack; + return sz; + } + } + return -1; + } + + /** {@inheritDoc} */ + @Override public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) { return new DfsObjectToPack(objectId, type); } private static final Comparator OFFSET_SORT = new Comparator() { + @Override public int compare(DfsObjectToPack a, DfsObjectToPack b) { return Long.signum(a.getOffset() - b.getOffset()); } }; + @Override public void selectObjectRepresentation(PackWriter packer, ProgressMonitor monitor, Iterable objects) throws IOException, MissingObjectException { - for (DfsPackFile pack : db.getPacks()) { - List tmp = findAllFromPack(pack, objects); + // Don't check dirty bit on PackList; assume ObjectToPacks all came + // from the current list. + List packs = sortPacksForSelectRepresentation(); + trySelectRepresentation(packer, monitor, objects, packs, false); + + List garbage = garbagePacksForSelectRepresentation(); + if (!garbage.isEmpty() && checkGarbagePacks(objects)) { + trySelectRepresentation(packer, monitor, objects, garbage, true); + } + } + + private void trySelectRepresentation(PackWriter packer, + ProgressMonitor monitor, Iterable objects, + List packs, boolean skipFound) throws IOException { + for (DfsPackFile pack : packs) { + List tmp = findAllFromPack(pack, objects, skipFound); if (tmp.isEmpty()) continue; Collections.sort(tmp, OFFSET_SORT); @@ -470,20 +611,82 @@ } } + private static final Comparator PACK_SORT_FOR_REUSE = new Comparator() { + @Override + public int compare(DfsPackFile af, DfsPackFile bf) { + DfsPackDescription ad = af.getPackDescription(); + DfsPackDescription bd = bf.getPackDescription(); + PackSource as = ad.getPackSource(); + PackSource bs = bd.getPackSource(); + + if (as != null && as == bs && DfsPackDescription.isGC(as)) { + // Push smaller GC files last; these likely have higher quality + // delta compression and the contained representation should be + // favored over other files. + return Long.signum(bd.getFileSize(PACK) - ad.getFileSize(PACK)); + } + + // DfsPackDescription.compareTo already did a reasonable sort. + // Rely on Arrays.sort being stable, leaving equal elements. + return 0; + } + }; + + private List sortPacksForSelectRepresentation() + throws IOException { + DfsPackFile[] packs = db.getPacks(); + List sorted = new ArrayList<>(packs.length); + for (DfsPackFile p : packs) { + if (p.getPackDescription().getPackSource() != UNREACHABLE_GARBAGE) { + sorted.add(p); + } + } + Collections.sort(sorted, PACK_SORT_FOR_REUSE); + return sorted; + } + + private List garbagePacksForSelectRepresentation() + throws IOException { + DfsPackFile[] packs = db.getPacks(); + List garbage = new ArrayList<>(packs.length); + for (DfsPackFile p : packs) { + if (p.getPackDescription().getPackSource() == UNREACHABLE_GARBAGE) { + garbage.add(p); + } + } + return garbage; + } + + private static boolean checkGarbagePacks(Iterable objects) { + for (ObjectToPack otp : objects) { + if (!((DfsObjectToPack) otp).isFound()) { + return true; + } + } + return false; + } + private List findAllFromPack(DfsPackFile pack, - Iterable objects) throws IOException { - List tmp = new BlockList(); + Iterable objects, boolean skipFound) + throws IOException { + List tmp = new BlockList<>(); PackIndex idx = pack.getPackIndex(this); - for (ObjectToPack otp : objects) { + for (ObjectToPack obj : objects) { + DfsObjectToPack otp = (DfsObjectToPack) obj; + if (skipFound && otp.isFound()) { + continue; + } long p = idx.findOffset(otp); - if (0 < p) { + if (0 < p && !pack.isCorrupt(p)) { otp.setOffset(p); - tmp.add((DfsObjectToPack) otp); + tmp.add(otp); } } return tmp; } + /** {@inheritDoc} */ + @Override public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp, boolean validate) throws IOException, StoredObjectRepresentationNotAvailableException { @@ -491,12 +694,16 @@ src.pack.copyAsIs(out, src, validate, this); } + /** {@inheritDoc} */ + @Override public void writeObjects(PackOutputStream out, List list) throws IOException { for (ObjectToPack otp : list) out.writeObject(otp); } + /** {@inheritDoc} */ + @Override public void copyPackAsIs(PackOutputStream out, CachedPack pack) throws IOException { ((DfsCachedPack) pack).copyAsIs(out, this); @@ -505,7 +712,7 @@ /** * Copy bytes from the window to a caller supplied buffer. * - * @param pack + * @param file * the file the desired window is stored within. * @param position * position within the file to read from. @@ -524,24 +731,24 @@ * this cursor does not match the provider or id and the proper * window could not be acquired through the provider's cache. */ - int copy(DfsPackFile pack, long position, byte[] dstbuf, int dstoff, int cnt) - throws IOException { + int copy(BlockBasedFile file, long position, byte[] dstbuf, int dstoff, + int cnt) throws IOException { if (cnt == 0) return 0; - long length = pack.length; + long length = file.length; if (0 <= length && length <= position) return 0; int need = cnt; do { - pin(pack, position); + pin(file, position); int r = block.copy(position, dstbuf, dstoff, need); position += r; dstoff += r; need -= r; if (length < 0) - length = pack.length; + length = file.length; } while (0 < need && position < length); return cnt - need; } @@ -575,9 +782,10 @@ for (int dstoff = 0;;) { int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); dstoff += n; - if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) + if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) { + stats.inflatedBytes += dstoff; return dstoff; - if (inf.needsInput()) { + } else if (inf.needsInput()) { pin(pack, position); position += block.setInput(position, inf); } else if (n == 0) @@ -605,15 +813,14 @@ inf.reset(); } - void pin(DfsPackFile pack, long position) throws IOException { - DfsBlock b = block; - if (b == null || !b.contains(pack.key, position)) { + void pin(BlockBasedFile file, long position) throws IOException { + if (block == null || !block.contains(file.key, position)) { // If memory is low, we may need what is in our window field to // be cleaned up by the GC during the get for the next window. // So we always clear it, even though we are just going to set // it again. block = null; - block = pack.getOrLoadBlock(position, this); + block = file.getOrLoadBlock(position, this); } } @@ -621,7 +828,20 @@ block = null; } - /** Release the current window cursor. */ + /** + * Get IO statistics accumulated by this reader. + * + * @return IO statistics accumulated by this reader. + */ + public DfsReaderIoStats getIoStats() { + return new DfsReaderIoStats(stats); + } + + /** + * {@inheritDoc} + *

+ * Release the current window cursor. + */ @Override public void close() { last = null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.storage.pack.PackConfig; -/** Options controlling how objects are read from a DFS stored repository. */ +/** + * Options controlling how objects are read from a DFS stored repository. + */ public class DfsReaderOptions { /** 1024 (number of bytes in one kibibyte/kilobyte) */ public static final int KiB = 1024; @@ -65,13 +67,19 @@ private int streamPackBufferSize; - /** Create a default reader configuration. */ + /** + * Create a default reader configuration. + */ public DfsReaderOptions() { setDeltaBaseCacheLimit(10 * MiB); setStreamFileThreshold(PackConfig.DEFAULT_BIG_FILE_THRESHOLD); } - /** @return maximum number of bytes to hold in per-reader DeltaBaseCache. */ + /** + * Get maximum number of bytes to hold in per-reader DeltaBaseCache. + * + * @return maximum number of bytes to hold in per-reader DeltaBaseCache. + */ public int getDeltaBaseCacheLimit() { return deltaBaseCacheLimit; } @@ -88,12 +96,18 @@ return this; } - /** @return the size threshold beyond which objects must be streamed. */ + /** + * Get the size threshold beyond which objects must be streamed. + * + * @return the size threshold beyond which objects must be streamed. + */ public int getStreamFileThreshold() { return streamFileThreshold; } /** + * Set new byte limit for objects that must be streamed. + * * @param newLimit * new byte limit for objects that must be streamed. Objects * smaller than this size can be obtained as a contiguous byte @@ -107,20 +121,24 @@ } /** + * Get number of bytes to use for buffering when streaming a pack file + * during copying. + * * @return number of bytes to use for buffering when streaming a pack file * during copying. If 0 the block size of the pack is used. - * @since 4.0 */ public int getStreamPackBufferSize() { return streamPackBufferSize; } /** + * Set new buffer size in bytes for buffers used when streaming pack files + * during copying. + * * @param bufsz * new buffer size in bytes for buffers used when streaming pack * files during copying. * @return {@code this} - * @since 4.0 */ public DfsReaderOptions setStreamPackBufferSize(int bufsz) { streamPackBufferSize = Math.max(0, bufsz); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,7 +64,10 @@ import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; -/** */ +/** + * Abstract DfsRefDatabase class. + * + */ public abstract class DfsRefDatabase extends RefDatabase { private final DfsRepository repository; @@ -78,10 +81,14 @@ */ protected DfsRefDatabase(DfsRepository repository) { this.repository = repository; - this.cache = new AtomicReference(); + this.cache = new AtomicReference<>(); } - /** @return the repository the database holds the references of. */ + /** + * Get the repository the database holds the references of. + * + * @return the repository the database holds the references of. + */ protected DfsRepository getRepository() { return repository; } @@ -90,6 +97,7 @@ return 0 < read().size(); } + /** {@inheritDoc} */ @Override public Ref exactRef(String name) throws IOException { RefCache curr = read(); @@ -97,6 +105,7 @@ return ref != null ? resolve(ref, 0, curr.ids) : null; } + /** {@inheritDoc} */ @Override public Ref getRef(String needle) throws IOException { RefCache curr = read(); @@ -110,17 +119,19 @@ return null; } + /** {@inheritDoc} */ @Override public List getAdditionalRefs() { return Collections.emptyList(); } + /** {@inheritDoc} */ @Override public Map getRefs(String prefix) throws IOException { RefCache curr = read(); RefList packed = RefList.emptyList(); RefList loose = curr.ids; - RefList.Builder sym = new RefList.Builder(curr.sym.size()); + RefList.Builder sym = new RefList.Builder<>(curr.sym.size()); for (int idx = 0; idx < curr.sym.size(); idx++) { Ref ref = curr.sym.get(idx); @@ -161,6 +172,7 @@ return new SymbolicRef(ref.getName(), dst); } + /** {@inheritDoc} */ @Override public Ref peel(Ref ref) throws IOException { final Ref oldLeaf = ref.getLeaf(); @@ -180,7 +192,7 @@ return recreate(ref, newLeaf); } - private Ref doPeel(final Ref leaf) throws MissingObjectException, + Ref doPeel(Ref leaf) throws MissingObjectException, IOException { try (RevWalk rw = new RevWalk(repository)) { RevObject obj = rw.parseAny(leaf.getObjectId()); @@ -199,7 +211,7 @@ } } - private static Ref recreate(Ref old, Ref leaf) { + static Ref recreate(Ref old, Ref leaf) { if (old.isSymbolic()) { Ref dst = recreate(old.getTarget(), leaf); return new SymbolicRef(old.getName(), dst); @@ -207,6 +219,7 @@ return leaf; } + /** {@inheritDoc} */ @Override public RefUpdate newUpdate(String refName, boolean detach) throws IOException { @@ -217,16 +230,13 @@ else detachingSymbolicRef = detach && ref.isSymbolic(); - if (detachingSymbolicRef) { - ref = new ObjectIdRef.Unpeeled(NEW, refName, ref.getObjectId()); - } - DfsRefUpdate update = new DfsRefUpdate(this, ref); if (detachingSymbolicRef) update.setDetachingSymbolicRef(); return update; } + /** {@inheritDoc} */ @Override public RefRename newRename(String fromName, String toName) throws IOException { @@ -235,6 +245,7 @@ return new DfsRefRename(src, dst); } + /** {@inheritDoc} */ @Override public boolean isNameConflicting(String refName) throws IOException { RefList all = read().ids; @@ -256,11 +267,19 @@ return false; } + /** {@inheritDoc} */ @Override public void create() { // Nothing to do. } + /** {@inheritDoc} */ + @Override + public void refresh() { + clearCache(); + } + + /** {@inheritDoc} */ @Override public void close() { clearCache(); @@ -303,13 +322,22 @@ * Read all known references in the repository. * * @return all current references of the repository. - * @throws IOException + * @throws java.io.IOException * references cannot be accessed. */ protected abstract RefCache scanAllRefs() throws IOException; /** * Compare a reference, and put if it matches. + *

+ * Two reference match if and only if they satisfy the following: + * + *

    + *
  • If one reference is a symbolic ref, the other one should be a symbolic + * ref. + *
  • If both are symbolic refs, the target names should be same. + *
  • If both are object ID refs, the object IDs should be same. + *
* * @param oldRef * old value to compare to. If the reference is expected to not @@ -319,7 +347,7 @@ * @param newRef * new reference to store. * @return true if the put was successful; false otherwise. - * @throws IOException + * @throws java.io.IOException * the reference cannot be put due to a system error. */ protected abstract boolean compareAndPut(Ref oldRef, Ref newRef) @@ -331,7 +359,7 @@ * @param oldRef * the old reference information that was previously read. * @return true if the remove was successful; false otherwise. - * @throws IOException + * @throws java.io.IOException * the reference could not be removed due to a system error. */ protected abstract boolean compareAndRemove(Ref oldRef) throws IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,6 +55,7 @@ super(src, dst); } + /** {@inheritDoc} */ @Override protected Result doRename() throws IOException { // TODO Correctly handle renaming foo/bar to foo. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.reftable.MergedReftable; +import org.eclipse.jgit.internal.storage.reftable.RefCursor; +import org.eclipse.jgit.internal.storage.reftable.Reftable; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.util.RefMap; + +/** + * A {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase} that uses + * reftable for storage. + *

+ * A {@code DfsRefDatabase} instance is thread-safe. + *

+ * Implementors may wish to use + * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription#getMaxUpdateIndex()} + * as the primary key identifier for a + * {@link org.eclipse.jgit.internal.storage.pack.PackExt#REFTABLE} only pack + * description, ensuring that when there are competing transactions one wins, + * and one will fail. + */ +public class DfsReftableDatabase extends DfsRefDatabase { + private final ReentrantLock lock = new ReentrantLock(true); + + private DfsReader ctx; + + private ReftableStack tableStack; + + private MergedReftable mergedTables; + + /** + * Initialize the reference database for a repository. + * + * @param repo + * the repository this database instance manages references for. + */ + protected DfsReftableDatabase(DfsRepository repo) { + super(repo); + } + + /** {@inheritDoc} */ + @Override + public boolean performsAtomicTransactions() { + return true; + } + + /** {@inheritDoc} */ + @Override + public BatchRefUpdate newBatchUpdate() { + DfsObjDatabase odb = getRepository().getObjectDatabase(); + return new ReftableBatchRefUpdate(this, odb); + } + + /** + * Get configuration to write new reftables with. + * + * @return configuration to write new reftables with. + */ + public ReftableConfig getReftableConfig() { + return new ReftableConfig(getRepository().getConfig()); + } + + /** + * Get the lock protecting this instance's state. + * + * @return the lock protecting this instance's state. + */ + protected ReentrantLock getLock() { + return lock; + } + + /** + * Whether to compact reftable instead of extending the stack depth. + * + * @return {@code true} if commit of a new small reftable should try to + * replace a prior small reftable by performing a compaction, + * instead of extending the stack depth. + */ + protected boolean compactDuringCommit() { + return true; + } + + /** + * Obtain a handle to the merged reader. + * + * @return (possibly cached) handle to the merged reader. + * @throws java.io.IOException + * if tables cannot be opened. + */ + protected Reftable reader() throws IOException { + lock.lock(); + try { + if (mergedTables == null) { + mergedTables = new MergedReftable(stack().readers()); + } + return mergedTables; + } finally { + lock.unlock(); + } + } + + /** + * Obtain a handle to the stack of reftables. + * + * @return (possibly cached) handle to the stack. + * @throws java.io.IOException + * if tables cannot be opened. + */ + protected ReftableStack stack() throws IOException { + lock.lock(); + try { + if (tableStack == null) { + DfsObjDatabase odb = getRepository().getObjectDatabase(); + if (ctx == null) { + ctx = odb.newReader(); + } + tableStack = ReftableStack.open(ctx, + Arrays.asList(odb.getReftables())); + } + return tableStack; + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isNameConflicting(String refName) throws IOException { + lock.lock(); + try { + Reftable table = reader(); + + // Cannot be nested within an existing reference. + int lastSlash = refName.lastIndexOf('/'); + while (0 < lastSlash) { + if (table.hasRef(refName.substring(0, lastSlash))) { + return true; + } + lastSlash = refName.lastIndexOf('/', lastSlash - 1); + } + + // Cannot be the container of an existing reference. + return table.hasRef(refName + '/'); + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public Ref exactRef(String name) throws IOException { + lock.lock(); + try { + Reftable table = reader(); + Ref ref = table.exactRef(name); + if (ref != null && ref.isSymbolic()) { + return table.resolve(ref); + } + return ref; + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public Ref getRef(String needle) throws IOException { + for (String prefix : SEARCH_PATH) { + Ref ref = exactRef(prefix + needle); + if (ref != null) { + return ref; + } + } + return null; + } + + /** {@inheritDoc} */ + @Override + public Map getRefs(String prefix) throws IOException { + RefList.Builder all = new RefList.Builder<>(); + lock.lock(); + try { + Reftable table = reader(); + try (RefCursor rc = ALL.equals(prefix) ? table.allRefs() + : table.seekRef(prefix)) { + while (rc.next()) { + Ref ref = table.resolve(rc.getRef()); + if (ref != null && ref.getObjectId() != null) { + all.add(ref); + } + } + } + } finally { + lock.unlock(); + } + + RefList none = RefList.emptyList(); + return new RefMap(prefix, all.toRefList(), none, none); + } + + /** {@inheritDoc} */ + @Override + public Ref peel(Ref ref) throws IOException { + Ref oldLeaf = ref.getLeaf(); + if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) { + return ref; + } + return recreate(ref, doPeel(oldLeaf)); + } + + @Override + boolean exists() throws IOException { + DfsObjDatabase odb = getRepository().getObjectDatabase(); + return odb.getReftables().length > 0; + } + + @Override + void clearCache() { + lock.lock(); + try { + if (tableStack != null) { + tableStack.close(); + tableStack = null; + } + if (ctx != null) { + ctx.close(); + ctx = null; + } + mergedTables = null; + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + protected boolean compareAndPut(Ref oldRef, @Nullable Ref newRef) + throws IOException { + ReceiveCommand cmd = toCommand(oldRef, newRef); + try (RevWalk rw = new RevWalk(getRepository())) { + newBatchUpdate().setAllowNonFastForwards(true).addCommand(cmd) + .execute(rw, NullProgressMonitor.INSTANCE); + } + switch (cmd.getResult()) { + case OK: + return true; + case REJECTED_OTHER_REASON: + throw new IOException(cmd.getMessage()); + case LOCK_FAILURE: + default: + return false; + } + } + + private static ReceiveCommand toCommand(Ref oldRef, Ref newRef) { + ObjectId oldId = toId(oldRef); + ObjectId newId = toId(newRef); + String name = toName(oldRef, newRef); + + if (oldRef != null && oldRef.isSymbolic()) { + if (newRef != null) { + if (newRef.isSymbolic()) { + return ReceiveCommand.link(oldRef.getTarget().getName(), + newRef.getTarget().getName(), name); + } else { + return ReceiveCommand.unlink(oldRef.getTarget().getName(), + newId, name); + } + } else { + return ReceiveCommand.unlink(oldRef.getTarget().getName(), + ObjectId.zeroId(), name); + } + } + + if (newRef != null && newRef.isSymbolic()) { + if (oldRef != null) { + if (oldRef.isSymbolic()) { + return ReceiveCommand.link(oldRef.getTarget().getName(), + newRef.getTarget().getName(), name); + } else { + return ReceiveCommand.link(oldId, + newRef.getTarget().getName(), name); + } + } else { + return ReceiveCommand.link(ObjectId.zeroId(), + newRef.getTarget().getName(), name); + } + } + + return new ReceiveCommand(oldId, newId, name); + } + + private static ObjectId toId(Ref ref) { + if (ref != null) { + ObjectId id = ref.getObjectId(); + if (id != null) { + return id; + } + } + return ObjectId.zeroId(); + } + + private static String toName(Ref oldRef, Ref newRef) { + return oldRef != null ? oldRef.getName() : newRef.getName(); + } + + /** {@inheritDoc} */ + @Override + protected boolean compareAndRemove(Ref oldRef) throws IOException { + return compareAndPut(oldRef, null); + } + + /** {@inheritDoc} */ + @Override + protected RefCache scanAllRefs() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + void stored(Ref ref) { + // Unnecessary; ReftableBatchRefUpdate calls clearCache(). + } + + @Override + void removed(String refName) { + // Unnecessary; ReftableBatchRefUpdate calls clearCache(). + } + + /** {@inheritDoc} */ + @Override + protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) { + // Do not cache peeled state in reftable. + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.internal.storage.reftable.ReftableReader; + +/** + * A reftable stored in {@link org.eclipse.jgit.internal.storage.dfs.DfsBlockCache}. + */ +public class DfsReftable extends BlockBasedFile { + /** + * Construct a reader for an existing reftable. + * + * @param desc + * description of the reftable within the DFS. + */ + public DfsReftable(DfsPackDescription desc) { + this(DfsBlockCache.getInstance(), desc); + } + + /** + * Construct a reader for an existing reftable. + * + * @param cache + * cache that will store the reftable data. + * @param desc + * description of the reftable within the DFS. + */ + public DfsReftable(DfsBlockCache cache, DfsPackDescription desc) { + super(cache, desc, REFTABLE); + + int bs = desc.getBlockSize(REFTABLE); + if (bs > 0) { + setBlockSize(bs); + } + + long sz = desc.getFileSize(REFTABLE); + length = sz > 0 ? sz : -1; + } + + /** + * Get description that was originally used to configure this file. + * + * @return description that was originally used to configure this file. + */ + public DfsPackDescription getPackDescription() { + return desc; + } + + /** + * Open reader on the reftable. + *

+ * The returned reader is not thread safe. + * + * @param ctx + * reader to access the DFS storage. + * @return cursor to read the table; caller must close. + * @throws java.io.IOException + * table cannot be opened. + */ + public ReftableReader open(DfsReader ctx) throws IOException { + return new ReftableReader(new CacheSource(this, cache, ctx)); + } + + private static final class CacheSource extends BlockSource { + private final DfsReftable file; + private final DfsBlockCache cache; + private final DfsReader ctx; + private ReadableChannel ch; + private int readAhead; + + CacheSource(DfsReftable file, DfsBlockCache cache, DfsReader ctx) { + this.file = file; + this.cache = cache; + this.ctx = ctx; + } + + @Override + public ByteBuffer read(long pos, int cnt) throws IOException { + if (ch == null && readAhead > 0 && notInCache(pos)) { + open().setReadAheadBytes(readAhead); + } + + DfsBlock block = cache.getOrLoad(file, pos, ctx, ch); + if (block.start == pos && block.size() >= cnt) { + return block.zeroCopyByteBuffer(cnt); + } + + byte[] dst = new byte[cnt]; + ByteBuffer buf = ByteBuffer.wrap(dst); + buf.position(ctx.copy(file, pos, dst, 0, cnt)); + return buf; + } + + private boolean notInCache(long pos) { + return cache.get(file.key, file.alignToBlock(pos)) == null; + } + + @Override + public long size() throws IOException { + long n = file.length; + if (n < 0) { + n = open().size(); + file.length = n; + } + return n; + } + + @Override + public void adviseSequentialRead(long start, long end) { + int sz = ctx.getOptions().getStreamPackBufferSize(); + if (sz > 0) { + readAhead = (int) Math.min(sz, end - start); + } + } + + private ReadableChannel open() throws IOException { + if (ch == null) { + ch = ctx.db.openFile(file.desc, file.ext); + } + return ch; + } + + @Override + public void close() { + if (ch != null) { + try { + ch.close(); + } catch (IOException e) { + // Ignore read close failures. + } finally { + ch = null; + } + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,9 +47,9 @@ import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.SymbolicRef; -import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; @@ -66,16 +66,19 @@ this.refdb = refdb; } + /** {@inheritDoc} */ @Override protected DfsRefDatabase getRefDatabase() { return refdb; } + /** {@inheritDoc} */ @Override protected DfsRepository getRepository() { return refdb.getRepository(); } + /** {@inheritDoc} */ @Override protected boolean tryLock(boolean deref) throws IOException { dstRef = getRef(); @@ -90,11 +93,13 @@ return true; } + /** {@inheritDoc} */ @Override protected void unlock() { // No state is held while "locked". } + /** {@inheritDoc} */ @Override public Result update(RevWalk walk) throws IOException { try { @@ -105,6 +110,7 @@ } } + /** {@inheritDoc} */ @Override protected Result doUpdate(Result desiredResult) throws IOException { ObjectIdRef newRef; @@ -129,6 +135,7 @@ return Result.LOCK_FAILURE; } + /** {@inheritDoc} */ @Override protected Result doDelete(Result desiredResult) throws IOException { if (getRefDatabase().compareAndRemove(dstRef)) { @@ -138,6 +145,7 @@ return Result.LOCK_FAILURE; } + /** {@inheritDoc} */ @Override protected Result doLink(String target) throws IOException { final SymbolicRef newRef = new SymbolicRef( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ import org.eclipse.jgit.lib.BaseRepositoryBuilder; /** - * Constructs a {@link DfsRepository}. + * Constructs a {@link org.eclipse.jgit.internal.storage.dfs.DfsRepository}. * * @param * type of the builder class. @@ -63,7 +63,11 @@ private DfsRepositoryDescription repoDesc; - /** @return options used by readers accessing the repository. */ + /** + * Get options used by readers accessing the repository. + * + * @return options used by readers accessing the repository. + */ public DfsReaderOptions getReaderOptions() { return readerOptions; } @@ -80,7 +84,11 @@ return self(); } - /** @return a description of the repository. */ + /** + * Get the description of the repository. + * + * @return the description of the repository. + */ public DfsRepositoryDescription getRepositoryDescription() { return repoDesc; } @@ -97,6 +105,7 @@ return self(); } + /** {@inheritDoc} */ @Override public B setup() throws IllegalArgumentException, IOException { super.setup(); @@ -108,24 +117,20 @@ } /** + * {@inheritDoc} + *

* Create a repository matching the configuration in this builder. *

* If an option was not set, the build method will try to default the option * based on other options. If insufficient information is available, an * exception is thrown to the caller. - * - * @return a repository matching this configuration. - * @throws IllegalArgumentException - * insufficient parameters were set. - * @throws IOException - * the repository could not be accessed to configure the rest of - * the builder's parameters. */ @Override public abstract R build() throws IOException; // We don't support local file IO and thus shouldn't permit these to set. + /** {@inheritDoc} */ @Override public B setGitDir(File gitDir) { if (gitDir != null) @@ -133,6 +138,7 @@ return self(); } + /** {@inheritDoc} */ @Override public B setObjectDirectory(File objectDirectory) { if (objectDirectory != null) @@ -140,12 +146,14 @@ return self(); } + /** {@inheritDoc} */ @Override public B addAlternateObjectDirectory(File other) { throw new UnsupportedOperationException( JGitText.get().unsupportedAlternates); } + /** {@inheritDoc} */ @Override public B setWorkTree(File workTree) { if (workTree != null) @@ -153,6 +161,7 @@ return self(); } + /** {@inheritDoc} */ @Override public B setIndexFile(File indexFile) { if (indexFile != null) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,15 @@ package org.eclipse.jgit.internal.storage.dfs; -/** A description of a Git repository on a DFS. */ +/** + * A description of a Git repository on a DFS. + */ public class DfsRepositoryDescription { private final String repositoryName; - /** Initialize a new, empty repository description. */ + /** + * Initialize a new, empty repository description. + */ public DfsRepositoryDescription() { this(null); } @@ -62,11 +66,16 @@ this.repositoryName = repositoryName; } - /** @return the name of the repository. */ + /** + * Get the name of the repository. + * + * @return the name of the repository. + */ public String getRepositoryName() { return repositoryName; } + /** {@inheritDoc} */ @Override public int hashCode() { if (getRepositoryName() != null) @@ -74,6 +83,7 @@ return System.identityHashCode(this); } + /** {@inheritDoc} */ @Override public boolean equals(Object b) { if (b instanceof DfsRepositoryDescription){ @@ -84,6 +94,7 @@ return false; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,13 @@ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; +import java.io.InputStream; import java.text.MessageFormat; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesNodeProvider; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; @@ -53,7 +58,9 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; -/** A Git repository on a DFS. */ +/** + * A Git repository on a DFS. + */ public abstract class DfsRepository extends Repository { private final DfsConfig config; @@ -71,13 +78,15 @@ this.description = builder.getRepositoryDescription(); } + /** {@inheritDoc} */ @Override public abstract DfsObjDatabase getObjectDatabase(); - @Override - public abstract DfsRefDatabase getRefDatabase(); - - /** @return a description of this repository. */ + /** + * Get the description of this repository. + * + * @return the description of this repository. + */ public DfsRepositoryDescription getDescription() { return description; } @@ -86,13 +95,17 @@ * Check if the repository already exists. * * @return true if the repository exists; false if it is new. - * @throws IOException + * @throws java.io.IOException * the repository cannot be checked. */ public boolean exists() throws IOException { - return getRefDatabase().exists(); + if (getRefDatabase() instanceof DfsRefDatabase) { + return ((DfsRefDatabase) getRefDatabase()).exists(); + } + return true; } + /** {@inheritDoc} */ @Override public void create(boolean bare) throws IOException { if (exists()) @@ -105,25 +118,64 @@ throw new IOException(result.name()); } + /** {@inheritDoc} */ @Override public StoredConfig getConfig() { return config; } + /** {@inheritDoc} */ @Override public void scanForRepoChanges() throws IOException { - getRefDatabase().clearCache(); + getRefDatabase().refresh(); getObjectDatabase().clearCache(); } + /** {@inheritDoc} */ @Override public void notifyIndexChanged() { // Do not send notifications. // There is no index, as there is no working tree. } + /** {@inheritDoc} */ @Override public ReflogReader getReflogReader(String refName) throws IOException { throw new UnsupportedOperationException(); } + + /** {@inheritDoc} */ + @Override + public AttributesNodeProvider createAttributesNodeProvider() { + // TODO Check if the implementation used in FileRepository can be used + // for this kind of repository + return new EmptyAttributesNodeProvider(); + } + + private static class EmptyAttributesNodeProvider implements + AttributesNodeProvider { + private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode(); + + @Override + public AttributesNode getInfoAttributesNode() throws IOException { + return emptyAttributesNode; + } + + @Override + public AttributesNode getGlobalAttributesNode() throws IOException { + return emptyAttributesNode; + } + + private static class EmptyAttributesNode extends AttributesNode { + + public EmptyAttributesNode() { + super(Collections. emptyList()); + } + + @Override + public void parse(InputStream in) throws IOException { + // Do nothing + } + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011, 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.internal.storage.dfs; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Arrays; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.pack.PackExt; + +/** + * Key used by {@link org.eclipse.jgit.internal.storage.dfs.DfsBlockCache} to disambiguate streams. + */ +public abstract class DfsStreamKey { + /** + * Create a {@code DfsStreamKey} + * + * @param repo + * description of the containing repository. + * @param name + * compute the key from a string name. + * @param ext + * pack file extension, or {@code null}. + * @return key for {@code name} + */ + public static DfsStreamKey of(DfsRepositoryDescription repo, String name, + @Nullable PackExt ext) { + return new ByteArrayDfsStreamKey(repo, name.getBytes(UTF_8), ext); + } + + final int hash; + + final int packExtPos; + + /** + * Constructor for DfsStreamKey. + * + * @param hash + * hash of the other identifying components of the key. + * @param ext + * pack file extension, or {@code null}. + */ + protected DfsStreamKey(int hash, @Nullable PackExt ext) { + // Multiply by 31 here so we can more directly combine with another + // value without doing the multiply there. + this.hash = hash * 31; + this.packExtPos = ext == null ? 0 : ext.getPosition(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hash; + } + + /** {@inheritDoc} */ + @Override + public abstract boolean equals(Object o); + + /** {@inheritDoc} */ + @SuppressWarnings("boxing") + @Override + public String toString() { + return String.format("DfsStreamKey[hash=%08x]", hash); //$NON-NLS-1$ + } + + private static final class ByteArrayDfsStreamKey extends DfsStreamKey { + private final DfsRepositoryDescription repo; + + private final byte[] name; + + ByteArrayDfsStreamKey(DfsRepositoryDescription repo, byte[] name, + @Nullable PackExt ext) { + super(repo.hashCode() * 31 + Arrays.hashCode(name), ext); + this.repo = repo; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ByteArrayDfsStreamKey) { + ByteArrayDfsStreamKey k = (ByteArrayDfsStreamKey) o; + return hash == k.hash && repo.equals(k.repo) + && Arrays.equals(name, k.name); + } + return false; + } + } + + static final class ForReverseIndex extends DfsStreamKey { + private final DfsStreamKey idxKey; + + ForReverseIndex(DfsStreamKey idxKey) { + super(idxKey.hash + 1, null); + this.idxKey = idxKey; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForReverseIndex + && idxKey.equals(((ForReverseIndex) o).idxKey); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,18 +46,22 @@ import org.eclipse.jgit.nls.NLS; import org.eclipse.jgit.nls.TranslationBundle; -/** Translation bundle for the DFS storage implementation. */ +/** + * Translation bundle for the DFS storage implementation. + */ public class DfsText extends TranslationBundle { - /** @return instance of this translation bundle */ + /** + * Get an instance of this translation bundle. + * + * @return instance of this translation bundle. + */ public static DfsText get() { return NLS.getBundleFor(DfsText.class); } // @formatter:off /***/ public String cannotReadIndex; - /***/ public String cannotReadBackDelta; /***/ public String shortReadOfBlock; /***/ public String shortReadOfIndex; - /***/ public String unexpectedEofInPack; /***/ public String willNotStoreEmptyPack; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -6,22 +6,13 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.storage.pack.PackExt; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectIdRef; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Ref.Storage; -import org.eclipse.jgit.lib.SymbolicRef; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.lib.RefDatabase; /** * Git repository stored entirely in the local process memory. @@ -43,46 +34,82 @@ } } - private static final AtomicInteger packId = new AtomicInteger(); + static final AtomicInteger packId = new AtomicInteger(); - private final DfsObjDatabase objdb; - - private final DfsRefDatabase refdb; + private final MemObjDatabase objdb; + private final MemRefDatabase refdb; + private String gitwebDescription; /** * Initialize a new in-memory repository. * * @param repoDesc * description of the repository. - * @since 2.0 */ public InMemoryRepository(DfsRepositoryDescription repoDesc) { this(new Builder().setRepositoryDescription(repoDesc)); } - private InMemoryRepository(Builder builder) { + InMemoryRepository(Builder builder) { super(builder); objdb = new MemObjDatabase(this); refdb = new MemRefDatabase(); } + /** {@inheritDoc} */ @Override - public DfsObjDatabase getObjectDatabase() { + public MemObjDatabase getObjectDatabase() { return objdb; } + /** {@inheritDoc} */ @Override - public DfsRefDatabase getRefDatabase() { + public RefDatabase getRefDatabase() { return refdb; } - private class MemObjDatabase extends DfsObjDatabase { - private List packs = new ArrayList(); + /** + * Enable (or disable) the atomic reference transaction support. + *

+ * Useful for testing atomic support enabled or disabled. + * + * @param atomic + * whether to use atomic reference transaction support + */ + public void setPerformsAtomicTransactions(boolean atomic) { + refdb.performsAtomicTransactions = atomic; + } + + /** {@inheritDoc} */ + @Override + @Nullable + public String getGitwebDescription() { + return gitwebDescription; + } + + /** {@inheritDoc} */ + @Override + public void setGitwebDescription(@Nullable String d) { + gitwebDescription = d; + } + + /** DfsObjDatabase used by InMemoryRepository. */ + public static class MemObjDatabase extends DfsObjDatabase { + private List packs = new ArrayList<>(); + private int blockSize; MemObjDatabase(DfsRepository repo) { super(repo, new DfsReaderOptions()); } + /** + * @param blockSize + * force a different block size for testing. + */ + public void setReadableChannelBlockSizeForTest(int blockSize) { + this.blockSize = blockSize; + } + @Override protected synchronized List listPacks() { return packs; @@ -102,12 +129,13 @@ Collection desc, Collection replace) { List n; - n = new ArrayList(desc.size() + packs.size()); + n = new ArrayList<>(desc.size() + packs.size()); n.addAll(desc); n.addAll(packs); if (replace != null) n.removeAll(replace); packs = n; + clearCache(); } @Override @@ -119,37 +147,43 @@ protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext) throws FileNotFoundException, IOException { MemPack memPack = (MemPack) desc; - byte[] file = memPack.fileMap.get(ext); + byte[] file = memPack.get(ext); if (file == null) throw new FileNotFoundException(desc.getFileName(ext)); - return new ByteArrayReadableChannel(file); + return new ByteArrayReadableChannel(file, blockSize); } @Override - protected DfsOutputStream writeFile( - DfsPackDescription desc, final PackExt ext) throws IOException { - final MemPack memPack = (MemPack) desc; + protected DfsOutputStream writeFile(DfsPackDescription desc, + PackExt ext) throws IOException { + MemPack memPack = (MemPack) desc; return new Out() { @Override public void flush() { - memPack.fileMap.put(ext, getData()); + memPack.put(ext, getData()); } }; } } private static class MemPack extends DfsPackDescription { - private final Map - fileMap = new HashMap(); + final byte[][] fileMap = new byte[PackExt.values().length][]; MemPack(String name, DfsRepositoryDescription repoDesc) { super(repoDesc, name); } + + void put(PackExt ext, byte[] data) { + fileMap[ext.getPosition()] = data; + } + + byte[] get(PackExt ext) { + return fileMap[ext.getPosition()]; + } } private abstract static class Out extends DfsOutputStream { private final ByteArrayOutputStream dst = new ByteArrayOutputStream(); - private byte[] data; @Override @@ -181,20 +215,20 @@ public void close() { flush(); } - } private static class ByteArrayReadableChannel implements ReadableChannel { private final byte[] data; - + private final int blockSize; private int position; - private boolean open = true; - ByteArrayReadableChannel(byte[] buf) { + ByteArrayReadableChannel(byte[] buf, int blockSize) { data = buf; + this.blockSize = blockSize; } + @Override public int read(ByteBuffer dst) { int n = Math.min(dst.remaining(), data.length - position); if (n == 0) @@ -204,121 +238,63 @@ return n; } + @Override public void close() { open = false; } + @Override public boolean isOpen() { return open; } + @Override public long position() { return position; } + @Override public void position(long newPosition) { position = (int) newPosition; } + @Override public long size() { return data.length; } + @Override public int blockSize() { - return 0; + return blockSize; } + @Override public void setReadAheadBytes(int b) { // Unnecessary on a byte array. } } - private class MemRefDatabase extends DfsRefDatabase { - private final ConcurrentMap refs = new ConcurrentHashMap(); + /** DfsRefDatabase used by InMemoryRepository. */ + protected class MemRefDatabase extends DfsReftableDatabase { + boolean performsAtomicTransactions = true; - MemRefDatabase() { + /** Initialize a new in-memory ref database. */ + protected MemRefDatabase() { super(InMemoryRepository.this); } @Override - protected RefCache scanAllRefs() throws IOException { - RefList.Builder ids = new RefList.Builder(); - RefList.Builder sym = new RefList.Builder(); - for (Ref ref : refs.values()) { - if (ref.isSymbolic()) - sym.add(ref); - ids.add(ref); - } - ids.sort(); - sym.sort(); - return new RefCache(ids.toRefList(), sym.toRefList()); - } - - @Override - protected boolean compareAndPut(Ref oldRef, Ref newRef) - throws IOException { - ObjectId id = newRef.getObjectId(); - if (id != null) { - try (RevWalk rw = new RevWalk(getRepository())) { - // Validate that the target exists in a new RevWalk, as the RevWalk - // from the RefUpdate might be reading back unflushed objects. - rw.parseAny(id); - } - } - String name = newRef.getName(); - if (oldRef == null) - return refs.putIfAbsent(name, newRef) == null; - - synchronized (refs) { - Ref cur = refs.get(name); - Ref toCompare = cur; - if (toCompare != null) { - if (toCompare.isSymbolic()) { - // Arm's-length dereference symrefs before the compare, since - // DfsRefUpdate#doLink(String) stores them undereferenced. - Ref leaf = toCompare.getLeaf(); - if (leaf.getObjectId() == null) { - leaf = refs.get(leaf.getName()); - if (leaf.isSymbolic()) - // Not supported at the moment. - throw new IllegalArgumentException(); - toCompare = new SymbolicRef( - name, - new ObjectIdRef.Unpeeled( - Storage.NEW, - leaf.getName(), - leaf.getObjectId())); - } else - toCompare = toCompare.getLeaf(); - } - if (eq(toCompare, oldRef)) - return refs.replace(name, cur, newRef); - } - } - - if (oldRef.getStorage() == Storage.NEW) - return refs.putIfAbsent(name, newRef) == null; - - return false; + public ReftableConfig getReftableConfig() { + ReftableConfig cfg = new ReftableConfig(); + cfg.setAlignBlocks(false); + cfg.setIndexObjects(false); + cfg.fromConfig(getRepository().getConfig()); + return cfg; } @Override - protected boolean compareAndRemove(Ref oldRef) throws IOException { - String name = oldRef.getName(); - Ref cur = refs.get(name); - if (cur != null && eq(cur, oldRef)) - return refs.remove(name, cur); - else - return false; - } - - private boolean eq(Ref a, Ref b) { - if (!Objects.equals(a.getName(), b.getName())) - return false; - // Compare leaf object IDs, since the oldRef passed into compareAndPut - // when detaching a symref is an ObjectIdRef. - return Objects.equals(a.getLeaf().getObjectId(), - b.getLeaf().getObjectId()); + public boolean performsAtomicTransactions() { + return performsAtomicTransactions; } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,29 +77,34 @@ this.db = db; } + /** {@inheritDoc} */ @Override public int getType() { return type; } + /** {@inheritDoc} */ @Override public long getSize() { return size; } + /** {@inheritDoc} */ @Override public boolean isLarge() { return true; } + /** {@inheritDoc} */ @Override public byte[] getCachedBytes() throws LargeObjectException { throw new LargeObjectException(); } + /** {@inheritDoc} */ @Override public ObjectStream openStream() throws MissingObjectException, IOException { - DfsReader ctx = new DfsReader(db); + DfsReader ctx = db.newReader(); InputStream in; try { in = new PackInputStream(pack, objectOffset + headerLength, ctx); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,6 +64,7 @@ ctx.pin(pack, pos); } + /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { int n = ctx.copy(pack, pos, b, off, len); @@ -71,6 +72,7 @@ return n; } + /** {@inheritDoc} */ @Override public int read() throws IOException { byte[] buf = new byte[1]; @@ -78,6 +80,7 @@ return n == 1 ? buf[0] & 0xff : -1; } + /** {@inheritDoc} */ @Override public void close() { ctx.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,13 +46,15 @@ import java.io.IOException; import java.nio.channels.ReadableByteChannel; -/** Readable random access byte channel from a file. */ +/** + * Readable random access byte channel from a file. + */ public interface ReadableChannel extends ReadableByteChannel { /** * Get the current position of the channel. * * @return r current offset. - * @throws IOException + * @throws java.io.IOException * the channel's current position cannot be obtained. */ public long position() throws IOException; @@ -63,7 +65,7 @@ * @param newPosition * position to move the channel to. The next read will start from * here. This should be a multiple of the {@link #blockSize()}. - * @throws IOException + * @throws java.io.IOException * the position cannot be updated. This may be because the * channel only supports block aligned IO and the current * position is not block aligned. @@ -78,7 +80,7 @@ * read has been completed, the underlying file size should be available. * * @return r total size of the channel; -1 if not yet available. - * @throws IOException + * @throws java.io.IOException * the size cannot be determined. */ public long size() throws IOException; @@ -92,9 +94,10 @@ *

* Channels should not recommend large block sizes. Sizes up to 1-4 MiB may * be reasonable, but sizes above that may be horribly inefficient. The - * {@link DfsBlockCache} favors the alignment suggested by the channel - * rather than the configured size under the assumption that reads are very - * expensive and the channel knows what size is best to access it with. + * {@link org.eclipse.jgit.internal.storage.dfs.DfsBlockCache} favors the + * alignment suggested by the channel rather than the configured size under + * the assumption that reads are very expensive and the channel knows what + * size is best to access it with. * * @return recommended alignment size for randomly positioned reads. Does * not need to be a power of 2. @@ -125,7 +128,7 @@ * * @param bufferSize * requested size of the read ahead buffer, in bytes. - * @throws IOException + * @throws java.io.IOException * if the read ahead cannot be adjusted. */ public void setReadAheadBytes(int bufferSize) throws IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.internal.storage.reftable.RefCursor; +import org.eclipse.jgit.internal.storage.reftable.Reftable; +import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.internal.storage.reftable.ReftableReader; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * {@link org.eclipse.jgit.lib.BatchRefUpdate} for + * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase}. + */ +public class ReftableBatchRefUpdate extends BatchRefUpdate { + private static final int AVG_BYTES = 36; + + private final DfsReftableDatabase refdb; + + private final DfsObjDatabase odb; + + private final ReentrantLock lock; + + private final ReftableConfig reftableConfig; + + /** + * Initialize batch update. + * + * @param refdb + * database the update will modify. + * @param odb + * object database to store the reftable. + */ + protected ReftableBatchRefUpdate(DfsReftableDatabase refdb, + DfsObjDatabase odb) { + super(refdb); + this.refdb = refdb; + this.odb = odb; + lock = refdb.getLock(); + reftableConfig = refdb.getReftableConfig(); + } + + /** {@inheritDoc} */ + @Override + public void execute(RevWalk rw, ProgressMonitor pm, List options) { + List pending = getPending(); + if (pending.isEmpty()) { + return; + } + if (options != null) { + setPushOptions(options); + } + try { + if (!checkObjectExistence(rw, pending)) { + return; + } + if (!checkNonFastForwards(rw, pending)) { + return; + } + + lock.lock(); + try { + Reftable table = refdb.reader(); + if (!checkExpected(table, pending)) { + return; + } + if (!checkConflicting(pending)) { + return; + } + if (!blockUntilTimestamps(MAX_WAIT)) { + return; + } + applyUpdates(rw, pending); + for (ReceiveCommand cmd : pending) { + cmd.setResult(OK); + } + } finally { + lock.unlock(); + } + } catch (IOException e) { + pending.get(0).setResult(LOCK_FAILURE, "io error"); //$NON-NLS-1$ + ReceiveCommand.abort(pending); + } + } + + private List getPending() { + return ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED); + } + + private boolean checkObjectExistence(RevWalk rw, + List pending) throws IOException { + for (ReceiveCommand cmd : pending) { + try { + if (!cmd.getNewId().equals(ObjectId.zeroId())) { + rw.parseAny(cmd.getNewId()); + } + } catch (MissingObjectException e) { + // ReceiveCommand#setResult(Result) converts REJECTED to + // REJECTED_NONFASTFORWARD, even though that result is also + // used for a missing object. Eagerly handle this case so we + // can set the right result. + cmd.setResult(REJECTED_MISSING_OBJECT); + ReceiveCommand.abort(pending); + return false; + } + } + return true; + } + + private boolean checkNonFastForwards(RevWalk rw, + List pending) throws IOException { + if (isAllowNonFastForwards()) { + return true; + } + for (ReceiveCommand cmd : pending) { + cmd.updateType(rw); + if (cmd.getType() == UPDATE_NONFASTFORWARD) { + cmd.setResult(REJECTED_NONFASTFORWARD); + ReceiveCommand.abort(pending); + return false; + } + } + return true; + } + + private boolean checkConflicting(List pending) + throws IOException { + Set names = new HashSet<>(); + for (ReceiveCommand cmd : pending) { + names.add(cmd.getRefName()); + } + + boolean ok = true; + for (ReceiveCommand cmd : pending) { + String name = cmd.getRefName(); + if (refdb.isNameConflicting(name)) { + cmd.setResult(LOCK_FAILURE); + ok = false; + } else { + int s = name.lastIndexOf('/'); + while (0 < s) { + if (names.contains(name.substring(0, s))) { + cmd.setResult(LOCK_FAILURE); + ok = false; + break; + } + s = name.lastIndexOf('/', s - 1); + } + } + } + if (!ok && isAtomic()) { + ReceiveCommand.abort(pending); + return false; + } + return ok; + } + + private boolean checkExpected(Reftable table, List pending) + throws IOException { + for (ReceiveCommand cmd : pending) { + Ref ref; + try (RefCursor rc = table.seekRef(cmd.getRefName())) { + ref = rc.next() ? rc.getRef() : null; + } + if (!matchOld(cmd, ref)) { + cmd.setResult(LOCK_FAILURE); + if (isAtomic()) { + ReceiveCommand.abort(pending); + return false; + } + } + } + return true; + } + + private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) { + if (ref == null) { + return AnyObjectId.equals(ObjectId.zeroId(), cmd.getOldId()) + && cmd.getOldSymref() == null; + } else if (ref.isSymbolic()) { + return ref.getTarget().getName().equals(cmd.getOldSymref()); + } + ObjectId id = ref.getObjectId(); + if (id == null) { + id = ObjectId.zeroId(); + } + return cmd.getOldId().equals(id); + } + + private void applyUpdates(RevWalk rw, List pending) + throws IOException { + List newRefs = toNewRefs(rw, pending); + long updateIndex = nextUpdateIndex(); + Set prune = Collections.emptySet(); + DfsPackDescription pack = odb.newPack(PackSource.INSERT); + try (DfsOutputStream out = odb.writeFile(pack, REFTABLE)) { + ReftableConfig cfg = DfsPackCompactor + .configureReftable(reftableConfig, out); + + ReftableWriter.Stats stats; + if (refdb.compactDuringCommit() + && newRefs.size() * AVG_BYTES <= cfg.getRefBlockSize() + && canCompactTopOfStack(cfg)) { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + write(tmp, cfg, updateIndex, newRefs, pending); + stats = compactTopOfStack(out, cfg, tmp.toByteArray()); + prune = toPruneTopOfStack(); + } else { + stats = write(out, cfg, updateIndex, newRefs, pending); + } + pack.addFileExt(REFTABLE); + pack.setReftableStats(stats); + } + + odb.commitPack(Collections.singleton(pack), prune); + odb.addReftable(pack, prune); + refdb.clearCache(); + } + + private ReftableWriter.Stats write(OutputStream os, ReftableConfig cfg, + long updateIndex, List newRefs, List pending) + throws IOException { + ReftableWriter writer = new ReftableWriter(cfg) + .setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex) + .begin(os).sortAndWriteRefs(newRefs); + if (!isRefLogDisabled()) { + writeLog(writer, updateIndex, pending); + } + writer.finish(); + return writer.getStats(); + } + + private void writeLog(ReftableWriter writer, long updateIndex, + List pending) throws IOException { + Map cmds = new HashMap<>(); + List byName = new ArrayList<>(pending.size()); + for (ReceiveCommand cmd : pending) { + cmds.put(cmd.getRefName(), cmd); + byName.add(cmd.getRefName()); + } + Collections.sort(byName); + + PersonIdent ident = getRefLogIdent(); + if (ident == null) { + ident = new PersonIdent(refdb.getRepository()); + } + for (String name : byName) { + ReceiveCommand cmd = cmds.get(name); + if (isRefLogDisabled(cmd)) { + continue; + } + String msg = getRefLogMessage(cmd); + if (isRefLogIncludingResult(cmd)) { + String strResult = toResultString(cmd); + if (strResult != null) { + msg = msg.isEmpty() ? strResult : msg + ": " + strResult; //$NON-NLS-1$ + } + } + writer.writeLog(name, updateIndex, ident, cmd.getOldId(), + cmd.getNewId(), msg); + } + } + + private String toResultString(ReceiveCommand cmd) { + switch (cmd.getType()) { + case CREATE: + return ReflogEntry.PREFIX_CREATED; + case UPDATE: + // Match the behavior of a single RefUpdate. In that case, setting + // the force bit completely bypasses the potentially expensive + // isMergedInto check, by design, so the reflog message may be + // inaccurate. + // + // Similarly, this class bypasses the isMergedInto checks when the + // force bit is set, meaning we can't actually distinguish between + // UPDATE and UPDATE_NONFASTFORWARD when isAllowNonFastForwards() + // returns true. + return isAllowNonFastForwards() ? ReflogEntry.PREFIX_FORCED_UPDATE + : ReflogEntry.PREFIX_FAST_FORWARD; + case UPDATE_NONFASTFORWARD: + return ReflogEntry.PREFIX_FORCED_UPDATE; + default: + return null; + } + } + + private static List toNewRefs(RevWalk rw, List pending) + throws IOException { + List refs = new ArrayList<>(pending.size()); + for (ReceiveCommand cmd : pending) { + String name = cmd.getRefName(); + ObjectId newId = cmd.getNewId(); + String newSymref = cmd.getNewSymref(); + if (AnyObjectId.equals(ObjectId.zeroId(), newId) + && newSymref == null) { + refs.add(new ObjectIdRef.Unpeeled(NEW, name, null)); + continue; + } else if (newSymref != null) { + refs.add(new SymbolicRef(name, + new ObjectIdRef.Unpeeled(NEW, newSymref, null))); + continue; + } + + RevObject obj = rw.parseAny(newId); + RevObject peel = null; + if (obj instanceof RevTag) { + peel = rw.peel(obj); + } + if (peel != null) { + refs.add(new ObjectIdRef.PeeledTag(PACKED, name, newId, + peel.copy())); + } else { + refs.add(new ObjectIdRef.PeeledNonTag(PACKED, name, newId)); + } + } + return refs; + } + + private long nextUpdateIndex() throws IOException { + long updateIndex = 0; + for (Reftable r : refdb.stack().readers()) { + if (r instanceof ReftableReader) { + updateIndex = Math.max(updateIndex, + ((ReftableReader) r).maxUpdateIndex()); + } + } + return updateIndex + 1; + } + + private boolean canCompactTopOfStack(ReftableConfig cfg) + throws IOException { + ReftableStack stack = refdb.stack(); + List readers = stack.readers(); + if (readers.isEmpty()) { + return false; + } + + int lastIdx = readers.size() - 1; + DfsReftable last = stack.files().get(lastIdx); + DfsPackDescription desc = last.getPackDescription(); + if (desc.getPackSource() != PackSource.INSERT + || !packOnlyContainsReftable(desc)) { + return false; + } + + Reftable table = readers.get(lastIdx); + int bs = cfg.getRefBlockSize(); + return table instanceof ReftableReader + && ((ReftableReader) table).size() <= 3 * bs; + } + + private ReftableWriter.Stats compactTopOfStack(OutputStream out, + ReftableConfig cfg, byte[] newTable) throws IOException { + List stack = refdb.stack().readers(); + Reftable last = stack.get(stack.size() - 1); + + List tables = new ArrayList<>(2); + tables.add(last); + tables.add(new ReftableReader(BlockSource.from(newTable))); + + ReftableCompactor compactor = new ReftableCompactor(); + compactor.setConfig(cfg); + compactor.setIncludeDeletes(true); + compactor.addAll(tables); + compactor.compact(out); + return compactor.getStats(); + } + + private Set toPruneTopOfStack() throws IOException { + List stack = refdb.stack().files(); + DfsReftable last = stack.get(stack.size() - 1); + return Collections.singleton(last.getPackDescription()); + } + + private boolean packOnlyContainsReftable(DfsPackDescription desc) { + for (PackExt ext : PackExt.values()) { + if (ext != REFTABLE && desc.hasFileExt(ext)) { + return false; + } + } + return true; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.internal.storage.reftable.Reftable; + +/** + * Tracks multiple open + * {@link org.eclipse.jgit.internal.storage.reftable.Reftable} instances. + */ +public class ReftableStack implements AutoCloseable { + /** + * Opens a stack of tables for reading. + * + * @param ctx + * context to read the tables with. This {@code ctx} will be + * retained by the stack and each of the table readers. + * @param files + * the tables to open. + * @return stack reference to close the tables. + * @throws java.io.IOException + * a table could not be opened + */ + public static ReftableStack open(DfsReader ctx, List files) + throws IOException { + ReftableStack stack = new ReftableStack(files.size()); + boolean close = true; + try { + for (DfsReftable t : files) { + stack.files.add(t); + stack.tables.add(t.open(ctx)); + } + close = false; + return stack; + } finally { + if (close) { + stack.close(); + } + } + } + + private final List files; + private final List tables; + + private ReftableStack(int tableCnt) { + this.files = new ArrayList<>(tableCnt); + this.tables = new ArrayList<>(tableCnt); + } + + /** + * Get unmodifiable list of DfsRefatble files + * + * @return unmodifiable list of DfsRefatble files, in the same order the + * files were passed to {@link #open(DfsReader, List)}. + */ + public List files() { + return Collections.unmodifiableList(files); + } + + /** + * Get unmodifiable list of tables + * + * @return unmodifiable list of tables, in the same order the files were + * passed to {@link #open(DfsReader, List)}. + */ + public List readers() { + return Collections.unmodifiableList(tables); + } + + /** {@inheritDoc} */ + @Override + public void close() { + for (Reftable t : tables) { + try { + t.close(); + } catch (IOException e) { + // Ignore close failures. + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,11 @@ package org.eclipse.jgit.internal.storage.file; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import com.googlecode.javaewah.EWAHCompressedBitmap; + /** * Base implementation of the PackBitmapIndex. */ @@ -58,6 +58,8 @@ this.bitmaps = bitmaps; } + /** {@inheritDoc} */ + @Override public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) { StoredBitmap sb = bitmaps.get(objectId); return sb != null ? sb.getBitmap() : null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,9 +47,6 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import com.googlecode.javaewah.EWAHCompressedBitmap; -import com.googlecode.javaewah.IntIterator; - import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BitmapIndex; @@ -59,15 +56,20 @@ import org.eclipse.jgit.lib.ObjectIdOwnerMap; import org.eclipse.jgit.util.BlockList; -/** A compressed bitmap representation of the entire object graph. */ +import com.googlecode.javaewah.EWAHCompressedBitmap; +import com.googlecode.javaewah.IntIterator; + +/** + * A compressed bitmap representation of the entire object graph. + */ public class BitmapIndexImpl implements BitmapIndex { private static final int EXTRA_BITS = 10 * 1024; - private final PackBitmapIndex packIndex; + final PackBitmapIndex packIndex; - private final MutableBitmapIndex mutableIndex; + final MutableBitmapIndex mutableIndex; - private final int indexObjectCount; + final int indexObjectCount; /** * Creates a BitmapIndex that is back by Compressed bitmaps. @@ -85,18 +87,22 @@ return packIndex; } + /** {@inheritDoc} */ + @Override public CompressedBitmap getBitmap(AnyObjectId objectId) { EWAHCompressedBitmap compressed = packIndex.getBitmap(objectId); if (compressed == null) return null; - return new CompressedBitmap(compressed); + return new CompressedBitmap(compressed, this); } + /** {@inheritDoc} */ + @Override public CompressedBitmapBuilder newBitmapBuilder() { - return new CompressedBitmapBuilder(); + return new CompressedBitmapBuilder(this); } - private int findPosition(AnyObjectId objectId) { + int findPosition(AnyObjectId objectId) { int position = packIndex.findPosition(objectId); if (position < 0) { position = mutableIndex.findPosition(objectId); @@ -106,10 +112,10 @@ return position; } - private int addObject(AnyObjectId objectId, int type) { + int findOrInsert(AnyObjectId objectId, int type) { int position = findPosition(objectId); if (position < 0) { - position = mutableIndex.addObject(objectId, type); + position = mutableIndex.findOrInsert(objectId, type); position += indexObjectCount; } return position; @@ -122,11 +128,11 @@ private BitSet toRemove; - private ComboBitset() { + ComboBitset() { this(new EWAHCompressedBitmap()); } - private ComboBitset(EWAHCompressedBitmap bitmap) { + ComboBitset(EWAHCompressedBitmap bitmap) { this.inflatingBitmap = new InflatingBitSet(bitmap); } @@ -197,15 +203,22 @@ } } - private final class CompressedBitmapBuilder implements BitmapBuilder { - private ComboBitset bitset = new ComboBitset(); + private static final class CompressedBitmapBuilder implements BitmapBuilder { + private ComboBitset bitset; + private final BitmapIndexImpl bitmapIndex; + + CompressedBitmapBuilder(BitmapIndexImpl bitmapIndex) { + this.bitset = new ComboBitset(); + this.bitmapIndex = bitmapIndex; + } + @Override public boolean add(AnyObjectId objectId, int type) { - int position = addObject(objectId, type); + int position = bitmapIndex.findOrInsert(objectId, type); if (bitset.contains(position)) return false; - Bitmap entry = getBitmap(objectId); + Bitmap entry = bitmapIndex.getBitmap(objectId); if (entry != null) { or(entry); return false; @@ -215,120 +228,142 @@ return true; } + @Override public boolean contains(AnyObjectId objectId) { - int position = findPosition(objectId); + int position = bitmapIndex.findPosition(objectId); return 0 <= position && bitset.contains(position); } + @Override + public BitmapBuilder addObject(AnyObjectId objectId, int type) { + bitset.set(bitmapIndex.findOrInsert(objectId, type)); + return this; + } + + @Override public void remove(AnyObjectId objectId) { - int position = findPosition(objectId); + int position = bitmapIndex.findPosition(objectId); if (0 <= position) bitset.remove(position); } + @Override public CompressedBitmapBuilder or(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.or(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.or(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.or(ewahBitmap(other)); return this; } + @Override public CompressedBitmapBuilder andNot(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.andNot(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.andNot(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.andNot(ewahBitmap(other)); return this; } + @Override public CompressedBitmapBuilder xor(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.xor(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.xor(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.xor(ewahBitmap(other)); return this; } /** @return the fully built immutable bitmap */ + @Override public CompressedBitmap build() { - return new CompressedBitmap(bitset.combine()); + return new CompressedBitmap(bitset.combine(), bitmapIndex); } + @Override public Iterator iterator() { return build().iterator(); } + @Override public int cardinality() { return bitset.combine().cardinality(); } + @Override public boolean removeAllOrNone(PackBitmapIndex index) { - if (!packIndex.equals(index)) + if (!bitmapIndex.packIndex.equals(index)) return false; EWAHCompressedBitmap curr = bitset.combine() - .xor(ones(indexObjectCount)); + .xor(ones(bitmapIndex.indexObjectCount)); IntIterator ii = curr.intIterator(); - if (ii.hasNext() && ii.next() < indexObjectCount) + if (ii.hasNext() && ii.next() < bitmapIndex.indexObjectCount) return false; bitset = new ComboBitset(curr); return true; } - private BitmapIndexImpl getBitmapIndex() { - return BitmapIndexImpl.this; + @Override + public BitmapIndexImpl getBitmapIndex() { + return bitmapIndex; } - } - final class CompressedBitmap implements Bitmap { - private final EWAHCompressedBitmap bitmap; + private EWAHCompressedBitmap ewahBitmap(Bitmap other) { + if (other instanceof CompressedBitmap) { + CompressedBitmap b = (CompressedBitmap) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitmap; + } + if (other instanceof CompressedBitmapBuilder) { + CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitset.combine(); + } + throw new IllegalArgumentException(); + } + } - private CompressedBitmap(EWAHCompressedBitmap bitmap) { + /** + * Wrapper for a {@link EWAHCompressedBitmap} and {@link PackBitmapIndex}. + *

+ * For a EWAHCompressedBitmap {@code bitmap} representing a vector of + * bits, {@code new CompressedBitmap(bitmap, bitmapIndex)} represents the + * objects at those positions in {@code bitmapIndex.packIndex}. + */ + public static final class CompressedBitmap implements Bitmap { + final EWAHCompressedBitmap bitmap; + final BitmapIndexImpl bitmapIndex; + + /** + * Construct compressed bitmap for given bitmap and bitmap index + * + * @param bitmap + * @param bitmapIndex + */ + public CompressedBitmap(EWAHCompressedBitmap bitmap, BitmapIndexImpl bitmapIndex) { this.bitmap = bitmap; + this.bitmapIndex = bitmapIndex; } + @Override public CompressedBitmap or(Bitmap other) { - return new CompressedBitmap(bitmap.or(bitmapOf(other))); + return new CompressedBitmap(bitmap.or(ewahBitmap(other)), bitmapIndex); } + @Override public CompressedBitmap andNot(Bitmap other) { - return new CompressedBitmap(bitmap.andNot(bitmapOf(other))); + return new CompressedBitmap(bitmap.andNot(ewahBitmap(other)), bitmapIndex); } + @Override public CompressedBitmap xor(Bitmap other) { - return new CompressedBitmap(bitmap.xor(bitmapOf(other))); - } - - private EWAHCompressedBitmap bitmapOf(Bitmap other) { - if (isSameCompressedBitmap(other)) - return ((CompressedBitmap) other).bitmap; - if (isSameCompressedBitmapBuilder(other)) - return ((CompressedBitmapBuilder) other).build().bitmap; - CompressedBitmapBuilder builder = newBitmapBuilder(); - builder.or(other); - return builder.build().bitmap; + return new CompressedBitmap(bitmap.xor(ewahBitmap(other)), bitmapIndex); } private final IntIterator ofObjectType(int type) { - return packIndex.ofObjectType(bitmap, type).intIterator(); + return bitmapIndex.packIndex.ofObjectType(bitmap, type).intIterator(); } + @Override public Iterator iterator() { - final IntIterator dynamic = bitmap.andNot(ones(indexObjectCount)) + final IntIterator dynamic = bitmap.andNot(ones(bitmapIndex.indexObjectCount)) .intIterator(); final IntIterator commits = ofObjectType(Constants.OBJ_COMMIT); final IntIterator trees = ofObjectType(Constants.OBJ_TREE); @@ -339,6 +374,7 @@ private int type; private IntIterator cached = dynamic; + @Override public boolean hasNext() { if (!cached.hasNext()) { if (commits.hasNext()) { @@ -360,23 +396,25 @@ return true; } + @Override public BitmapObject next() { if (!hasNext()) throw new NoSuchElementException(); int position = cached.next(); - if (position < indexObjectCount) { + if (position < bitmapIndex.indexObjectCount) { out.type = type; - out.objectId = packIndex.getObject(position); + out.objectId = bitmapIndex.packIndex.getObject(position); } else { - position -= indexObjectCount; - MutableEntry entry = mutableIndex.getObject(position); + position -= bitmapIndex.indexObjectCount; + MutableEntry entry = bitmapIndex.mutableIndex.getObject(position); out.type = entry.type; out.objectId = entry; } return out; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -387,17 +425,31 @@ return bitmap; } - private BitmapIndexImpl getPackBitmapIndex() { - return BitmapIndexImpl.this; + private EWAHCompressedBitmap ewahBitmap(Bitmap other) { + if (other instanceof CompressedBitmap) { + CompressedBitmap b = (CompressedBitmap) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitmap; + } + if (other instanceof CompressedBitmapBuilder) { + CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitset.combine(); + } + throw new IllegalArgumentException(); } } private static final class MutableBitmapIndex { private final ObjectIdOwnerMap - revMap = new ObjectIdOwnerMap(); + revMap = new ObjectIdOwnerMap<>(); private final BlockList - revList = new BlockList(); + revList = new BlockList<>(); int findPosition(AnyObjectId objectId) { MutableEntry entry = revMap.get(objectId); @@ -419,7 +471,7 @@ } } - int addObject(AnyObjectId objectId, int type) { + int findOrInsert(AnyObjectId objectId, int type) { MutableEntry entry = new MutableEntry( objectId, type, revList.size()); revList.add(entry); @@ -429,9 +481,9 @@ } private static final class MutableEntry extends ObjectIdOwnerMap.Entry { - private final int type; + final int type; - private final int position; + final int position; MutableEntry(AnyObjectId objectId, int type, int position) { super(objectId); @@ -456,29 +508,13 @@ } } - private boolean isSameCompressedBitmap(Bitmap other) { - if (other instanceof CompressedBitmap) { - CompressedBitmap b = (CompressedBitmap) other; - return this == b.getPackBitmapIndex(); - } - return false; - } - - private boolean isSameCompressedBitmapBuilder(Bitmap other) { - if (other instanceof CompressedBitmapBuilder) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - return this == b.getBitmapIndex(); - } - return false; - } - - private static final EWAHCompressedBitmap ones(int sizeInBits) { + static final EWAHCompressedBitmap ones(int sizeInBits) { EWAHCompressedBitmap mask = new EWAHCompressedBitmap(); mask.addStreamOfEmptyWords( - true, sizeInBits / EWAHCompressedBitmap.wordinbits); - int remaining = sizeInBits % EWAHCompressedBitmap.wordinbits; + true, sizeInBits / EWAHCompressedBitmap.WORD_IN_BITS); + int remaining = sizeInBits % EWAHCompressedBitmap.WORD_IN_BITS; if (remaining > 0) - mask.add((1L << remaining) - 1, remaining); + mask.addWord((1L << remaining) - 1, remaining); return mask; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitSet.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitSet.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitSet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitSet.java 2019-09-03 12:37:49.000000000 +0000 @@ -96,7 +96,7 @@ } if (lastNonEmptyWord != 0) - compressed.add(lastNonEmptyWord); + compressed.addWord(lastNonEmptyWord); if (runningEmptyWords > 0) { compressed.addStreamOfEmptyWords(false, runningEmptyWords); @@ -107,7 +107,7 @@ } int bitsThatMatter = 64 - Long.numberOfLeadingZeros(lastNonEmptyWord); if (bitsThatMatter > 0) - compressed.add(lastNonEmptyWord, bitsThatMatter); + compressed.addWord(lastNonEmptyWord, bitsThatMatter); return compressed; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,6 +63,7 @@ array = b; } + /** {@inheritDoc} */ @Override protected int copy(final int p, final byte[] b, final int o, int n) { n = Math.min(array.length - p, n); @@ -70,6 +71,7 @@ return n; } + /** {@inheritDoc} */ @Override protected int setInput(final int pos, final Inflater inf) throws DataFormatException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,6 +65,7 @@ buffer = b; } + /** {@inheritDoc} */ @Override protected int copy(final int p, final byte[] b, final int o, int n) { final ByteBuffer s = buffer.slice(); @@ -89,6 +90,7 @@ } } + /** {@inheritDoc} */ @Override protected int setInput(final int pos, final Inflater inf) throws DataFormatException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,6 +66,16 @@ protected final long end; + /** + * Constructor for ByteWindow. + * + * @param p + * a {@link org.eclipse.jgit.internal.storage.file.PackFile}. + * @param s + * where the byte window starts in the pack file + * @param n + * size of the byte window + */ protected ByteWindow(final PackFile p, final long s, final int n) { pack = p; start = s; @@ -127,6 +137,17 @@ return setInput((int) (pos - start), inf); } + /** + * Set the input + * + * @param pos + * position + * @param inf + * an {@link java.util.zip.Inflater} object. + * @return size of the byte window + * @throws java.util.zip.DataFormatException + * if any. + */ protected abstract int setInput(int pos, Inflater inf) throws DataFormatException; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,8 +47,10 @@ import java.io.File; import java.io.IOException; import java.util.Collection; +import java.util.HashSet; import java.util.Set; +import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle; import org.eclipse.jgit.internal.storage.pack.ObjectToPack; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AbbreviatedObjectId; @@ -90,7 +92,7 @@ } private ObjectIdOwnerMap scanLoose() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); File objects = wrapped.getDirectory(); String[] fanout = objects.list(); if (fanout == null) @@ -115,11 +117,13 @@ return m; } + /** {@inheritDoc} */ @Override public void close() { // Don't close anything. } + /** {@inheritDoc} */ @Override public ObjectDatabase newCachedDatabase() { return this; @@ -160,43 +164,71 @@ return alts; } + private Set skipMe(Set skips) { + Set withMe = new HashSet<>(); + if (skips != null) { + withMe.addAll(skips); + } + withMe.add(getAlternateId()); + return withMe; + } + @Override void resolve(Set matches, AbbreviatedObjectId id) throws IOException { - // In theory we could accelerate the loose object scan using our - // unpackedObjects map, but its not worth the huge code complexity. - // Scanning a single loose directory is fast enough, and this is - // unlikely to be called anyway. - // wrapped.resolve(matches, id); } + /** {@inheritDoc} */ @Override public boolean has(final AnyObjectId objectId) throws IOException { - if (unpackedObjects.contains(objectId)) + return has(objectId, null); + } + + private boolean has(final AnyObjectId objectId, Set skips) + throws IOException { + if (unpackedObjects.contains(objectId)) { return true; - if (wrapped.hasPackedObject(objectId)) + } + if (wrapped.hasPackedObject(objectId)) { return true; + } + skips = skipMe(skips); for (CachedObjectDirectory alt : myAlternates()) { - if (alt.has(objectId)) - return true; + if (!skips.contains(alt.getAlternateId())) { + if (alt.has(objectId, skips)) { + return true; + } + } } return false; } @Override - ObjectLoader openObject(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { + return openObject(curs, objectId, null); + } + + private ObjectLoader openObject(final WindowCursor curs, + final AnyObjectId objectId, Set skips) + throws IOException { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } ldr = wrapped.openPackedObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = skipMe(skips); for (CachedObjectDirectory alt : myAlternates()) { - ldr = alt.openObject(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getAlternateId())) { + ldr = alt.openObject(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } @@ -260,4 +292,8 @@ super(id); } } + + private AlternateHandle.Id getAlternateId() { + return wrapped.getAlternateId(); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,11 +65,15 @@ to = comment.substring(p2 + " to ".length(), p3); //$NON-NLS-1$ } + /** {@inheritDoc} */ + @Override public String getFromBranch() { return from; } + /** {@inheritDoc} */ + @Override public String getToBranch() { return to; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ class DeltaBaseCache { private static final int CACHE_SZ = 1024; - private static final SoftReference DEAD; + static final SoftReference DEAD; private static int hash(final long position) { return (((int) position) << 22) >>> 22; @@ -69,7 +69,7 @@ private int openByteCount; static { - DEAD = new SoftReference(null); + DEAD = new SoftReference<>(null); reconfigure(new WindowCacheConfig()); } @@ -115,7 +115,7 @@ e.provider = pack; e.position = position; e.sz = data.length; - e.data = new SoftReference(new Entry(data, objectType)); + e.data = new SoftReference<>(new Entry(data, objectType)); moveToHead(e); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,11 +64,13 @@ INSERTED, EXISTS_PACKED, EXISTS_LOOSE, FAILURE; } + /** {@inheritDoc} */ @Override public ObjectReader newReader() { return new WindowCursor(this); } + /** {@inheritDoc} */ @Override public ObjectDirectoryInserter newInserter() { return new ObjectDirectoryInserter(this, getConfig()); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,11 +49,20 @@ import static org.eclipse.jgit.lib.RefDatabase.ALL; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; +import java.text.ParseException; import java.util.HashSet; +import java.util.Locale; +import java.util.Objects; import java.util.Set; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedListener; @@ -61,12 +70,14 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository; +import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; import org.eclipse.jgit.lib.BaseRepositoryBuilder; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.HideDotFiles; import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefUpdate; @@ -74,8 +85,11 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; @@ -102,19 +116,15 @@ * This class is thread-safe. *

* This implementation only handles a subtly undocumented subset of git features. - * */ public class FileRepository extends Repository { - private final FileBasedConfig systemConfig; + private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$ + private final FileBasedConfig systemConfig; private final FileBasedConfig userConfig; - private final FileBasedConfig repoConfig; - private final RefDatabase refs; - private final ObjectDirectory objectDatabase; - private FileSnapshot snapshot; /** @@ -122,8 +132,9 @@ *

* The work tree, object directory, alternate object directories and index * file locations are deduced from the given git directory and the default - * rules by running {@link FileRepositoryBuilder}. This constructor is the - * same as saying: + * rules by running + * {@link org.eclipse.jgit.storage.file.FileRepositoryBuilder}. This + * constructor is the same as saying: * *

 	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
@@ -131,7 +142,7 @@
 	 *
 	 * @param gitDir
 	 *            GIT_DIR (the location of the repository metadata).
-	 * @throws IOException
+	 * @throws java.io.IOException
 	 *             the repository appears to already exist but cannot be
 	 *             accessed.
 	 * @see FileRepositoryBuilder
@@ -145,7 +156,7 @@
 	 *
 	 * @param gitDir
 	 *            GIT_DIR (the location of the repository metadata).
-	 * @throws IOException
+	 * @throws java.io.IOException
 	 *             the repository appears to already exist but cannot be
 	 *             accessed.
 	 * @see FileRepositoryBuilder
@@ -159,7 +170,7 @@
 	 *
 	 * @param options
 	 *            description of the repository's important paths.
-	 * @throws IOException
+	 * @throws java.io.IOException
 	 *             the user configuration file or repository configuration file
 	 *             cannot be accessed.
 	 */
@@ -172,10 +183,12 @@
 					getFS());
 		else
 			systemConfig = new FileBasedConfig(null, FS.DETECTED) {
+				@Override
 				public void load() {
 					// empty, do not load
 				}
 
+				@Override
 				public boolean isOutdated() {
 					// regular class would bomb here
 					return false;
@@ -192,12 +205,28 @@
 		loadRepoConfig();
 
 		repoConfig.addChangeListener(new ConfigChangedListener() {
+			@Override
 			public void onConfigChanged(ConfigChangedEvent event) {
 				fireEvent(event);
 			}
 		});
 
-		refs = new RefDirectory(this);
+		final long repositoryFormatVersion = getConfig().getLong(
+				ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
+
+		String reftype = repoConfig.getString(
+				"extensions", null, "refStorage"); //$NON-NLS-1$ //$NON-NLS-2$
+		if (repositoryFormatVersion >= 1 && reftype != null) {
+			if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
+				refs = new RefTreeDatabase(this, new RefDirectory(this));
+			} else {
+				throw new IOException(JGitText.get().unknownRepositoryFormat);
+			}
+		} else {
+			refs = new RefDirectory(this);
+		}
+
 		objectDatabase = new ObjectDirectory(repoConfig, //
 				options.getObjectDirectory(), //
 				options.getAlternateObjectDirectories(), //
@@ -205,10 +234,7 @@
 				new File(getDirectory(), Constants.SHALLOW));
 
 		if (objectDatabase.exists()) {
-			final long repositoryFormatVersion = getConfig().getLong(
-					ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
-			if (repositoryFormatVersion > 0)
+			if (repositoryFormatVersion > 1)
 				throw new IOException(MessageFormat.format(
 						JGitText.get().unknownRepositoryFormat2,
 						Long.valueOf(repositoryFormatVersion)));
@@ -221,47 +247,40 @@
 	private void loadSystemConfig() throws IOException {
 		try {
 			systemConfig.load();
-		} catch (ConfigInvalidException e1) {
-			IOException e2 = new IOException(MessageFormat.format(JGitText
+		} catch (ConfigInvalidException e) {
+			throw new IOException(MessageFormat.format(JGitText
 					.get().systemConfigFileInvalid, systemConfig.getFile()
-					.getAbsolutePath(), e1));
-			e2.initCause(e1);
-			throw e2;
+							.getAbsolutePath(),
+					e), e);
 		}
 	}
 
 	private void loadUserConfig() throws IOException {
 		try {
 			userConfig.load();
-		} catch (ConfigInvalidException e1) {
-			IOException e2 = new IOException(MessageFormat.format(JGitText
+		} catch (ConfigInvalidException e) {
+			throw new IOException(MessageFormat.format(JGitText
 					.get().userConfigFileInvalid, userConfig.getFile()
-					.getAbsolutePath(), e1));
-			e2.initCause(e1);
-			throw e2;
+							.getAbsolutePath(),
+					e), e);
 		}
 	}
 
 	private void loadRepoConfig() throws IOException {
 		try {
 			repoConfig.load();
-		} catch (ConfigInvalidException e1) {
-			IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat);
-			e2.initCause(e1);
-			throw e2;
+		} catch (ConfigInvalidException e) {
+			throw new IOException(JGitText.get().unknownRepositoryFormat, e);
 		}
 	}
 
 	/**
+	 * {@inheritDoc}
+	 * 

* Create a new Git repository initializing the necessary files and * directories. - * - * @param bare - * if true, a bare repository is created. - * - * @throws IOException - * in case of IO problem */ + @Override public void create(boolean bare) throws IOException { final FileBasedConfig cfg = getConfig(); if (cfg.getFile().exists()) { @@ -316,7 +335,7 @@ if (symLinks != null) cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name() - .toLowerCase()); + .toLowerCase(Locale.ROOT)); cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, @@ -337,7 +356,7 @@ ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree() .getAbsolutePath()); LockFile dotGitLockFile = new LockFile(new File(workTree, - Constants.DOT_GIT), getFS()); + Constants.DOT_GIT)); try { if (dotGitLockFile.lock()) { dotGitLockFile.write(Constants.encode(Constants.GITDIR @@ -353,27 +372,28 @@ } /** + * Get the directory containing the objects owned by this repository + * * @return the directory containing the objects owned by this repository. */ public File getObjectsDirectory() { return objectDatabase.getDirectory(); } - /** - * @return the object database which stores this repository's data. - */ + /** {@inheritDoc} */ + @Override public ObjectDirectory getObjectDatabase() { return objectDatabase; } - /** @return the reference database which stores the reference namespace. */ + /** {@inheritDoc} */ + @Override public RefDatabase getRefDatabase() { return refs; } - /** - * @return the configuration of this repository - */ + /** {@inheritDoc} */ + @Override public FileBasedConfig getConfig() { if (systemConfig.isOutdated()) { try { @@ -399,6 +419,76 @@ return repoConfig; } + /** {@inheritDoc} */ + @Override + @Nullable + public String getGitwebDescription() throws IOException { + String d; + try { + d = RawParseUtils.decode(IO.readFully(descriptionFile())); + } catch (FileNotFoundException err) { + return null; + } + if (d != null) { + d = d.trim(); + if (d.isEmpty() || UNNAMED.equals(d)) { + return null; + } + } + return d; + } + + /** {@inheritDoc} */ + @Override + public void setGitwebDescription(@Nullable String description) + throws IOException { + String old = getGitwebDescription(); + if (Objects.equals(old, description)) { + return; + } + + File path = descriptionFile(); + LockFile lock = new LockFile(path); + if (!lock.lock()) { + throw new IOException(MessageFormat.format(JGitText.get().lockError, + path.getAbsolutePath())); + } + try { + String d = description; + if (d != null) { + d = d.trim(); + if (!d.isEmpty()) { + d += '\n'; + } + } else { + d = ""; //$NON-NLS-1$ + } + lock.write(Constants.encode(d)); + lock.commit(); + } finally { + lock.unlock(); + } + } + + private File descriptionFile() { + return new File(getDirectory(), "description"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + *

+ * Objects known to exist but not expressed by {@link #getAllRefs()}. + *

+ * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help a + * client trying to push changes avoid pushing more than it needs to. + */ + @Override + public Set getAdditionalHaves() { + return getAdditionalHaves(null); + } + /** * Objects known to exist but not expressed by {@link #getAllRefs()}. *

@@ -407,13 +497,17 @@ * exposing any other details about the other repository. This may help * a client trying to push changes avoid pushing more than it needs to. * + * @param skips + * Set of AlternateHandle Ids already seen + * * @return unmodifiable collection of other known objects. */ - public Set getAdditionalHaves() { - HashSet r = new HashSet(); + private Set getAdditionalHaves(Set skips) { + HashSet r = new HashSet<>(); + skips = objectDatabase.addMe(skips); for (AlternateHandle d : objectDatabase.myAlternates()) { - if (d instanceof AlternateRepository) { - Repository repo; + if (d instanceof AlternateRepository && !skips.contains(d.getId())) { + FileRepository repo; repo = ((AlternateRepository) d).repository; for (Ref ref : repo.getAllRefs().values()) { @@ -422,7 +516,7 @@ if (ref.getPeeledObjectId() != null) r.add(ref.getPeeledObjectId()); } - r.addAll(repo.getAdditionalHaves()); + r.addAll(repo.getAdditionalHaves(skips)); } } return r; @@ -433,7 +527,7 @@ * * @param pack * path of the pack file to open. - * @throws IOException + * @throws java.io.IOException * index file could not be opened, read, or is not recognized as * a Git pack file index. */ @@ -441,15 +535,14 @@ objectDatabase.openPack(pack); } + /** {@inheritDoc} */ @Override public void scanForRepoChanges() throws IOException { getRefDatabase().getRefs(ALL); // This will look for changes to refs detectIndexChanges(); } - /** - * Detect index changes. - */ + /** Detect index changes. */ private void detectIndexChanges() { if (isBare()) return; @@ -461,22 +554,97 @@ notifyIndexChanged(); } + /** {@inheritDoc} */ @Override public void notifyIndexChanged() { snapshot = FileSnapshot.save(getIndexFile()); fireEvent(new IndexChangedEvent()); } - /** - * @param refName - * @return a {@link ReflogReader} for the supplied refname, or null if the - * named ref does not exist. - * @throws IOException the ref could not be accessed. - */ + /** {@inheritDoc} */ + @Override public ReflogReader getReflogReader(String refName) throws IOException { - Ref ref = getRef(refName); + Ref ref = findRef(refName); if (ref != null) return new ReflogReaderImpl(this, ref.getName()); return null; } + + /** {@inheritDoc} */ + @Override + public AttributesNodeProvider createAttributesNodeProvider() { + return new AttributesNodeProviderImpl(this); + } + + /** + * Implementation a {@link AttributesNodeProvider} for a + * {@link FileRepository}. + * + * @author Arthur Daussy + * + */ + static class AttributesNodeProviderImpl implements + AttributesNodeProvider { + + private AttributesNode infoAttributesNode; + + private AttributesNode globalAttributesNode; + + /** + * Constructor. + * + * @param repo + * {@link Repository} that will provide the attribute nodes. + */ + protected AttributesNodeProviderImpl(Repository repo) { + infoAttributesNode = new InfoAttributesNode(repo); + globalAttributesNode = new GlobalAttributesNode(repo); + } + + @Override + public AttributesNode getInfoAttributesNode() throws IOException { + if (infoAttributesNode instanceof InfoAttributesNode) + infoAttributesNode = ((InfoAttributesNode) infoAttributesNode) + .load(); + return infoAttributesNode; + } + + @Override + public AttributesNode getGlobalAttributesNode() throws IOException { + if (globalAttributesNode instanceof GlobalAttributesNode) + globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode) + .load(); + return globalAttributesNode; + } + + static void loadRulesFromFile(AttributesNode r, File attrs) + throws FileNotFoundException, IOException { + if (attrs.exists()) { + try (FileInputStream in = new FileInputStream(attrs)) { + r.parse(in); + } + } + } + + } + + private boolean shouldAutoDetach() { + return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION, + ConfigConstants.CONFIG_KEY_AUTODETACH, true); + } + + /** {@inheritDoc} */ + @Override + public void autoGC(ProgressMonitor monitor) { + GC gc = new GC(this); + gc.setPackConfig(new PackConfig(this)); + gc.setProgressMonitor(monitor); + gc.setAuto(true); + gc.setBackground(shouldAutoDetach()); + try { + gc.gc(); + } catch (ParseException | IOException e) { + throw new JGitInternalException(JGitText.get().gcFailed, e); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,8 @@ package org.eclipse.jgit.internal.storage.file; import java.io.File; +import java.io.IOException; +import java.nio.file.attribute.BasicFileAttributes; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -69,13 +71,20 @@ */ public class FileSnapshot { /** + * An unknown file size. + * + * This value is used when a comparison needs to happen purely on the lastUpdate. + */ + public static final long UNKNOWN_SIZE = -1; + + /** * A FileSnapshot that is considered to always be modified. *

* This instance is useful for application code that wants to lazily read a * file, but only after {@link #isModified(File)} gets invoked. The returned * snapshot contains only invalid status information. */ - public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1); + public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, UNKNOWN_SIZE); /** * A FileSnapshot that is clean if the file does not exist. @@ -84,7 +93,7 @@ * file to be clean. {@link #isModified(File)} will return false if the file * path does not exist. */ - public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0) { + public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0) { @Override public boolean isModified(File path) { return FS.DETECTED.exists(path); @@ -102,9 +111,18 @@ * @return the snapshot. */ public static FileSnapshot save(File path) { - final long read = System.currentTimeMillis(); - final long modified = path.lastModified(); - return new FileSnapshot(read, modified); + long read = System.currentTimeMillis(); + long modified; + long size; + try { + BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); + modified = fileAttributes.lastModifiedTime().toMillis(); + size = fileAttributes.size(); + } catch (IOException e) { + modified = path.lastModified(); + size = path.length(); + } + return new FileSnapshot(read, modified, size); } /** @@ -115,12 +133,11 @@ * * @param modified * the last modification time of the file - * * @return the snapshot. */ public static FileSnapshot save(long modified) { final long read = System.currentTimeMillis(); - return new FileSnapshot(read, modified); + return new FileSnapshot(read, modified, -1); } /** Last observed modification time of the path. */ @@ -132,13 +149,21 @@ /** True once {@link #lastRead} is far later than {@link #lastModified}. */ private boolean cannotBeRacilyClean; - private FileSnapshot(long read, long modified) { + /** Underlying file-system size in bytes. + * + * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ + private final long size; + + private FileSnapshot(long read, long modified, long size) { this.lastRead = read; this.lastModified = modified; this.cannotBeRacilyClean = notRacyClean(read); + this.size = size; } /** + * Get time of last snapshot update + * * @return time of last snapshot update */ public long lastModified() { @@ -146,6 +171,13 @@ } /** + * @return file size in bytes of last snapshot update + */ + public long size() { + return size; + } + + /** * Check if the path may have been modified since the snapshot was saved. * * @param path @@ -153,7 +185,17 @@ * @return true if the path needs to be read again. */ public boolean isModified(File path) { - return isModified(path.lastModified()); + long currLastModified; + long currSize; + try { + BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); + currLastModified = fileAttributes.lastModifiedTime().toMillis(); + currSize = fileAttributes.size(); + } catch (IOException e) { + currLastModified = path.lastModified(); + currSize = path.length(); + } + return (currSize != UNKNOWN_SIZE && currSize != size) || isModified(currLastModified); } /** @@ -196,6 +238,7 @@ return lastModified == other.lastModified; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { if (other instanceof FileSnapshot) @@ -203,6 +246,7 @@ return false; } + /** {@inheritDoc} */ @Override public int hashCode() { // This is pretty pointless, but override hashCode to ensure that @@ -211,6 +255,7 @@ return (int) lastModified; } + /** {@inheritDoc} */ @Override public String toString() { if (this == DIRTY) @@ -252,12 +297,6 @@ return false; } - // Our lastRead flag may be old, refresh and retry - lastRead = System.currentTimeMillis(); - if (notRacyClean(lastRead)) { - return false; - } - // We last read this path too close to its last observed // modification time. We may have missed a modification. // Scan again, to ensure we still see the same state. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,16 +45,24 @@ import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; -import static org.eclipse.jgit.lib.RefDatabase.ALL; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.text.ParseException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -66,11 +74,18 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.CancelledException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -78,18 +93,22 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; -import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; -import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.internal.storage.reftree.RefTreeNames; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.ReflogReader; +import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -99,17 +118,54 @@ import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.GitDateParser; import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * A garbage collector for git {@link FileRepository}. Instances of this class - * are not thread-safe. Don't use the same instance from multiple threads. + * A garbage collector for git + * {@link org.eclipse.jgit.internal.storage.file.FileRepository}. Instances of + * this class are not thread-safe. Don't use the same instance from multiple + * threads. * * This class started as a copy of DfsGarbageCollector from Shawn O. Pearce * adapted to FileRepositories. */ public class GC { + private final static Logger LOG = LoggerFactory + .getLogger(GC.class); + private static final String PRUNE_EXPIRE_DEFAULT = "2.weeks.ago"; //$NON-NLS-1$ + private static final String PRUNE_PACK_EXPIRE_DEFAULT = "1.hour.ago"; //$NON-NLS-1$ + + private static final Pattern PATTERN_LOOSE_OBJECT = Pattern + .compile("[0-9a-fA-F]{38}"); //$NON-NLS-1$ + + private static final String PACK_EXT = "." + PackExt.PACK.getExtension();//$NON-NLS-1$ + + private static final String BITMAP_EXT = "." //$NON-NLS-1$ + + PackExt.BITMAP_INDEX.getExtension(); + + private static final String INDEX_EXT = "." + PackExt.INDEX.getExtension(); //$NON-NLS-1$ + + private static final int DEFAULT_AUTOPACKLIMIT = 50; + + private static final int DEFAULT_AUTOLIMIT = 6700; + + private static volatile ExecutorService executor; + + /** + * Set the executor for running auto-gc in the background. If no executor is + * set JGit's own WorkQueue will be used. + * + * @param e + * the executor to be used for running auto-gc + * @since 4.8 + */ + public static void setExecutor(ExecutorService e) { + executor = e; + } + private final FileRepository repo; private ProgressMonitor pm; @@ -118,6 +174,10 @@ private Date expire; + private long packExpireAgeMillis = -1; + + private Date packExpire; + private PackConfig pconfig = null; /** @@ -126,7 +186,7 @@ * difference between the current refs and the refs which existed during * last {@link #repack()}. */ - private Map lastPackedRefs; + private Collection lastPackedRefs; /** * Holds the starting time of the last repack() execution. This is needed in @@ -136,6 +196,16 @@ private long lastRepackTime; /** + * Whether gc should do automatic housekeeping + */ + private boolean automatic; + + /** + * Whether to run gc in a background thread + */ + private boolean background; + + /** * Creates a new garbage collector with default values. An expirationTime of * two weeks and null as progress monitor will be used. * @@ -148,7 +218,8 @@ } /** - * Runs a garbage collector on a {@link FileRepository}. It will + * Runs a garbage collector on a + * {@link org.eclipse.jgit.internal.storage.file.FileRepository}. It will *

    *
  • pack loose references into packed-refs
  • *
  • repack all reachable objects into new pack files and delete the old @@ -156,37 +227,141 @@ *
  • prune all loose objects which are now reachable by packs
  • *
* - * @return the collection of {@link PackFile}'s which are newly created - * @throws IOException - * @throws ParseException + * If {@link #setAuto(boolean)} was set to {@code true} {@code gc} will + * first check whether any housekeeping is required; if not, it exits + * without performing any work. + * + * If {@link #setBackground(boolean)} was set to {@code true} + * {@code collectGarbage} will start the gc in the background, and then + * return immediately. In this case, errors will not be reported except in + * gc.log. + * + * @return the collection of + * {@link org.eclipse.jgit.internal.storage.file.PackFile}'s which + * are newly created + * @throws java.io.IOException + * @throws java.text.ParseException * If the configuration parameter "gc.pruneexpire" couldn't be * parsed */ + // TODO(ms): in 5.0 change signature and return Future> public Collection gc() throws IOException, ParseException { + if (!background) { + return doGc(); + } + final GcLog gcLog = new GcLog(repo); + if (!gcLog.lock()) { + // there is already a background gc running + return Collections.emptyList(); + } + + Callable> gcTask = () -> { + try { + Collection newPacks = doGc(); + if (automatic && tooManyLooseObjects()) { + String message = JGitText.get().gcTooManyUnpruned; + gcLog.write(message); + gcLog.commit(); + } + return newPacks; + } catch (IOException | ParseException e) { + try { + gcLog.write(e.getMessage()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + gcLog.write(sw.toString()); + gcLog.commit(); + } catch (IOException e2) { + e2.addSuppressed(e); + LOG.error(e2.getMessage(), e2); + } + } finally { + gcLog.unlock(); + } + return Collections.emptyList(); + }; + // TODO(ms): in 5.0 change signature and return the Future + executor().submit(gcTask); + return Collections.emptyList(); + } + + private ExecutorService executor() { + return (executor != null) ? executor : WorkQueue.getExecutor(); + } + + private Collection doGc() throws IOException, ParseException { + if (automatic && !needGc()) { + return Collections.emptyList(); + } pm.start(6 /* tasks */); packRefs(); // TODO: implement reflog_expire(pm, repo); Collection newPacks = repack(); - prune(Collections. emptySet()); + prune(Collections.emptySet()); // TODO: implement rerere_gc(pm); return newPacks; } /** + * Loosen objects in a pack file which are not also in the newly-created + * pack files. + * + * @param inserter + * @param reader + * @param pack + * @param existing + * @throws IOException + */ + private void loosen(ObjectDirectoryInserter inserter, ObjectReader reader, PackFile pack, HashSet existing) + throws IOException { + for (PackIndex.MutableEntry entry : pack) { + ObjectId oid = entry.toObjectId(); + if (existing.contains(oid)) { + continue; + } + existing.add(oid); + ObjectLoader loader = reader.open(oid); + inserter.insert(loader.getType(), + loader.getSize(), + loader.openStream(), + true /* create this object even though it's a duplicate */); + } + } + + /** * Delete old pack files. What is 'old' is defined by specifying a set of * old pack files and a set of new pack files. Each pack file contained in - * old pack files but not contained in new pack files will be deleted. If an - * expirationDate is set then pack files which are younger than the - * expirationDate will not be deleted. + * old pack files but not contained in new pack files will be deleted. If + * preserveOldPacks is set, keep a copy of the pack file in the preserve + * directory. If an expirationDate is set then pack files which are younger + * than the expirationDate will not be deleted nor preserved. + *

+ * If we're not immediately expiring loose objects, loosen any objects + * in the old pack files which aren't in the new pack files. * * @param oldPacks * @param newPacks * @throws ParseException + * @throws IOException */ private void deleteOldPacks(Collection oldPacks, - Collection newPacks) throws ParseException { - long expireDate = getExpireDate(); + Collection newPacks) throws ParseException, IOException { + HashSet ids = new HashSet<>(); + for (PackFile pack : newPacks) { + for (PackIndex.MutableEntry entry : pack) { + ids.add(entry.toObjectId()); + } + } + ObjectReader reader = repo.newObjectReader(); + ObjectDirectory dir = repo.getObjectDatabase(); + ObjectDirectoryInserter inserter = dir.newInserter(); + boolean shouldLoosen = !"now".equals(getPruneExpireStr()) && //$NON-NLS-1$ + getExpireDate() < Long.MAX_VALUE; + + prunePreserved(); + long packExpireDate = getPackExpireDate(); oldPackLoop: for (PackFile oldPack : oldPacks) { + checkCancelled(); String oldName = oldPack.getPackName(); // check whether an old pack file is also among the list of new // pack files. Then we must not delete it. @@ -195,17 +370,60 @@ continue oldPackLoop; if (!oldPack.shouldBeKept() - && oldPack.getPackFile().lastModified() < expireDate) { + && repo.getFS().lastModified( + oldPack.getPackFile()) < packExpireDate) { oldPack.close(); + if (shouldLoosen) { + loosen(inserter, reader, oldPack, ids); + } prunePack(oldName); } } - // close the complete object database. Thats my only chance to force + + // close the complete object database. That's my only chance to force // rescanning and to detect that certain pack files are now deleted. repo.getObjectDatabase().close(); } /** + * Deletes old pack file, unless 'preserve-oldpacks' is set, in which case it + * moves the pack file to the preserved directory + * + * @param packFile + * @param packName + * @param ext + * @param deleteOptions + * @throws IOException + */ + private void removeOldPack(File packFile, String packName, PackExt ext, + int deleteOptions) throws IOException { + if (pconfig != null && pconfig.isPreserveOldPacks()) { + File oldPackDir = repo.getObjectDatabase().getPreservedDirectory(); + FileUtils.mkdir(oldPackDir, true); + + String oldPackName = "pack-" + packName + ".old-" + ext.getExtension(); //$NON-NLS-1$ //$NON-NLS-2$ + File oldPackFile = new File(oldPackDir, oldPackName); + FileUtils.rename(packFile, oldPackFile); + } else { + FileUtils.delete(packFile, deleteOptions); + } + } + + /** + * Delete the preserved directory including all pack files within + */ + private void prunePreserved() { + if (pconfig != null && pconfig.isPrunePreserved()) { + try { + FileUtils.delete(repo.getObjectDatabase().getPreservedDirectory(), + FileUtils.RECURSIVE | FileUtils.RETRY | FileUtils.SKIP_MISSING); + } catch (IOException e) { + // Deletion of the preserved pack files failed. Silently return. + } + } + } + + /** * Delete files associated with a single pack file. First try to delete the * ".pack" file because on some platforms the ".pack" file may be locked and * can't be deleted. In such a case it is better to detect this early and @@ -224,7 +442,7 @@ for (PackExt ext : extensions) if (PackExt.PACK.equals(ext)) { File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ - FileUtils.delete(f, deleteOptions); + removeOldPack(f, packName, ext, deleteOptions); break; } // The .pack file has been deleted. Delete as many as the other @@ -233,7 +451,7 @@ for (PackExt ext : extensions) { if (!PackExt.PACK.equals(ext)) { File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ - FileUtils.delete(f, deleteOptions); + removeOldPack(f, packName, ext, deleteOptions); } } } catch (IOException e) { @@ -246,7 +464,7 @@ * which can be found in packs. If certain objects can't be pruned (e.g. * because the filesystem delete operation fails) this is silently ignored. * - * @throws IOException + * @throws java.io.IOException */ public void prunePacked() throws IOException { ObjectDirectory objdb = repo.getObjectDatabase(); @@ -258,6 +476,7 @@ pm.beginTask(JGitText.get().pruneLoosePackedObjects, fanout.length); try { for (String d : fanout) { + checkCancelled(); pm.update(1); if (d.length() != 2) continue; @@ -265,6 +484,7 @@ if (entries == null) continue; for (String e : entries) { + checkCancelled(); if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) continue; ObjectId id; @@ -276,11 +496,13 @@ continue; } boolean found = false; - for (PackFile p : packs) + for (PackFile p : packs) { + checkCancelled(); if (p.hasObject(id)) { found = true; break; } + } if (found) FileUtils.delete(objdb.fileFor(id), FileUtils.RETRY | FileUtils.SKIP_MISSING @@ -300,9 +522,8 @@ * * @param objectsToKeep * a set of objects which should explicitly not be pruned - * - * @throws IOException - * @throws ParseException + * @throws java.io.IOException + * @throws java.text.ParseException * If the configuration parameter "gc.pruneexpire" couldn't be * parsed */ @@ -312,65 +533,77 @@ // Collect all loose objects which are old enough, not referenced from // the index and not in objectsToKeep - Map deletionCandidates = new HashMap(); + Map deletionCandidates = new HashMap<>(); Set indexObjects = null; File objects = repo.getObjectsDirectory(); String[] fanout = objects.list(); - if (fanout != null && fanout.length > 0) { - pm.beginTask(JGitText.get().pruneLooseUnreferencedObjects, - fanout.length); - try { - for (String d : fanout) { - pm.update(1); - if (d.length() != 2) + if (fanout == null || fanout.length == 0) { + return; + } + pm.beginTask(JGitText.get().pruneLooseUnreferencedObjects, + fanout.length); + try { + for (String d : fanout) { + checkCancelled(); + pm.update(1); + if (d.length() != 2) + continue; + File dir = new File(objects, d); + File[] entries = dir.listFiles(); + if (entries == null || entries.length == 0) { + FileUtils.delete(dir, FileUtils.IGNORE_ERRORS); + continue; + } + for (File f : entries) { + checkCancelled(); + String fName = f.getName(); + if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) continue; - File[] entries = new File(objects, d).listFiles(); - if (entries == null) + if (repo.getFS().lastModified(f) >= expireDate) continue; - for (File f : entries) { - String fName = f.getName(); - if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) + try { + ObjectId id = ObjectId.fromString(d + fName); + if (objectsToKeep.contains(id)) continue; - if (f.lastModified() >= expireDate) + if (indexObjects == null) + indexObjects = listNonHEADIndexObjects(); + if (indexObjects.contains(id)) continue; - try { - ObjectId id = ObjectId.fromString(d + fName); - if (objectsToKeep.contains(id)) - continue; - if (indexObjects == null) - indexObjects = listNonHEADIndexObjects(); - if (indexObjects.contains(id)) - continue; - deletionCandidates.put(id, f); - } catch (IllegalArgumentException notAnObject) { - // ignoring the file that does not represent loose - // object - continue; - } + deletionCandidates.put(id, f); + } catch (IllegalArgumentException notAnObject) { + // ignoring the file that does not represent loose + // object } } - } finally { - pm.endTask(); } + } finally { + pm.endTask(); } - if (deletionCandidates.isEmpty()) + + if (deletionCandidates.isEmpty()) { return; + } + + checkCancelled(); // From the set of current refs remove all those which have been handled // during last repack(). Only those refs will survive which have been // added or modified since the last repack. Only these can save existing // loose refs from being pruned. - Map newRefs; + Collection newRefs; if (lastPackedRefs == null || lastPackedRefs.isEmpty()) newRefs = getAllRefs(); else { - newRefs = new HashMap(); - for (Iterator> i = getAllRefs().entrySet() - .iterator(); i.hasNext();) { - Entry newEntry = i.next(); - Ref old = lastPackedRefs.get(newEntry.getKey()); - if (!equals(newEntry.getValue(), old)) - newRefs.put(newEntry.getKey(), newEntry.getValue()); + Map last = new HashMap<>(); + for (Ref r : lastPackedRefs) { + last.put(r.getName(), r); + } + newRefs = new ArrayList<>(); + for (Ref r : getAllRefs()) { + Ref old = last.get(r.getName()); + if (!equals(r, old)) { + newRefs.add(r); + } } } @@ -382,11 +615,14 @@ // leave this method. ObjectWalk w = new ObjectWalk(repo); try { - for (Ref cr : newRefs.values()) + for (Ref cr : newRefs) { + checkCancelled(); w.markStart(w.parseAny(cr.getObjectId())); + } if (lastPackedRefs != null) - for (Ref lpr : lastPackedRefs.values()) + for (Ref lpr : lastPackedRefs) { w.markUninteresting(w.parseAny(lpr.getObjectId())); + } removeReferenced(deletionCandidates, w); } finally { w.dispose(); @@ -403,12 +639,16 @@ // additional reflog entries not handled during last repack() ObjectWalk w = new ObjectWalk(repo); try { - for (Ref ar : getAllRefs().values()) - for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) + for (Ref ar : getAllRefs()) + for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) { + checkCancelled(); w.markStart(w.parseAny(id)); + } if (lastPackedRefs != null) - for (Ref lpr : lastPackedRefs.values()) + for (Ref lpr : lastPackedRefs) { + checkCancelled(); w.markUninteresting(w.parseAny(lpr.getObjectId())); + } removeReferenced(deletionCandidates, w); } finally { w.dispose(); @@ -417,10 +657,24 @@ if (deletionCandidates.isEmpty()) return; + checkCancelled(); + // delete all candidates which have survived: these are unreferenced - // loose objects - for (File f : deletionCandidates.values()) - f.delete(); + // loose objects. Make a last check, though, to avoid deleting objects + // that could have been referenced while the candidates list was being + // built (by an incoming push, for example). + Set touchedFanout = new HashSet<>(); + for (File f : deletionCandidates.values()) { + if (f.lastModified() < expireDate) { + f.delete(); + touchedFanout.add(f.getParentFile()); + } + } + + for (File f : touchedFanout) { + FileUtils.delete(f, + FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS); + } repo.getObjectDatabase().close(); } @@ -429,9 +683,7 @@ long expireDate = Long.MAX_VALUE; if (expire == null && expireAgeMillis == -1) { - String pruneExpireStr = repo.getConfig().getString( - ConfigConstants.CONFIG_GC_SECTION, null, - ConfigConstants.CONFIG_KEY_PRUNEEXPIRE); + String pruneExpireStr = getPruneExpireStr(); if (pruneExpireStr == null) pruneExpireStr = PRUNE_EXPIRE_DEFAULT; expire = GitDateParser.parse(pruneExpireStr, null, SystemReader @@ -445,6 +697,32 @@ return expireDate; } + private String getPruneExpireStr() { + return repo.getConfig().getString( + ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PRUNEEXPIRE); + } + + private long getPackExpireDate() throws ParseException { + long packExpireDate = Long.MAX_VALUE; + + if (packExpire == null && packExpireAgeMillis == -1) { + String prunePackExpireStr = repo.getConfig().getString( + ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PRUNEPACKEXPIRE); + if (prunePackExpireStr == null) + prunePackExpireStr = PRUNE_PACK_EXPIRE_DEFAULT; + packExpire = GitDateParser.parse(prunePackExpireStr, null, + SystemReader.getInstance().getLocale()); + packExpireAgeMillis = -1; + } + if (packExpire != null) + packExpireDate = packExpire.getTime(); + if (packExpireAgeMillis != -1) + packExpireDate = System.currentTimeMillis() - packExpireAgeMillis; + return packExpireDate; + } + /** * Remove all entries from a map which key is the id of an object referenced * by the given ObjectWalk @@ -460,45 +738,46 @@ IncorrectObjectTypeException, IOException { RevObject ro = w.next(); while (ro != null) { - if (id2File.remove(ro.getId()) != null) - if (id2File.isEmpty()) - return; + checkCancelled(); + if (id2File.remove(ro.getId()) != null && id2File.isEmpty()) { + return; + } ro = w.next(); } ro = w.nextObject(); while (ro != null) { - if (id2File.remove(ro.getId()) != null) - if (id2File.isEmpty()) - return; + checkCancelled(); + if (id2File.remove(ro.getId()) != null && id2File.isEmpty()) { + return; + } ro = w.nextObject(); } } private static boolean equals(Ref r1, Ref r2) { - if (r1 == null || r2 == null) + if (r1 == null || r2 == null) { return false; + } if (r1.isSymbolic()) { - if (!r2.isSymbolic()) - return false; - return r1.getTarget().getName().equals(r2.getTarget().getName()); - } else { - if (r2.isSymbolic()) - return false; - return r1.getObjectId().equals(r2.getObjectId()); + return r2.isSymbolic() && r1.getTarget().getName() + .equals(r2.getTarget().getName()); } + return !r2.isSymbolic() + && Objects.equals(r1.getObjectId(), r2.getObjectId()); } /** * Packs all non-symbolic, loose refs into packed-refs. * - * @throws IOException + * @throws java.io.IOException */ public void packRefs() throws IOException { Collection refs = repo.getRefDatabase().getRefs(Constants.R_REFS).values(); - List refsToBePacked = new ArrayList(refs.size()); + List refsToBePacked = new ArrayList<>(refs.size()); pm.beginTask(JGitText.get().packRefs, refs.size()); try { for (Ref ref : refs) { + checkCancelled(); if (!ref.isSymbolic() && ref.getStorage().isLoose()) refsToBePacked.add(ref.getName()); pm.update(1); @@ -518,57 +797,90 @@ * repacked. All old pack files which existed before are deleted. * * @return a collection of the newly created pack files - * @throws IOException + * @throws java.io.IOException * when during reading of refs, index, packfiles, objects, * reflog-entries or during writing to the packfiles - * {@link IOException} occurs + * {@link java.io.IOException} occurs */ public Collection repack() throws IOException { Collection toBeDeleted = repo.getObjectDatabase().getPacks(); long time = System.currentTimeMillis(); - Map refsBefore = getAllRefs(); + Collection refsBefore = getAllRefs(); - Set allHeads = new HashSet(); - Set nonHeads = new HashSet(); - Set tagTargets = new HashSet(); + Set allHeadsAndTags = new HashSet<>(); + Set allHeads = new HashSet<>(); + Set allTags = new HashSet<>(); + Set nonHeads = new HashSet<>(); + Set txnHeads = new HashSet<>(); + Set tagTargets = new HashSet<>(); Set indexObjects = listNonHEADIndexObjects(); + RefDatabase refdb = repo.getRefDatabase(); - for (Ref ref : refsBefore.values()) { + for (Ref ref : refsBefore) { + checkCancelled(); nonHeads.addAll(listRefLogObjects(ref, 0)); - if (ref.isSymbolic() || ref.getObjectId() == null) + if (ref.isSymbolic() || ref.getObjectId() == null) { continue; - if (ref.getName().startsWith(Constants.R_HEADS)) + } + if (isHead(ref)) { allHeads.add(ref.getObjectId()); - else + } else if (isTag(ref)) { + allTags.add(ref.getObjectId()); + } else if (RefTreeNames.isRefTree(refdb, ref.getName())) { + txnHeads.add(ref.getObjectId()); + } else { nonHeads.add(ref.getObjectId()); - if (ref.getPeeledObjectId() != null) + } + if (ref.getPeeledObjectId() != null) { tagTargets.add(ref.getPeeledObjectId()); + } } - List excluded = new LinkedList(); - for (final PackFile f : repo.getObjectDatabase().getPacks()) + List excluded = new LinkedList<>(); + for (final PackFile f : repo.getObjectDatabase().getPacks()) { + checkCancelled(); if (f.shouldBeKept()) - excluded.add(objectIdSet(f.getIndex())); + excluded.add(f.getIndex()); + } + + // Don't exclude tags that are also branch tips + allTags.removeAll(allHeads); + allHeadsAndTags.addAll(allHeads); + allHeadsAndTags.addAll(allTags); - tagTargets.addAll(allHeads); + // Hoist all branch tips and tags earlier in the pack file + tagTargets.addAll(allHeadsAndTags); nonHeads.addAll(indexObjects); - List ret = new ArrayList(2); + // Combine the GC_REST objects into the GC pack if requested + if (pconfig != null && pconfig.getSinglePack()) { + allHeadsAndTags.addAll(nonHeads); + nonHeads.clear(); + } + + List ret = new ArrayList<>(2); PackFile heads = null; - if (!allHeads.isEmpty()) { - heads = writePack(allHeads, Collections. emptySet(), + if (!allHeadsAndTags.isEmpty()) { + heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags, tagTargets, excluded); if (heads != null) { ret.add(heads); - excluded.add(0, objectIdSet(heads.getIndex())); + excluded.add(0, heads.getIndex()); } } if (!nonHeads.isEmpty()) { - PackFile rest = writePack(nonHeads, allHeads, tagTargets, excluded); + PackFile rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE, + tagTargets, excluded); if (rest != null) ret.add(rest); } + if (!txnHeads.isEmpty()) { + PackFile txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE, + null, excluded); + if (txn != null) + ret.add(txn); + } try { deleteOldPacks(toBeDeleted, ret); } catch (ParseException e) { @@ -578,12 +890,135 @@ throw new IOException(e); } prunePacked(); + deleteEmptyRefsFolders(); + deleteOrphans(); + deleteTempPacksIdx(); lastPackedRefs = refsBefore; lastRepackTime = time; return ret; } + private static boolean isHead(Ref ref) { + return ref.getName().startsWith(Constants.R_HEADS); + } + + private static boolean isTag(Ref ref) { + return ref.getName().startsWith(Constants.R_TAGS); + } + + private void deleteEmptyRefsFolders() throws IOException { + Path refs = repo.getDirectory().toPath().resolve(Constants.R_REFS); + // Avoid deleting a folder that was created after the threshold so that concurrent + // operations trying to create a reference are not impacted + Instant threshold = Instant.now().minus(30, ChronoUnit.SECONDS); + try (Stream entries = Files.list(refs)) { + Iterator iterator = entries.iterator(); + while (iterator.hasNext()) { + try (Stream s = Files.list(iterator.next())) { + s.filter(path -> canBeSafelyDeleted(path, threshold)).forEach(this::deleteDir); + } + } + } + } + + private boolean canBeSafelyDeleted(Path path, Instant threshold) { + try { + return Files.getLastModifiedTime(path).toInstant().isBefore(threshold); + } + catch (IOException e) { + LOG.warn(MessageFormat.format( + JGitText.get().cannotAccessLastModifiedForSafeDeletion, + path), e); + return false; + } + } + + private void deleteDir(Path dir) { + try (Stream dirs = Files.walk(dir)) { + dirs.filter(this::isDirectory).sorted(Comparator.reverseOrder()) + .forEach(this::delete); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + } + + private boolean isDirectory(Path p) { + return p.toFile().isDirectory(); + } + + private void delete(Path d) { + try { + Files.delete(d); + } catch (DirectoryNotEmptyException e) { + // Don't log + } catch (IOException e) { + LOG.error(MessageFormat.format(JGitText.get().cannotDeleteFile, d), + e); + } + } + + /** + * Deletes orphans + *

+ * A file is considered an orphan if it is either a "bitmap" or an index + * file, and its corresponding pack file is missing in the list. + *

+ */ + private void deleteOrphans() { + Path packDir = repo.getObjectDatabase().getPackDirectory().toPath(); + List fileNames = null; + try (Stream files = Files.list(packDir)) { + fileNames = files.map(path -> path.getFileName().toString()) + .filter(name -> (name.endsWith(PACK_EXT) + || name.endsWith(BITMAP_EXT) + || name.endsWith(INDEX_EXT))) + .sorted(Collections.reverseOrder()) + .collect(Collectors.toList()); + } catch (IOException e1) { + // ignore + } + if (fileNames == null) { + return; + } + + String base = null; + for (String n : fileNames) { + if (n.endsWith(PACK_EXT)) { + base = n.substring(0, n.lastIndexOf('.')); + } else { + if (base == null || !n.startsWith(base)) { + try { + Files.delete(packDir.resolve(n)); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + } + } + } + } + + private void deleteTempPacksIdx() { + Path packDir = repo.getObjectDatabase().getPackDirectory().toPath(); + Instant threshold = Instant.now().minus(1, ChronoUnit.DAYS); + try (DirectoryStream stream = + Files.newDirectoryStream(packDir, "gc_*_tmp")) { //$NON-NLS-1$ + stream.forEach(t -> { + try { + Instant lastModified = Files.getLastModifiedTime(t) + .toInstant(); + if (lastModified.isBefore(threshold)) { + Files.deleteIfExists(t); + } + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + } + /** * @param ref * the ref which log should be inspected @@ -592,11 +1027,15 @@ * @throws IOException */ private Set listRefLogObjects(Ref ref, long minTime) throws IOException { - List rlEntries = repo.getReflogReader(ref.getName()) + ReflogReader reflogReader = repo.getReflogReader(ref.getName()); + if (reflogReader == null) { + return Collections.emptySet(); + } + List rlEntries = reflogReader .getReverseEntries(); if (rlEntries == null || rlEntries.isEmpty()) - return Collections. emptySet(); - Set ret = new HashSet(); + return Collections.emptySet(); + Set ret = new HashSet<>(); for (ReflogEntry e : rlEntries) { if (e.getWho().getWhen().getTime() < minTime) break; @@ -611,17 +1050,33 @@ } /** - * Returns a map of all refs and additional refs (e.g. FETCH_HEAD, - * MERGE_HEAD, ...) + * Returns a collection of all refs and additional refs. + * + * Additional refs which don't start with "refs/" are not returned because + * they should not save objects from being garbage collected. Examples for + * such references are ORIG_HEAD, MERGE_HEAD, FETCH_HEAD and + * CHERRY_PICK_HEAD. * - * @return a map where names of refs point to ref objects + * @return a collection of refs pointing to live objects. * @throws IOException */ - private Map getAllRefs() throws IOException { - Map ret = repo.getRefDatabase().getRefs(ALL); - for (Ref ref : repo.getRefDatabase().getAdditionalRefs()) - ret.put(ref.getName(), ref); - return ret; + private Collection getAllRefs() throws IOException { + RefDatabase refdb = repo.getRefDatabase(); + Collection refs = refdb.getRefs(RefDatabase.ALL).values(); + List addl = refdb.getAdditionalRefs(); + if (!addl.isEmpty()) { + List all = new ArrayList<>(refs.size() + addl.size()); + all.addAll(refs); + // add additional refs which start with refs/ + for (Ref r : addl) { + checkCancelled(); + if (r.getName().startsWith(Constants.R_REFS)) { + all.add(r); + } + } + return all; + } + return refs; } /** @@ -635,10 +1090,7 @@ */ private Set listNonHEADIndexObjects() throws CorruptObjectException, IOException { - try { - if (repo.getIndexFile() == null) - return Collections.emptySet(); - } catch (NoWorkTreeException e) { + if (repo.isBare()) { return Collections.emptySet(); } try (TreeWalk treeWalk = new TreeWalk(repo)) { @@ -652,9 +1104,10 @@ treeWalk.setFilter(TreeFilter.ANY_DIFF); treeWalk.setRecursive(true); - Set ret = new HashSet(); + Set ret = new HashSet<>(); while (treeWalk.next()) { + checkCancelled(); ObjectId objectId = treeWalk.getObjectId(0); switch (treeWalk.getRawMode(0) & FileMode.TYPE_MASK) { case FileMode.TYPE_MISSING: @@ -679,44 +1132,47 @@ } } - private PackFile writePack(Set want, - Set have, Set tagTargets, - List excludeObjects) throws IOException { + private PackFile writePack(@NonNull Set want, + @NonNull Set have, @NonNull Set tags, + Set tagTargets, List excludeObjects) + throws IOException { + checkCancelled(); File tmpPack = null; - Map tmpExts = new TreeMap( - new Comparator() { - public int compare(PackExt o1, PackExt o2) { - // INDEX entries must be returned last, so the pack - // scanner does pick up the new pack until all the - // PackExt entries have been written. - if (o1 == o2) - return 0; - if (o1 == PackExt.INDEX) - return 1; - if (o2 == PackExt.INDEX) - return -1; - return Integer.signum(o1.hashCode() - o2.hashCode()); - } - - }); + Map tmpExts = new TreeMap<>((o1, o2) -> { + // INDEX entries must be returned last, so the pack + // scanner does pick up the new pack until all the + // PackExt entries have been written. + if (o1 == o2) { + return 0; + } + if (o1 == PackExt.INDEX) { + return 1; + } + if (o2 == PackExt.INDEX) { + return -1; + } + return Integer.signum(o1.hashCode() - o2.hashCode()); + }); try (PackWriter pw = new PackWriter( (pconfig == null) ? new PackConfig(repo) : pconfig, repo.newObjectReader())) { // prepare the PackWriter pw.setDeltaBaseAsOffset(true); pw.setReuseDeltaCommits(false); - if (tagTargets != null) + if (tagTargets != null) { pw.setTagTargets(tagTargets); + } if (excludeObjects != null) for (ObjectIdSet idx : excludeObjects) pw.excludeObjects(idx); - pw.preparePack(pm, want, have); + pw.preparePack(pm, want, have, PackWriter.NONE, tags); if (pw.getObjectCount() == 0) return null; + checkCancelled(); // create temporary files String id = pw.computeName().getName(); - File packdir = new File(repo.getObjectsDirectory(), "pack"); //$NON-NLS-1$ + File packdir = repo.getObjectDatabase().getPackDirectory(); tmpPack = File.createTempFile("gc_", ".pack_tmp", packdir); //$NON-NLS-1$ //$NON-NLS-2$ final String tmpBase = tmpPack.getName() .substring(0, tmpPack.getName().lastIndexOf('.')); @@ -728,27 +1184,21 @@ JGitText.get().cannotCreateIndexfile, tmpIdx.getPath())); // write the packfile - FileOutputStream fos = new FileOutputStream(tmpPack); - FileChannel channel = fos.getChannel(); - OutputStream channelStream = Channels.newOutputStream(channel); - try { + try (FileOutputStream fos = new FileOutputStream(tmpPack); + FileChannel channel = fos.getChannel(); + OutputStream channelStream = Channels + .newOutputStream(channel)) { pw.writePack(pm, pm, channelStream); - } finally { channel.force(true); - channelStream.close(); - fos.close(); } // write the packindex - fos = new FileOutputStream(tmpIdx); - FileChannel idxChannel = fos.getChannel(); - OutputStream idxStream = Channels.newOutputStream(idxChannel); - try { + try (FileOutputStream fos = new FileOutputStream(tmpIdx); + FileChannel idxChannel = fos.getChannel(); + OutputStream idxStream = Channels + .newOutputStream(idxChannel)) { pw.writeIndex(idxStream); - } finally { idxChannel.force(true); - idxStream.close(); - fos.close(); } if (pw.prepareBitmapIndex(pm)) { @@ -760,65 +1210,47 @@ JGitText.get().cannotCreateIndexfile, tmpBitmapIdx.getPath())); - fos = new FileOutputStream(tmpBitmapIdx); - idxChannel = fos.getChannel(); - idxStream = Channels.newOutputStream(idxChannel); - try { + try (FileOutputStream fos = new FileOutputStream(tmpBitmapIdx); + FileChannel idxChannel = fos.getChannel(); + OutputStream idxStream = Channels + .newOutputStream(idxChannel)) { pw.writeBitmapIndex(idxStream); - } finally { idxChannel.force(true); - idxStream.close(); - fos.close(); } } // rename the temporary files to real files File realPack = nameFor(id, ".pack"); //$NON-NLS-1$ - // if the packfile already exists (because we are rewriting a - // packfile for the same set of objects maybe with different - // PackConfig) then make sure we get rid of all handles on the file. - // Windows will not allow for rename otherwise. - if (realPack.exists()) - for (PackFile p : repo.getObjectDatabase().getPacks()) - if (realPack.getPath().equals(p.getPackFile().getPath())) { - p.close(); - break; - } + repo.getObjectDatabase().closeAllPackHandles(realPack); tmpPack.setReadOnly(); - boolean delete = true; - try { - FileUtils.rename(tmpPack, realPack); - delete = false; - for (Map.Entry tmpEntry : tmpExts.entrySet()) { - File tmpExt = tmpEntry.getValue(); - tmpExt.setReadOnly(); - File realExt = nameFor( - id, "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$ - try { - FileUtils.rename(tmpExt, realExt); - } catch (IOException e) { - File newExt = new File(realExt.getParentFile(), - realExt.getName() + ".new"); //$NON-NLS-1$ - if (!tmpExt.renameTo(newExt)) - newExt = tmpExt; - throw new IOException(MessageFormat.format( - JGitText.get().panicCantRenameIndexFile, newExt, - realExt)); - } - } + FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE); + for (Map.Entry tmpEntry : tmpExts.entrySet()) { + File tmpExt = tmpEntry.getValue(); + tmpExt.setReadOnly(); - } finally { - if (delete) { - if (tmpPack.exists()) - tmpPack.delete(); - for (File tmpExt : tmpExts.values()) { - if (tmpExt.exists()) - tmpExt.delete(); + File realExt = nameFor(id, + "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$ + try { + FileUtils.rename(tmpExt, realExt, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + File newExt = new File(realExt.getParentFile(), + realExt.getName() + ".new"); //$NON-NLS-1$ + try { + FileUtils.rename(tmpExt, newExt, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e2) { + newExt = tmpExt; + e = e2; } + throw new IOException(MessageFormat.format( + JGitText.get().panicCantRenameIndexFile, newExt, + realExt), e); } } + return repo.getObjectDatabase().openPack(realPack); } finally { if (tmpPack != null && tmpPack.exists()) @@ -831,15 +1263,21 @@ } private File nameFor(String name, String ext) { - File packdir = new File(repo.getObjectsDirectory(), "pack"); //$NON-NLS-1$ + File packdir = repo.getObjectDatabase().getPackDirectory(); return new File(packdir, "pack-" + name + ext); //$NON-NLS-1$ } + private void checkCancelled() throws CancelledException { + if (pm.isCancelled() || Thread.currentThread().isInterrupted()) { + throw new CancelledException(JGitText.get().operationCanceled); + } + } + /** * A class holding statistical data for a FileRepository regarding how many * objects are stored as loose or packed objects */ - public class RepoStatistics { + public static class RepoStatistics { /** * The number of objects stored in pack files. If the same object is * stored in multiple pack files then it is counted as often as it @@ -877,6 +1315,12 @@ */ public long numberOfPackedRefs; + /** + * The number of bitmaps in the bitmap indices. + */ + public long numberOfBitmaps; + + @Override public String toString() { final StringBuilder b = new StringBuilder(); b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$ @@ -886,16 +1330,16 @@ b.append(", numberOfPackedRefs=").append(numberOfPackedRefs); //$NON-NLS-1$ b.append(", sizeOfLooseObjects=").append(sizeOfLooseObjects); //$NON-NLS-1$ b.append(", sizeOfPackedObjects=").append(sizeOfPackedObjects); //$NON-NLS-1$ + b.append(", numberOfBitmaps=").append(numberOfBitmaps); //$NON-NLS-1$ return b.toString(); } } /** - * Returns the number of objects stored in pack files. If an object is - * contained in multiple pack files it is counted as often as it occurs. + * Returns information about objects and pack files for a FileRepository. * - * @return the number of objects stored in pack files - * @throws IOException + * @return information about objects and pack files for a FileRepository + * @throws java.io.IOException */ public RepoStatistics getStatistics() throws IOException { RepoStatistics ret = new RepoStatistics(); @@ -904,6 +1348,8 @@ ret.numberOfPackedObjects += f.getIndex().getObjectCount(); ret.numberOfPackFiles++; ret.sizeOfPackedObjects += f.getPackFile().length(); + if (f.getBitmapIndex() != null) + ret.numberOfBitmaps += f.getBitmapIndex().getBitmapCount(); } File objDir = repo.getObjectsDirectory(); String[] fanout = objDir.list(); @@ -938,7 +1384,7 @@ /** * Set the progress monitor used for garbage collection methods. * - * @param pm + * @param pm a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return this */ public GC setProgressMonitor(ProgressMonitor pm) { @@ -961,13 +1407,27 @@ } /** + * During gc() or prune() packfiles which are created or modified in the + * last packExpireAgeMillis milliseconds will not be deleted. + * Only older packfiles may be deleted. If set to 0 then every packfile is a + * candidate for deletion. + * + * @param packExpireAgeMillis + * minimal age of packfiles to be deleted in milliseconds. + */ + public void setPackExpireAgeMillis(long packExpireAgeMillis) { + this.packExpireAgeMillis = packExpireAgeMillis; + expire = null; + } + + /** * Set the PackConfig used when (re-)writing packfiles. This allows to * influence how packs are written and to implement something similar to * "git gc --aggressive" * - * @since 3.6 * @param pconfig - * the {@link PackConfig} used when writing packs + * the {@link org.eclipse.jgit.storage.pack.PackConfig} used when + * writing packs */ public void setPackConfig(PackConfig pconfig) { this.pconfig = pconfig; @@ -990,11 +1450,135 @@ expireAgeMillis = -1; } - private static ObjectIdSet objectIdSet(final PackIndex idx) { - return new ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return idx.hasObject(objectId); + /** + * During gc() or prune() packfiles which are created or modified after or + * at packExpire will not be deleted. Only older packfiles may + * be deleted. If set to null then every packfile is a candidate for + * deletion. + * + * @param packExpire + * instant in time which defines packfile expiration + */ + public void setPackExpire(Date packExpire) { + this.packExpire = packExpire; + packExpireAgeMillis = -1; + } + + /** + * Set the {@code gc --auto} option. + * + * With this option, gc checks whether any housekeeping is required; if not, + * it exits without performing any work. Some JGit commands run + * {@code gc --auto} after performing operations that could create many + * loose objects. + *

+ * Housekeeping is required if there are too many loose objects or too many + * packs in the repository. If the number of loose objects exceeds the value + * of the gc.auto option JGit GC consolidates all existing packs into a + * single pack (equivalent to {@code -A} option), whereas git-core would + * combine all loose objects into a single pack using {@code repack -d -l}. + * Setting the value of {@code gc.auto} to 0 disables automatic packing of + * loose objects. + *

+ * If the number of packs exceeds the value of {@code gc.autoPackLimit}, + * then existing packs (except those marked with a .keep file) are + * consolidated into a single pack by using the {@code -A} option of repack. + * Setting {@code gc.autoPackLimit} to 0 disables automatic consolidation of + * packs. + *

+ * Like git the following jgit commands run auto gc: + *

    + *
  • fetch
  • + *
  • merge
  • + *
  • rebase
  • + *
  • receive-pack
  • + *
+ * The auto gc for receive-pack can be suppressed by setting the config + * option {@code receive.autogc = false} + * + * @param auto + * defines whether gc should do automatic housekeeping + */ + public void setAuto(boolean auto) { + this.automatic = auto; + } + + /** + * @param background + * whether to run the gc in a background thread. + */ + void setBackground(boolean background) { + this.background = background; + } + + private boolean needGc() { + if (tooManyPacks()) { + addRepackAllOption(); + } else { + return tooManyLooseObjects(); + } + // TODO run pre-auto-gc hook, if it fails return false + return true; + } + + private void addRepackAllOption() { + // TODO: if JGit GC is enhanced to support repack's option -l this + // method needs to be implemented + } + + /** + * @return {@code true} if number of packs > gc.autopacklimit (default 50) + */ + boolean tooManyPacks() { + int autopacklimit = repo.getConfig().getInt( + ConfigConstants.CONFIG_GC_SECTION, + ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, + DEFAULT_AUTOPACKLIMIT); + if (autopacklimit <= 0) { + return false; + } + // JGit always creates two packfiles, one for the objects reachable from + // branches, and another one for the rest + return repo.getObjectDatabase().getPacks().size() > (autopacklimit + 1); + } + + /** + * Quickly estimate number of loose objects, SHA1 is distributed evenly so + * counting objects in one directory (bucket 17) is sufficient + * + * @return {@code true} if number of loose objects > gc.auto (default 6700) + */ + boolean tooManyLooseObjects() { + int auto = getLooseObjectLimit(); + if (auto <= 0) { + return false; + } + int n = 0; + int threshold = (auto + 255) / 256; + Path dir = repo.getObjectsDirectory().toPath().resolve("17"); //$NON-NLS-1$ + if (!dir.toFile().exists()) { + return false; + } + try (DirectoryStream stream = Files.newDirectoryStream(dir, file -> { + Path fileName = file.getFileName(); + return file.toFile().isFile() && fileName != null + && PATTERN_LOOSE_OBJECT.matcher(fileName.toString()) + .matches(); + })) { + for (Iterator iter = stream.iterator(); iter.hasNext(); iter + .next()) { + if (++n > threshold) { + return true; + } } - }; + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + return false; + } + + private int getLooseObjectLimit() { + return repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION, + ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 Two Sigma Open Source + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.attribute.FileTime; +import java.text.ParseException; +import java.time.Instant; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.GitDateParser; +import org.eclipse.jgit.util.SystemReader; + +/** + * This class manages the gc.log file for a {@link FileRepository}. + */ +class GcLog { + private final FileRepository repo; + + private final File logFile; + + private final LockFile lock; + + private Instant gcLogExpire; + + private static final String LOG_EXPIRY_DEFAULT = "1.day.ago"; //$NON-NLS-1$ + + private boolean nonEmpty = false; + + /** + * Construct a GcLog object for a {@link FileRepository} + * + * @param repo + * the repository + */ + GcLog(FileRepository repo) { + this.repo = repo; + logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$ + lock = new LockFile(logFile); + } + + private Instant getLogExpiry() throws ParseException { + if (gcLogExpire == null) { + String logExpiryStr = repo.getConfig().getString( + ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_LOGEXPIRY); + if (logExpiryStr == null) { + logExpiryStr = LOG_EXPIRY_DEFAULT; + } + gcLogExpire = GitDateParser.parse(logExpiryStr, null, + SystemReader.getInstance().getLocale()).toInstant(); + } + return gcLogExpire; + } + + private boolean autoGcBlockedByOldLockFile() { + try { + FileTime lastModified = Files.getLastModifiedTime(FileUtils.toPath(logFile)); + if (lastModified.toInstant().compareTo(getLogExpiry()) > 0) { + // There is an existing log file, which is too recent to ignore + return true; + } + } catch (NoSuchFileException e) { + // No existing log file, OK. + } catch (IOException | ParseException e) { + throw new JGitInternalException(e.getMessage(), e); + } + return false; + } + + /** + * Lock the GC log file for updates + * + * @return {@code true} if we hold the lock + */ + boolean lock() { + try { + if (!lock.lock()) { + return false; + } + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + if (autoGcBlockedByOldLockFile()) { + lock.unlock(); + return false; + } + return true; + } + + /** + * Unlock (roll back) the GC log lock + */ + void unlock() { + lock.unlock(); + } + + /** + * Commit changes to the gc log, if there have been any writes. Otherwise, + * just unlock and delete the existing file (if any) + * + * @return true if committing (or unlocking/deleting) succeeds. + */ + boolean commit() { + if (nonEmpty) { + return lock.commit(); + } else { + logFile.delete(); + lock.unlock(); + return true; + } + } + + /** + * Write to the pending gc log. Content will be committed upon a call to + * commit() + * + * @param content + * The content to write + * @throws IOException + */ + void write(String content) throws IOException { + if (content.length() > 0) { + nonEmpty = true; + } + lock.write(content.getBytes(UTF_8)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; + +/** + * Attribute node loaded from global system-wide file. + */ +public class GlobalAttributesNode extends AttributesNode { + final Repository repository; + + /** + * Constructor for GlobalAttributesNode. + * + * @param repository + * the {@link org.eclipse.jgit.lib.Repository}. + */ + public GlobalAttributesNode(Repository repository) { + this.repository = repository; + } + + /** + * Load the attributes node + * + * @return the attributes node + * @throws java.io.IOException + */ + public AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + String path = repository.getConfig().get(CoreConfig.KEY) + .getAttributesFile(); + if (path != null) { + File attributesFile; + if (path.startsWith("~/")) { //$NON-NLS-1$ + attributesFile = fs.resolve(fs.userHome(), + path.substring(2)); + } else { + attributesFile = fs.resolve(null, path); + } + FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile); + } + return r.getRules().isEmpty() ? null : r; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; + +/** + * Attribute node loaded from the $GIT_DIR/info/attributes file. + */ +public class InfoAttributesNode extends AttributesNode { + final Repository repository; + + /** + * Constructor for InfoAttributesNode. + * + * @param repository + * the {@link org.eclipse.jgit.lib.Repository}. + */ + public InfoAttributesNode(Repository repository) { + this.repository = repository; + } + + /** + * Load the attributes node + * + * @return the attributes node + * @throws java.io.IOException + */ + public AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + + File attributes = fs.resolve(repository.getDirectory(), + Constants.INFO_ATTRIBUTES); + FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes); + + return r.getRules().isEmpty() ? null : r; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,32 +77,35 @@ this.db = db; } + /** {@inheritDoc} */ @Override public int getType() { return type; } + /** {@inheritDoc} */ @Override public long getSize() { return size; } + /** {@inheritDoc} */ @Override public boolean isLarge() { return true; } + /** {@inheritDoc} */ @Override public byte[] getCachedBytes() throws LargeObjectException { try { throw new LargeObjectException(getObjectId()); } catch (IOException cannotObtainId) { - LargeObjectException err = new LargeObjectException(); - err.initCause(cannotObtainId); - throw err; + throw new LargeObjectException(cannotObtainId); } } + /** {@inheritDoc} */ @Override public ObjectStream openStream() throws MissingObjectException, IOException { WindowCursor wc = new WindowCursor(db); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,109 @@ +/* + * 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.internal.storage.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; + +/** + * Lazily loads a set of ObjectIds, one per line. + */ +public class LazyObjectIdSetFile implements ObjectIdSet { + private final File src; + private ObjectIdOwnerMap set; + + /** + * Create a new lazy set from a file. + * + * @param src + * the source file. + */ + public LazyObjectIdSetFile(File src) { + this.src = src; + } + + /** {@inheritDoc} */ + @Override + public boolean contains(AnyObjectId objectId) { + if (set == null) { + set = load(); + } + return set.contains(objectId); + } + + private ObjectIdOwnerMap load() { + ObjectIdOwnerMap r = new ObjectIdOwnerMap<>(); + try (FileInputStream fin = new FileInputStream(src); + Reader rin = new InputStreamReader(fin, UTF_8); + BufferedReader br = new BufferedReader(rin)) { + MutableObjectId id = new MutableObjectId(); + for (String line; (line = br.readLine()) != null;) { + id.fromString(line); + if (!r.contains(id)) { + r.add(new Entry(id)); + } + } + } catch (IOException e) { + // Ignore IO errors accessing the lazy set. + } + return r; + } + + static class Entry extends ObjectIdOwnerMap.Entry { + Entry(AnyObjectId id) { + super(id); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,6 +71,7 @@ this.packs = packs.toArray(new PackFile[packs.size()]); } + /** {@inheritDoc} */ @Override public long getObjectCount() throws IOException { long cnt = 0; @@ -85,6 +86,7 @@ pack.copyPackAsIs(out, wc); } + /** {@inheritDoc} */ @Override public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) { try { @@ -118,7 +120,7 @@ } private String getPackFilePath(String packName) { - final File packDir = new File(odb.getDirectory(), "pack"); //$NON-NLS-1$ + final File packDir = odb.getPackDirectory(); return new File(packDir, "pack-" + packName + ".pack").getPath(); //$NON-NLS-1$ //$NON-NLS-2$ } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java 2019-09-03 12:37:49.000000000 +0000 @@ -92,11 +92,13 @@ private ObjectId baseId; + /** {@inheritDoc} */ @Override public int getWeight() { return (int) Math.min(length, Integer.MAX_VALUE); } + /** {@inheritDoc} */ @Override public ObjectId getDeltaBase() { if (baseId == null && getFormat() == PACK_DELTA) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,12 +62,14 @@ super(src, type); } + /** {@inheritDoc} */ @Override protected void clearReuseAsIs() { super.clearReuseAsIs(); pack = null; } + /** {@inheritDoc} */ @Override public void select(StoredObjectRepresentation ref) { LocalObjectRepresentation ptr = (LocalObjectRepresentation) ref; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,8 @@ package org.eclipse.jgit.internal.storage.file; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -54,14 +56,17 @@ import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; -import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.LockToken; import org.eclipse.jgit.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Git style file locking and replacement. @@ -74,16 +79,18 @@ * name. */ public class LockFile { - static final String SUFFIX = ".lock"; //$NON-NLS-1$ + private final static Logger LOG = LoggerFactory.getLogger(LockFile.class); /** * Unlock the given file. *

* This method can be used for recovering from a thrown - * {@link LockFailedException} . This method does not validate that the lock - * is or is not currently held before attempting to unlock it. + * {@link org.eclipse.jgit.errors.LockFailedException} . This method does + * not validate that the lock is or is not currently held before attempting + * to unlock it. * * @param file + * a {@link java.io.File} object. * @return true if unlocked, false if unlocking failed */ public static boolean unlock(final File file) { @@ -104,13 +111,15 @@ * @return lock file */ static File getLockFile(File file) { - return new File(file.getParentFile(), file.getName() + SUFFIX); + return new File(file.getParentFile(), + file.getName() + LOCK_SUFFIX); } /** Filter to skip over active lock files when listing a directory. */ static final FilenameFilter FILTER = new FilenameFilter() { + @Override public boolean accept(File dir, String name) { - return !name.endsWith(SUFFIX); + return !name.endsWith(LOCK_SUFFIX); } }; @@ -120,15 +129,15 @@ private boolean haveLck; - private FileOutputStream os; + FileOutputStream os; private boolean needSnapshot; - private boolean fsync; + boolean fsync; private FileSnapshot commitSnapshot; - private final FS fs; + private LockToken token; /** * Create a new lock for any file. @@ -138,11 +147,25 @@ * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. + * @deprecated use + * {@link org.eclipse.jgit.internal.storage.file.LockFile#LockFile(File)} + * instead */ + @Deprecated public LockFile(final File f, final FS fs) { ref = f; lck = getLockFile(ref); - this.fs = fs; + } + + /** + * Create a new lock for any file. + * + * @param f + * the file that will be locked. + */ + public LockFile(final File f) { + ref = f; + lck = getLockFile(ref); } /** @@ -150,13 +173,14 @@ * * @return true if the lock is now held by the caller; false if it is held * by someone else. - * @throws IOException + * @throws java.io.IOException * the temporary output file could not be created. The caller * does not hold the lock. */ public boolean lock() throws IOException { FileUtils.mkdirs(lck.getParentFile(), true); - if (lck.createNewFile()) { + token = FS.DETECTED.createNewFileAtomic(lck); + if (token.isCreated()) { haveLck = true; try { os = new FileOutputStream(lck); @@ -164,6 +188,8 @@ unlock(); throw ioe; } + } else { + closeToken(); } return haveLck; } @@ -173,7 +199,7 @@ * * @return true if the lock is now held by the caller; false if it is held * by someone else. - * @throws IOException + * @throws java.io.IOException * the temporary output file could not be created. The caller * does not hold the lock. */ @@ -194,20 +220,19 @@ * This method does nothing if the current file does not exist, or exists * but is empty. * - * @throws IOException + * @throws java.io.IOException * the temporary file could not be written, or a read error * occurred while reading from the current file. The lock is * released before throwing the underlying IO exception to the * caller. - * @throws RuntimeException + * @throws java.lang.RuntimeException * the temporary file could not be written. The lock is released * before throwing the underlying exception to the caller. */ public void copyCurrentContent() throws IOException { requireLock(); try { - final FileInputStream fis = new FileInputStream(ref); - try { + try (FileInputStream fis = new FileInputStream(ref)) { if (fsync) { FileChannel in = fis.getChannel(); long pos = 0; @@ -223,10 +248,12 @@ while ((r = fis.read(buf)) >= 0) os.write(buf, 0, r); } - } finally { - fis.close(); } } catch (FileNotFoundException fnfe) { + if (ref.exists()) { + unlock(); + throw fnfe; + } // Don't worry about a file that doesn't exist yet, it // conceptually has no current content to copy. // @@ -248,10 +275,10 @@ * @param id * the id to store in the file. The id will be written in hex, * followed by a sole LF. - * @throws IOException + * @throws java.io.IOException * the temporary file could not be written. The lock is released * before throwing the underlying IO exception to the caller. - * @throws RuntimeException + * @throws java.lang.RuntimeException * the temporary file could not be written. The lock is released * before throwing the underlying exception to the caller. */ @@ -269,10 +296,10 @@ * the bytes to store in the temporary file. No additional bytes * are added, so if the file must end with an LF it must appear * at the end of the byte array. - * @throws IOException + * @throws java.io.IOException * the temporary file could not be written. The lock is released * before throwing the underlying IO exception to the caller. - * @throws RuntimeException + * @throws java.lang.RuntimeException * the temporary file could not be written. The lock is released * before throwing the underlying exception to the caller. */ @@ -358,7 +385,7 @@ }; } - private void requireLock() { + void requireLock() { if (os == null) { unlock(); throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); @@ -378,7 +405,8 @@ } /** - * Request that {@link #commit()} remember the {@link FileSnapshot}. + * Request that {@link #commit()} remember the + * {@link org.eclipse.jgit.internal.storage.file.FileSnapshot}. * * @param on * true if the commit method must remember the FileSnapshot. @@ -404,7 +432,7 @@ * method sleeps until it can force the new lock file's modification date to * be later than the target file. * - * @throws InterruptedException + * @throws java.lang.InterruptedException * the thread was interrupted before the last modified date of * the lock file was different from the last modified date of * the target file. @@ -427,7 +455,7 @@ * @return true if the commit was successful and the file contains the new * data; false if the commit failed and the file remains with the * old data. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * the lock is not held. */ public boolean commit() { @@ -437,56 +465,22 @@ } saveStatInformation(); - if (lck.renameTo(ref)) { + try { + FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE); haveLck = false; + closeToken(); return true; + } catch (IOException e) { + unlock(); + return false; } - if (!ref.exists() || deleteRef()) { - if (renameLock()) { - haveLck = false; - return true; - } - } - unlock(); - return false; - } - - private boolean deleteRef() { - if (!fs.retryFailedLockFileCommit()) - return ref.delete(); - - // File deletion fails on windows if another thread is - // concurrently reading the same file. So try a few times. - // - for (int attempts = 0; attempts < 10; attempts++) { - if (ref.delete()) - return true; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return false; - } - } - return false; } - private boolean renameLock() { - if (!fs.retryFailedLockFileCommit()) - return lck.renameTo(ref); - - // File renaming fails on windows if another thread is - // concurrently reading the same file. So try a few times. - // - for (int attempts = 0; attempts < 10; attempts++) { - if (lck.renameTo(ref)) - return true; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return false; - } + private void closeToken() { + if (token != null) { + token.close(); + token = null; } - return false; } private void saveStatInformation() { @@ -503,7 +497,11 @@ return commitSnapshot.lastModified(); } - /** @return get the {@link FileSnapshot} just before commit. */ + /** + * Get the {@link FileSnapshot} just before commit. + * + * @return get the {@link FileSnapshot} just before commit. + */ public FileSnapshot getCommitSnapshot() { return commitSnapshot; } @@ -527,8 +525,9 @@ if (os != null) { try { os.close(); - } catch (IOException ioe) { - // Ignore this + } catch (IOException e) { + LOG.error(MessageFormat + .format(JGitText.get().unlockLockFileFailed, lck), e); } os = null; } @@ -538,11 +537,15 @@ try { FileUtils.delete(lck, FileUtils.RETRY); } catch (IOException e) { - // couldn't delete the file even after retry. + LOG.error(MessageFormat + .format(JGitText.get().unlockLockFileFailed, lck), e); + } finally { + closeToken(); } } } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,12 +49,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.Channels; -import java.security.DigestOutputStream; -import java.security.MessageDigest; import java.text.MessageFormat; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; @@ -69,6 +68,7 @@ import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.sha1.SHA1; /** Creates loose objects in a {@link ObjectDirectory}. */ class ObjectDirectoryInserter extends ObjectInserter { @@ -83,37 +83,76 @@ config = cfg.get(WriteConfig.KEY); } + /** {@inheritDoc} */ @Override public ObjectId insert(int type, byte[] data, int off, int len) throws IOException { + return insert(type, data, off, len, false); + } + + /** + * Insert a loose object into the database. If createDuplicate is true, + * write the loose object even if we already have it in the loose or packed + * ODB. + * + * @param type + * @param data + * @param off + * @param len + * @param createDuplicate + * @return ObjectId + * @throws IOException + */ + private ObjectId insert( + int type, byte[] data, int off, int len, boolean createDuplicate) + throws IOException { ObjectId id = idFor(type, data, off, len); - if (db.has(id)) { + if (!createDuplicate && db.has(id)) { return id; } else { File tmp = toTemp(type, data, off, len); - return insertOneObject(tmp, id); + return insertOneObject(tmp, id, createDuplicate); } } + /** {@inheritDoc} */ @Override public ObjectId insert(final int type, long len, final InputStream is) throws IOException { + return insert(type, len, is, false); + } + + /** + * Insert a loose object into the database. If createDuplicate is true, + * write the loose object even if we already have it in the loose or packed + * ODB. + * + * @param type + * @param len + * @param is + * @param createDuplicate + * @return ObjectId + * @throws IOException + */ + ObjectId insert(int type, long len, InputStream is, boolean createDuplicate) + throws IOException { if (len <= buffer().length) { byte[] buf = buffer(); int actLen = IO.readFully(is, buf, 0); - return insert(type, buf, 0, actLen); + return insert(type, buf, 0, actLen, createDuplicate); } else { - MessageDigest md = digest(); + SHA1 md = digest(); File tmp = toTemp(md, type, len, is); - ObjectId id = ObjectId.fromRaw(md.digest()); - return insertOneObject(tmp, id); + ObjectId id = md.toObjectId(); + return insertOneObject(tmp, id, createDuplicate); } } - private ObjectId insertOneObject(final File tmp, final ObjectId id) + private ObjectId insertOneObject( + File tmp, ObjectId id, boolean createDuplicate) throws IOException, ObjectWritingException { - switch (db.insertUnpackedObject(tmp, id, false /* no duplicate */)) { + switch (db.insertUnpackedObject(tmp, id, createDuplicate)) { case INSERTED: case EXISTS_PACKED: case EXISTS_LOOSE: @@ -129,21 +168,25 @@ .format(JGitText.get().unableToCreateNewObject, dst)); } + /** {@inheritDoc} */ @Override public PackParser newPackParser(InputStream in) throws IOException { return new ObjectDirectoryPackParser(db, in); } + /** {@inheritDoc} */ @Override public ObjectReader newReader() { - return new WindowCursor(db); + return new WindowCursor(db, this); } + /** {@inheritDoc} */ @Override public void flush() throws IOException { // Do nothing. Loose objects are immediately visible. } + /** {@inheritDoc} */ @Override public void close() { if (deflate != null) { @@ -156,7 +199,7 @@ } @SuppressWarnings("resource" /* java 7 */) - private File toTemp(final MessageDigest md, final int type, long len, + private File toTemp(final SHA1 md, final int type, long len, final InputStream is) throws IOException, FileNotFoundException, Error { boolean delete = true; @@ -168,7 +211,7 @@ if (config.getFSyncObjectFiles()) out = Channels.newOutputStream(fOut.getChannel()); DeflaterOutputStream cOut = compress(out); - DigestOutputStream dOut = new DigestOutputStream(cOut, md); + SHA1OutputStream dOut = new SHA1OutputStream(cOut, md); writeHeader(dOut, type, len); final byte[] buf = buffer(); @@ -248,4 +291,25 @@ return new EOFException(MessageFormat.format( JGitText.get().inputDidntMatchLength, Long.valueOf(missing))); } + + private static class SHA1OutputStream extends FilterOutputStream { + private final SHA1 md; + + SHA1OutputStream(OutputStream out, SHA1 md) { + super(out); + this.md = md; + } + + @Override + public void write(int b) throws IOException { + md.update((byte) b); + out.write(b); + } + + @Override + public void write(byte[] in, int p, int n) throws IOException { + md.update(in, p, n); + out.write(in, p, n); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,6 +52,9 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -61,6 +64,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -87,12 +91,12 @@ import org.slf4j.LoggerFactory; /** - * Traditional file system based {@link ObjectDatabase}. + * Traditional file system based {@link org.eclipse.jgit.lib.ObjectDatabase}. *

* This is the classical object database representation for a Git repository, * where objects are stored loose by hashing them into directories by their - * {@link ObjectId}, or are stored in compressed containers known as - * {@link PackFile}s. + * {@link org.eclipse.jgit.lib.ObjectId}, or are stored in compressed containers + * known as {@link org.eclipse.jgit.internal.storage.file.PackFile}s. *

* Optionally an object database can reference one or more alternates; other * ObjectDatabase instances that are searched in addition to the current @@ -114,6 +118,8 @@ /** Maximum number of candidates offered as resolutions of abbreviation. */ private static final int RESOLVE_ABBREV_LIMIT = 256; + private final AlternateHandle handle = new AlternateHandle(this); + private final Config config; private final File objects; @@ -122,9 +128,9 @@ private final File packDirectory; - private final File alternatesFile; + private final File preservedDirectory; - private final AtomicReference packList; + private final File alternatesFile; private final FS fs; @@ -138,6 +144,8 @@ private Set shallowCommitsIds; + final AtomicReference packList; + /** * Initialize a reference to an on-disk object directory. * @@ -153,7 +161,7 @@ * @param shallowFile * file which contains IDs of shallow commits, null if shallow * commits handling should be turned off - * @throws IOException + * @throws java.io.IOException * an alternate object cannot be opened. */ public ObjectDirectory(final Config cfg, final File dir, @@ -162,13 +170,14 @@ objects = dir; infoDirectory = new File(objects, "info"); //$NON-NLS-1$ packDirectory = new File(objects, "pack"); //$NON-NLS-1$ + preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$ alternatesFile = new File(infoDirectory, "alternates"); //$NON-NLS-1$ - packList = new AtomicReference(NO_PACKS); + packList = new AtomicReference<>(NO_PACKS); unpackedObjectCache = new UnpackedObjectCache(); this.fs = fs; this.shallowFile = shallowFile; - alternates = new AtomicReference(); + alternates = new AtomicReference<>(); if (alternatePaths != null) { AlternateHandle[] alt; @@ -179,18 +188,38 @@ } } - /** - * @return the location of the objects directory. - */ + /** {@inheritDoc} */ + @Override public final File getDirectory() { return objects; } + /** + *

Getter for the field packDirectory.

+ * + * @return the location of the pack directory. + * @since 4.10 + */ + public final File getPackDirectory() { + return packDirectory; + } + + /** + *

Getter for the field preservedDirectory.

+ * + * @return the location of the preserved directory. + */ + public final File getPreservedDirectory() { + return preservedDirectory; + } + + /** {@inheritDoc} */ @Override public boolean exists() { return fs.exists(objects); } + /** {@inheritDoc} */ @Override public void create() throws IOException { FileUtils.mkdirs(objects); @@ -198,11 +227,23 @@ FileUtils.mkdir(packDirectory); } + /** {@inheritDoc} */ @Override public ObjectDirectoryInserter newInserter() { return new ObjectDirectoryInserter(this, config); } + /** + * Create a new inserter that inserts all objects as pack files, not loose + * objects. + * + * @return new inserter. + */ + public PackInserter newPackInserter() { + return new PackInserter(this); + } + + /** {@inheritDoc} */ @Override public void close() { unpackedObjectCache.clear(); @@ -221,13 +262,7 @@ } } - /** - * @return unmodifiable collection of all known pack files local to this - * directory. Most recent packs are presented first. Packs most - * likely to contain more recent objects appear before packs - * containing objects referenced by commits further back in the - * history of the repository. - */ + /** {@inheritDoc} */ @Override public Collection getPacks() { PackList list = packList.get(); @@ -238,15 +273,11 @@ } /** + * {@inheritDoc} + *

* Add a single existing pack to the list of available pack files. - * - * @param pack - * path of the pack file to open. - * @return the pack that was opened and added to the database. - * @throws IOException - * index file could not be opened, read, or is not recognized as - * a Git pack file index. */ + @Override public PackFile openPack(final File pack) throws IOException { final String p = pack.getName(); @@ -271,34 +302,48 @@ return res; } + /** {@inheritDoc} */ @Override public String toString() { return "ObjectDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } + /** {@inheritDoc} */ @Override public boolean has(AnyObjectId objectId) { return unpackedObjectCache.isUnpacked(objectId) - || hasPackedInSelfOrAlternate(objectId) - || hasLooseInSelfOrAlternate(objectId); + || hasPackedInSelfOrAlternate(objectId, null) + || hasLooseInSelfOrAlternate(objectId, null); } - private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId) { - if (hasPackedObject(objectId)) + private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId, + Set skips) { + if (hasPackedObject(objectId)) { return true; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - if (alt.db.hasPackedInSelfOrAlternate(objectId)) - return true; + if (!skips.contains(alt.getId())) { + if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) { + return true; + } + } } return false; } - private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId) { - if (fileFor(objectId).exists()) + private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId, + Set skips) { + if (fileFor(objectId).exists()) { return true; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - if (alt.db.hasLooseInSelfOrAlternate(objectId)) - return true; + if (!skips.contains(alt.getId())) { + if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) { + return true; + } + } } return false; } @@ -315,6 +360,9 @@ // The hasObject call should have only touched the index, // so any failure here indicates the index is unreadable // by this process, and the pack is likewise not readable. + LOG.warn(MessageFormat.format( + JGitText.get().unableToReadPackfile, + p.getPackFile().getAbsolutePath()), e); removePack(p); } } @@ -325,6 +373,12 @@ @Override void resolve(Set matches, AbbreviatedObjectId id) throws IOException { + resolve(matches, id, null); + } + + private void resolve(Set matches, AbbreviatedObjectId id, + Set skips) + throws IOException { // Go through the packs once. If we didn't find any resolutions // scan for new packs and check once more. int oldSize = matches.size(); @@ -334,6 +388,7 @@ for (PackFile p : pList.packs) { try { p.resolve(matches, id, RESOLVE_ABBREV_LIMIT); + p.resetTransientErrorCount(); } catch (IOException e) { handlePackError(e, p); } @@ -360,10 +415,14 @@ } } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - alt.db.resolve(matches, id); - if (matches.size() > RESOLVE_ABBREV_LIMIT) - return; + if (!skips.contains(alt.getId())) { + alt.db.resolve(matches, id, skips); + if (matches.size() > RESOLVE_ABBREV_LIMIT) { + return; + } + } } } @@ -372,37 +431,50 @@ throws IOException { if (unpackedObjectCache.isUnpacked(objectId)) { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } } - ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId); - if (ldr != null) + ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null); + if (ldr != null) { return ldr; - return openLooseFromSelfOrAlternate(curs, objectId); + } + return openLooseFromSelfOrAlternate(curs, objectId, null); } private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs, - AnyObjectId objectId) { + AnyObjectId objectId, Set skips) { ObjectLoader ldr = openPackedObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getId())) { + ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs, - AnyObjectId objectId) throws IOException { + AnyObjectId objectId, Set skips) + throws IOException { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getId())) { + ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } @@ -415,6 +487,7 @@ for (PackFile p : pList.packs) { try { ObjectLoader ldr = p.get(curs, objectId); + p.resetTransientErrorCount(); if (ldr != null) return ldr; } catch (PackMismatchException e) { @@ -431,58 +504,70 @@ return null; } + @Override ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id) throws IOException { - try { - File path = fileFor(id); - FileInputStream in = new FileInputStream(path); - try { - unpackedObjectCache.add(id); - return UnpackedObject.open(in, path, id, curs); - } finally { - in.close(); - } + File path = fileFor(id); + try (FileInputStream in = new FileInputStream(path)) { + unpackedObjectCache.add(id); + return UnpackedObject.open(in, path, id, curs); } catch (FileNotFoundException noFile) { + if (path.exists()) { + throw noFile; + } unpackedObjectCache.remove(id); return null; } } + @Override long getObjectSize(WindowCursor curs, AnyObjectId id) throws IOException { if (unpackedObjectCache.isUnpacked(id)) { long len = getLooseObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } } - long len = getPackedSizeFromSelfOrAlternate(curs, id); - if (0 <= len) + long len = getPackedSizeFromSelfOrAlternate(curs, id, null); + if (0 <= len) { return len; - return getLooseSizeFromSelfOrAlternate(curs, id); + } + return getLooseSizeFromSelfOrAlternate(curs, id, null); } private long getPackedSizeFromSelfOrAlternate(WindowCursor curs, - AnyObjectId id) { + AnyObjectId id, Set skips) { long len = getPackedObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id); - if (0 <= len) - return len; + if (!skips.contains(alt.getId())) { + len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips); + if (0 <= len) { + return len; + } + } } return -1; } private long getLooseSizeFromSelfOrAlternate(WindowCursor curs, - AnyObjectId id) throws IOException { + AnyObjectId id, Set skips) throws IOException { long len = getLooseObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id); - if (0 <= len) - return len; + if (!skips.contains(alt.getId())) { + len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips); + if (0 <= len) { + return len; + } + } } return -1; } @@ -495,6 +580,7 @@ for (PackFile p : pList.packs) { try { long len = p.getObjectSize(curs, id); + p.resetTransientErrorCount(); if (0 <= len) return len; } catch (PackMismatchException e) { @@ -513,15 +599,14 @@ private long getLooseObjectSize(WindowCursor curs, AnyObjectId id) throws IOException { - try { - FileInputStream in = new FileInputStream(fileFor(id)); - try { - unpackedObjectCache.add(id); - return UnpackedObject.getSize(in, id, curs); - } finally { - in.close(); - } + File f = fileFor(id); + try (FileInputStream in = new FileInputStream(f)) { + unpackedObjectCache.add(id); + return UnpackedObject.getSize(in, id, curs); } catch (FileNotFoundException noFile) { + if (f.exists()) { + throw noFile; + } unpackedObjectCache.remove(id); return -1; } @@ -529,12 +614,18 @@ @Override void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { + WindowCursor curs) throws IOException { + selectObjectRepresentation(packer, otp, curs, null); + } + + private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs, Set skips) throws IOException { PackList pList = packList.get(); SEARCH: for (;;) { for (final PackFile p : pList.packs) { try { LocalObjectRepresentation rep = p.representation(curs, otp); + p.resetTransientErrorCount(); if (rep != null) packer.select(otp, rep); } catch (PackMismatchException e) { @@ -549,41 +640,62 @@ break SEARCH; } - for (AlternateHandle h : myAlternates()) - h.db.selectObjectRepresentation(packer, otp, curs); + skips = addMe(skips); + for (AlternateHandle h : myAlternates()) { + if (!skips.contains(h.getId())) { + h.db.selectObjectRepresentation(packer, otp, curs, skips); + } + } } private void handlePackError(IOException e, PackFile p) { String warnTmpl = null; + int transientErrorCount = 0; + String errTmpl = JGitText.get().exceptionWhileReadingPack; if ((e instanceof CorruptObjectException) || (e instanceof PackInvalidException)) { warnTmpl = JGitText.get().corruptPack; + LOG.warn(MessageFormat.format(warnTmpl, + p.getPackFile().getAbsolutePath()), e); // Assume the pack is corrupted, and remove it from the list. removePack(p); } else if (e instanceof FileNotFoundException) { - warnTmpl = JGitText.get().packWasDeleted; - removePack(p); - } else if (FileUtils.isStaleFileHandle(e)) { + if (p.getPackFile().exists()) { + errTmpl = JGitText.get().packInaccessible; + transientErrorCount = p.incrementTransientErrorCount(); + } else { + warnTmpl = JGitText.get().packWasDeleted; + removePack(p); + } + } else if (FileUtils.isStaleFileHandleInCausalChain(e)) { warnTmpl = JGitText.get().packHandleIsStale; removePack(p); + } else { + transientErrorCount = p.incrementTransientErrorCount(); } if (warnTmpl != null) { - if (LOG.isDebugEnabled()) { - LOG.debug(MessageFormat.format(warnTmpl, - p.getPackFile().getAbsolutePath()), e); - } else { - LOG.warn(MessageFormat.format(warnTmpl, - p.getPackFile().getAbsolutePath())); - } + LOG.warn(MessageFormat.format(warnTmpl, + p.getPackFile().getAbsolutePath()), e); } else { - // Don't remove the pack from the list, as the error may be - // transient. - LOG.error(MessageFormat.format( - JGitText.get().exceptionWhileReadingPack, p.getPackFile() - .getAbsolutePath()), e); + if (doLogExponentialBackoff(transientErrorCount)) { + // Don't remove the pack from the list, as the error may be + // transient. + LOG.error(MessageFormat.format(errTmpl, + p.getPackFile().getAbsolutePath(), + Integer.valueOf(transientErrorCount)), e); + } } } + /** + * @param n + * count of consecutive failures + * @return @{code true} if i is a power of 2 + */ + private boolean doLogExponentialBackoff(int n) { + return (n & (n - 1)) == 0; + } + @Override InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id, boolean createDuplicate) throws IOException { @@ -607,10 +719,16 @@ FileUtils.delete(tmp, FileUtils.RETRY); return InsertLooseObjectResult.EXISTS_LOOSE; } - if (tmp.renameTo(dst)) { + try { + Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst), + StandardCopyOption.ATOMIC_MOVE); dst.setReadOnly(); unpackedObjectCache.add(id); return InsertLooseObjectResult.INSERTED; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + // ignore } // Maybe the directory doesn't exist yet as the object @@ -618,10 +736,16 @@ // try the rename first as the directory likely does exist. // FileUtils.mkdir(dst.getParentFile(), true); - if (tmp.renameTo(dst)) { + try { + Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst), + StandardCopyOption.ATOMIC_MOVE); dst.setReadOnly(); unpackedObjectCache.add(id); return InsertLooseObjectResult.INSERTED; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + LOG.debug(e.getMessage(), e); } if (!createDuplicate && has(id)) { @@ -638,7 +762,7 @@ return InsertLooseObjectResult.FAILURE; } - private boolean searchPacksAgain(PackList old) { + boolean searchPacksAgain(PackList old) { // Whether to trust the pack folder's modification time. If set // to false we will always scan the .git/objects/pack folder to // check for new pack files. If set to true (default) we use the @@ -653,6 +777,7 @@ && old != scanPacks(old); } + @Override Config getConfig() { return config; } @@ -669,15 +794,18 @@ if (shallowFileSnapshot == null || shallowFileSnapshot.isModified(shallowFile)) { - shallowCommitsIds = new HashSet(); + shallowCommitsIds = new HashSet<>(); - final BufferedReader reader = open(shallowFile); - try { + try (BufferedReader reader = open(shallowFile)) { String line; - while ((line = reader.readLine()) != null) - shallowCommitsIds.add(ObjectId.fromString(line)); - } finally { - reader.close(); + while ((line = reader.readLine()) != null) { + try { + shallowCommitsIds.add(ObjectId.fromString(line)); + } catch (IllegalArgumentException ex) { + throw new IOException(MessageFormat + .format(JGitText.get().badShallowLine, line)); + } + } } shallowFileSnapshot = FileSnapshot.save(shallowFile); @@ -698,8 +826,6 @@ final PackFile[] oldList = o.packs; final String name = pf.getPackFile().getName(); for (PackFile p : oldList) { - if (PackFile.SORT.compare(pf, p) < 0) - break; if (name.equals(p.getPackFile().getName())) return; } @@ -760,7 +886,7 @@ final Map forReuse = reuseMap(old); final FileSnapshot snapshot = FileSnapshot.save(packDirectory); final Set names = listPackDirectory(); - final List list = new ArrayList(names.size() >> 2); + final List list = new ArrayList<>(names.size() >> 2); boolean foundNew = false; for (final String indexName : names) { // Must match "pack-[0-9a-f]{40}.idx" to be an index. @@ -784,13 +910,14 @@ } final String packName = base + PACK.getExtension(); + final File packFile = new File(packDirectory, packName); final PackFile oldPack = forReuse.remove(packName); - if (oldPack != null) { + if (oldPack != null + && !oldPack.getFileSnapshot().isModified(packFile)) { list.add(oldPack); continue; } - final File packFile = new File(packDirectory, packName); list.add(new PackFile(packFile, extensions)); foundNew = true; } @@ -818,7 +945,7 @@ } private static Map reuseMap(final PackList old) { - final Map forReuse = new HashMap(); + final Map forReuse = new HashMap<>(); for (final PackFile p : old.packs) { if (p.invalid()) { // The pack instance is corrupted, and cannot be safely used @@ -847,7 +974,7 @@ final String[] nameList = packDirectory.list(); if (nameList == null) return Collections.emptySet(); - final Set nameSet = new HashSet(nameList.length << 1); + final Set nameSet = new HashSet<>(nameList.length << 1); for (final String name : nameList) { if (name.startsWith("pack-")) //$NON-NLS-1$ nameSet.add(name); @@ -855,6 +982,21 @@ return nameSet; } + void closeAllPackHandles(File packFile) { + // if the packfile already exists (because we are rewriting a + // packfile for the same set of objects maybe with different + // PackConfig) then make sure we get rid of all handles on the file. + // Windows will not allow for rename otherwise. + if (packFile.exists()) { + for (PackFile p : getPacks()) { + if (packFile.getPath().equals(p.getPackFile().getPath())) { + p.close(); + break; + } + } + } + } + AlternateHandle[] myAlternates() { AlternateHandle[] alt = alternates.get(); if (alt == null) { @@ -873,16 +1015,21 @@ return alt; } + Set addMe(Set skips) { + if (skips == null) { + skips = new HashSet<>(); + } + skips.add(handle.getId()); + return skips; + } + private AlternateHandle[] loadAlternates() throws IOException { - final List l = new ArrayList(4); - final BufferedReader br = open(alternatesFile); - try { + final List l = new ArrayList<>(4); + try (BufferedReader br = open(alternatesFile)) { String line; while ((line = br.readLine()) != null) { l.add(openAlternate(line)); } - } finally { - br.close(); } return l.toArray(new AlternateHandle[l.size()]); } @@ -911,12 +1058,11 @@ } /** + * {@inheritDoc} + *

* Compute the location of a loose object file. - * - * @param objectId - * identity of the loose object to map to the directory. - * @return location of the object, if it were to exist as a loose object. */ + @Override public File fileFor(AnyObjectId objectId) { String n = objectId.name(); String d = n.substring(0, 2); @@ -924,7 +1070,7 @@ return new File(new File(getDirectory(), d), f); } - private static final class PackList { + static final class PackList { /** State just before reading the pack directory. */ final FileSnapshot snapshot; @@ -938,6 +1084,38 @@ } static class AlternateHandle { + static class Id { + String alternateId; + + public Id(File object) { + try { + this.alternateId = object.getCanonicalPath(); + } catch (Exception e) { + alternateId = null; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof Id)) { + return false; + } + Id aId = (Id) o; + return Objects.equals(alternateId, aId.alternateId); + } + + @Override + public int hashCode() { + if (alternateId == null) { + return 1; + } + return alternateId.hashCode(); + } + } + final ObjectDirectory db; AlternateHandle(ObjectDirectory db) { @@ -947,6 +1125,10 @@ void close() { db.close(); } + + public Id getId(){ + return db.getAlternateId(); + } } static class AlternateRepository extends AlternateHandle { @@ -957,11 +1139,13 @@ repository = r; } + @Override void close() { repository.close(); } } + /** {@inheritDoc} */ @Override public ObjectDatabase newCachedDatabase() { return newCachedFileObjectDatabase(); @@ -970,4 +1154,8 @@ CachedObjectDirectory newCachedFileObjectDatabase() { return new CachedObjectDirectory(this); } + + AlternateHandle.Id getAlternateId() { + return new AlternateHandle.Id(objects); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.text.MessageFormat; import java.util.Arrays; @@ -63,7 +64,6 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackedObjectInfo; @@ -71,10 +71,11 @@ import org.eclipse.jgit.util.NB; /** - * Consumes a pack stream and stores as a pack file in {@link ObjectDirectory}. + * Consumes a pack stream and stores as a pack file in + * {@link org.eclipse.jgit.internal.storage.file.ObjectDirectory}. *

* To obtain an instance of a parser, applications should use - * {@link ObjectInserter#newPackParser(InputStream)}. + * {@link org.eclipse.jgit.lib.ObjectInserter#newPackParser(InputStream)}. */ public class ObjectDirectoryPackParser extends PackParser { private final FileObjectDatabase db; @@ -157,7 +158,7 @@ } /** - * Get the imported {@link PackFile}. + * Get the imported {@link org.eclipse.jgit.internal.storage.file.PackFile}. *

* This method is supplied only to support testing; applications shouldn't * be using it directly to access the imported data. @@ -168,6 +169,7 @@ return newPack; } + /** {@inheritDoc} */ @Override public long getPackSize() { if (newPack == null) @@ -183,6 +185,7 @@ return size; } + /** {@inheritDoc} */ @Override public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving) throws IOException { @@ -217,34 +220,40 @@ } } + /** {@inheritDoc} */ @Override protected void onPackHeader(long objectCount) throws IOException { // Ignored, the count is not required. } + /** {@inheritDoc} */ @Override protected void onBeginWholeObject(long streamPosition, int type, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected void onEndWholeObject(PackedObjectInfo info) throws IOException { info.setCRC((int) crc.getValue()); } + /** {@inheritDoc} */ @Override protected void onBeginOfsDelta(long streamPosition, long baseStreamPosition, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId, long inflatedSize) throws IOException { crc.reset(); } + /** {@inheritDoc} */ @Override protected UnresolvedDelta onEndDelta() throws IOException { UnresolvedDelta delta = new UnresolvedDelta(); @@ -252,30 +261,35 @@ return delta; } + /** {@inheritDoc} */ @Override protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode, byte[] data) throws IOException { // ObjectDirectory ignores this event. } + /** {@inheritDoc} */ @Override protected void onObjectHeader(Source src, byte[] raw, int pos, int len) throws IOException { crc.update(raw, pos, len); } + /** {@inheritDoc} */ @Override protected void onObjectData(Source src, byte[] raw, int pos, int len) throws IOException { crc.update(raw, pos, len); } + /** {@inheritDoc} */ @Override protected void onStoreStream(byte[] raw, int pos, int len) throws IOException { out.write(raw, pos, len); } + /** {@inheritDoc} */ @Override protected void onPackFooter(byte[] hash) throws IOException { packEnd = out.getFilePointer(); @@ -284,6 +298,7 @@ packHash = hash; } + /** {@inheritDoc} */ @Override protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, ObjectTypeAndSize info) throws IOException { @@ -292,6 +307,7 @@ return readObjectHeader(info); } + /** {@inheritDoc} */ @Override protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, ObjectTypeAndSize info) throws IOException { @@ -300,11 +316,13 @@ return readObjectHeader(info); } + /** {@inheritDoc} */ @Override protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException { return out.read(dst, pos, cnt); } + /** {@inheritDoc} */ @Override protected boolean checkCRC(int oldCRC) { return oldCRC == (int) crc.getValue(); @@ -322,6 +340,7 @@ tmpPack.deleteOnExit(); } + /** {@inheritDoc} */ @Override protected boolean onAppendBase(final int typeCode, final byte[] data, final PackedObjectInfo info) throws IOException { @@ -364,6 +383,7 @@ return true; } + /** {@inheritDoc} */ @Override protected void onEndThinPack() throws IOException { final byte[] buf = buffer(); @@ -476,20 +496,25 @@ } } - if (!tmpPack.renameTo(finalPack)) { + try { + FileUtils.rename(tmpPack, finalPack, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { cleanupTemporaryFiles(); keep.unlock(); throw new IOException(MessageFormat.format( - JGitText.get().cannotMovePackTo, finalPack)); + JGitText.get().cannotMovePackTo, finalPack), e); } - if (!tmpIdx.renameTo(finalIdx)) { + try { + FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { cleanupTemporaryFiles(); keep.unlock(); if (!finalPack.delete()) finalPack.deleteOnExit(); throw new IOException(MessageFormat.format( - JGitText.get().cannotMoveIndexTo, finalIdx)); + JGitText.get().cannotMoveIndexTo, finalIdx), e); } try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,8 +50,6 @@ import java.util.List; import java.util.NoSuchElementException; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap; import org.eclipse.jgit.internal.storage.pack.ObjectToPack; @@ -63,8 +61,11 @@ import org.eclipse.jgit.lib.ObjectIdOwnerMap; import org.eclipse.jgit.util.BlockList; +import com.googlecode.javaewah.EWAHCompressedBitmap; + /** - * Helper for constructing {@link PackBitmapIndex}es. + * Helper for constructing + * {@link org.eclipse.jgit.internal.storage.file.PackBitmapIndex}es. */ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { private static final int MAX_XOR_OFFSET_SEARCH = 10; @@ -74,10 +75,10 @@ private final EWAHCompressedBitmap blobs; private final EWAHCompressedBitmap tags; private final BlockList byOffset; - private final BlockList - byAddOrder = new BlockList(); - private final ObjectIdOwnerMap - positionEntries = new ObjectIdOwnerMap(); + final BlockList + byAddOrder = new BlockList<>(); + final ObjectIdOwnerMap + positionEntries = new ObjectIdOwnerMap<>(); /** * Creates a PackBitmapIndex used for building the contents of an index @@ -133,6 +134,7 @@ positionEntries.add(new PositionEntry(entries.get(i), i)); } Collections.sort(entries, new Comparator() { + @Override public int compare(ObjectToPack a, ObjectToPack b) { return Long.signum(a.getOffset() - b.getOffset()); } @@ -144,7 +146,11 @@ } } - /** @return set of objects included in the pack. */ + /** + * Get set of objects included in the pack. + * + * @return set of objects included in the pack. + */ public ObjectIdOwnerMap getObjectSet() { ObjectIdOwnerMap r = new ObjectIdOwnerMap<>(); for (PositionEntry e : byOffset) { @@ -196,6 +202,7 @@ byAddOrder.add(result); } + /** {@inheritDoc} */ @Override public EWAHCompressedBitmap ofObjectType( EWAHCompressedBitmap bitmap, int type) { @@ -212,6 +219,7 @@ throw new IllegalArgumentException(); } + /** {@inheritDoc} */ @Override public int findPosition(AnyObjectId objectId) { PositionEntry entry = positionEntries.get(objectId); @@ -220,6 +228,7 @@ return entry.offsetPosition; } + /** {@inheritDoc} */ @Override public ObjectId getObject(int position) throws IllegalArgumentException { ObjectId objectId = byOffset.get(position); @@ -228,60 +237,91 @@ return objectId; } - /** @return the commit object bitmap. */ + /** + * Get the commit object bitmap. + * + * @return the commit object bitmap. + */ public EWAHCompressedBitmap getCommits() { return commits; } - /** @return the tree object bitmap. */ + /** + * Get the tree object bitmap. + * + * @return the tree object bitmap. + */ public EWAHCompressedBitmap getTrees() { return trees; } - /** @return the blob object bitmap. */ + /** + * Get the blob object bitmap. + * + * @return the blob object bitmap. + */ public EWAHCompressedBitmap getBlobs() { return blobs; } - /** @return the tag object bitmap. */ + /** + * Get the tag object bitmap. + * + * @return the tag object bitmap. + */ public EWAHCompressedBitmap getTags() { return tags; } - /** @return the index storage options. */ + /** + * Get the index storage options. + * + * @return the index storage options. + */ public int getOptions() { return PackBitmapIndexV1.OPT_FULL; } - /** @return the number of bitmaps. */ + /** {@inheritDoc} */ + @Override public int getBitmapCount() { return getBitmaps().size(); } - /** Removes all the bitmaps entries added. */ + /** + * Remove all the bitmaps entries added. + */ public void clearBitmaps() { byAddOrder.clear(); getBitmaps().clear(); } + /** {@inheritDoc} */ @Override public int getObjectCount() { return byOffset.size(); } - /** @return an iterator over the xor compressed entries. */ + /** + * Get an iterator over the xor compressed entries. + * + * @return an iterator over the xor compressed entries. + */ public Iterable getCompressedBitmaps() { // Add order is from oldest to newest. The reverse add order is the // output order. return new Iterable() { + @Override public Iterator iterator() { return new Iterator() { private int index = byAddOrder.size() - 1; + @Override public boolean hasNext() { return index >= 0; } + @Override public StoredEntry next() { if (!hasNext()) throw new NoSuchElementException(); @@ -315,6 +355,7 @@ bestXorOffset, item.getFlags()); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -330,7 +371,7 @@ private final int xorOffset; private final int flags; - private StoredEntry(long objectId, EWAHCompressedBitmap bitmap, + StoredEntry(long objectId, EWAHCompressedBitmap bitmap, int xorOffset, int flags) { this.objectId = objectId; this.bitmap = bitmap; @@ -360,11 +401,11 @@ } private static final class PositionEntry extends ObjectIdOwnerMap.Entry { - private final int namePosition; + final int namePosition; - private int offsetPosition; + int offsetPosition; - private PositionEntry(AnyObjectId objectId, int namePosition) { + PositionEntry(AnyObjectId objectId, int namePosition) { super(objectId); this.namePosition = namePosition; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,20 +49,20 @@ import java.io.InputStream; import java.text.MessageFormat; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; +import com.googlecode.javaewah.EWAHCompressedBitmap; + /** * Logical representation of the bitmap data stored in the pack index. - * {@link ObjectId}s are encoded as a single integer in the range [0, - * {@link #getObjectCount()}). Compressed bitmaps are available at certain - * {@code ObjectId}s, which represent all of the objects reachable from that - * {@code ObjectId} (include the {@code ObjectId} itself). The meaning of the - * positions in the bitmaps can be decoded using {@link #getObject(int)} and + * {@link org.eclipse.jgit.lib.ObjectId}s are encoded as a single integer in the + * range [0, {@link #getObjectCount()}). Compressed bitmaps are available at + * certain {@code ObjectId}s, which represent all of the objects reachable from + * that {@code ObjectId} (include the {@code ObjectId} itself). The meaning of + * the positions in the bitmaps can be decoded using {@link #getObject(int)} and * {@link #ofObjectType(EWAHCompressedBitmap, int)}. Furthermore, * {@link #findPosition(AnyObjectId)} can be used to build other bitmaps that a * compatible with the encoded bitmaps available from the index. @@ -85,7 +85,7 @@ * @param reverseIndex * the pack reverse index for the corresponding pack file. * @return a copy of the index in-memory. - * @throws IOException + * @throws java.io.IOException * the stream cannot be read. * @throws CorruptObjectException * the stream does not contain a valid pack bitmap index. @@ -97,12 +97,10 @@ try { return read(fd, packIndex, reverseIndex); } catch (IOException ioe) { - final String path = idxFile.getAbsolutePath(); - final IOException err; - err = new IOException(MessageFormat.format( - JGitText.get().unreadablePackIndex, path)); - err.initCause(ioe); - throw err; + throw new IOException(MessageFormat + .format(JGitText.get().unreadablePackIndex, + idxFile.getAbsolutePath()), + ioe); } finally { try { fd.close(); @@ -128,7 +126,7 @@ * @param reverseIndex * the pack reverse index for the corresponding pack file. * @return a copy of the index in-memory. - * @throws IOException + * @throws java.io.IOException * the stream cannot be read. * @throws CorruptObjectException * the stream does not contain a valid pack bitmap index. @@ -157,7 +155,7 @@ * @param position * the id for which the object will be found. * @return the ObjectId. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * when the item is not found. */ public abstract ObjectId getObject(int position) throws IllegalArgumentException; @@ -193,4 +191,11 @@ * pack that this index was generated from. */ public abstract int getObjectCount(); + + /** + * Returns the number of bitmaps in this bitmap index. + * + * @return the number of bitmaps in this bitmap index. + */ + public abstract int getBitmapCount(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,15 +47,15 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import com.googlecode.javaewah.EWAHCompressedBitmap; -import com.googlecode.javaewah.IntIterator; - import org.eclipse.jgit.internal.storage.file.BasePackBitmapIndex.StoredBitmap; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BitmapIndex; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import com.googlecode.javaewah.EWAHCompressedBitmap; +import com.googlecode.javaewah.IntIterator; + /** * A PackBitmapIndex that remaps the bitmaps in the previous index to the * positions in the new pack index. Note, unlike typical PackBitmapIndex @@ -66,7 +66,7 @@ implements Iterable { private final BasePackBitmapIndex oldPackIndex; - private final PackBitmapIndex newPackIndex; + final PackBitmapIndex newPackIndex; private final ObjectIdOwnerMap convertedBitmaps; private final BitSet inflated; private final int[] prevToNewMapping; @@ -107,7 +107,7 @@ BasePackBitmapIndex oldPackIndex, PackBitmapIndex newPackIndex) { this.oldPackIndex = oldPackIndex; this.newPackIndex = newPackIndex; - convertedBitmaps = new ObjectIdOwnerMap(); + convertedBitmaps = new ObjectIdOwnerMap<>(); inflated = new BitSet(newPackIndex.getObjectCount()); prevToNewMapping = new int[oldPackIndex.getObjectCount()]; @@ -116,27 +116,33 @@ oldPackIndex.getObject(pos)); } + /** {@inheritDoc} */ @Override public int findPosition(AnyObjectId objectId) { return newPackIndex.findPosition(objectId); } + /** {@inheritDoc} */ @Override public ObjectId getObject(int position) throws IllegalArgumentException { return newPackIndex.getObject(position); } + /** {@inheritDoc} */ @Override public int getObjectCount() { return newPackIndex.getObjectCount(); } + /** {@inheritDoc} */ @Override public EWAHCompressedBitmap ofObjectType( EWAHCompressedBitmap bitmap, int type) { return newPackIndex.ofObjectType(bitmap, type); } + /** {@inheritDoc} */ + @Override public Iterator iterator() { if (oldPackIndex == null) return Collections. emptyList().iterator(); @@ -145,6 +151,7 @@ return new Iterator() { private Entry entry; + @Override public boolean hasNext() { while (entry == null && it.hasNext()) { StoredBitmap sb = it.next(); @@ -154,6 +161,7 @@ return entry != null; } + @Override public Entry next() { if (!hasNext()) throw new NoSuchElementException(); @@ -163,12 +171,14 @@ return res; } + @Override public void remove() { throw new UnsupportedOperationException(); } }; } + /** {@inheritDoc} */ @Override public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) { EWAHCompressedBitmap bitmap = newPackIndex.getBitmap(objectId); @@ -199,7 +209,7 @@ public final class Entry extends ObjectId { private final int flags; - private Entry(AnyObjectId src, int flags) { + Entry(AnyObjectId src, int flags) { super(src); this.flags = flags; } @@ -209,4 +219,11 @@ return flags; } } + + /** {@inheritDoc} */ + @Override + public int getBitmapCount() { + // The count is only useful for the end index, not the remapper. + return 0; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,6 @@ import java.text.MessageFormat; import java.util.Arrays; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -59,6 +57,8 @@ import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.NB; +import com.googlecode.javaewah.EWAHCompressedBitmap; + /** * Support for the pack bitmap index v1 format. * @@ -175,6 +175,7 @@ } } + /** {@inheritDoc} */ @Override public int findPosition(AnyObjectId objectId) { long offset = packIndex.findOffset(objectId); @@ -183,6 +184,7 @@ return reverseIndex.findPostion(offset); } + /** {@inheritDoc} */ @Override public ObjectId getObject(int position) throws IllegalArgumentException { ObjectId objectId = reverseIndex.findObjectByPosition(position); @@ -191,11 +193,13 @@ return objectId; } + /** {@inheritDoc} */ @Override public int getObjectCount() { return (int) packIndex.getObjectCount(); } + /** {@inheritDoc} */ @Override public EWAHCompressedBitmap ofObjectType( EWAHCompressedBitmap bitmap, int type) { @@ -212,6 +216,13 @@ throw new IllegalArgumentException(); } + /** {@inheritDoc} */ + @Override + public int getBitmapCount() { + return bitmaps.size(); + } + + /** {@inheritDoc} */ @Override public boolean equals(Object o) { // TODO(cranger): compare the pack checksum? @@ -220,6 +231,7 @@ return false; } + /** {@inheritDoc} */ @Override public int hashCode() { return getPackIndex().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,12 +50,11 @@ import java.security.DigestOutputStream; import java.text.MessageFormat; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder.StoredEntry; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; + +import com.googlecode.javaewah.EWAHCompressedBitmap; /** * Creates the version 1 pack bitmap index files. @@ -74,7 +73,7 @@ */ public PackBitmapIndexWriterV1(final OutputStream dst) { out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst - : new SafeBufferedOutputStream(dst), + : new BufferedOutputStream(dst), Constants.newMessageDigest()); dataOutput = new SimpleDataOutput(out); } @@ -90,7 +89,7 @@ * @param packDataChecksum * checksum signature of the entire pack data content. This is * traditionally the last 20 bytes of the pack file's own stream. - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream, or this * index format cannot store the object data supplied. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2017, 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.internal.storage.file; + +import static java.util.stream.Collectors.toList; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.LockFailedException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.RefList; + +/** + * Implementation of {@link BatchRefUpdate} that uses the {@code packed-refs} + * file to support atomically updating multiple refs. + *

+ * The algorithm is designed to be compatible with traditional single ref + * updates operating on single refs only. Regardless of success or failure, the + * results are atomic: from the perspective of any reader, either all updates in + * the batch will be visible, or none will. In the case of process failure + * during any of the following steps, removal of stale lock files is always + * safe, and will never result in an inconsistent state, although the update may + * or may not have been applied. + *

+ * The algorithm is: + *

    + *
  1. Pack loose refs involved in the transaction using the normal pack-refs + * operation. This ensures that creating lock files in the following step + * succeeds even if a batch contains both a delete of {@code refs/x} (loose) and + * a create of {@code refs/x/y}.
  2. + *
  3. Create locks for all loose refs involved in the transaction, even if they + * are not currently loose.
  4. + *
  5. Pack loose refs again, this time while holding all lock files (see {@link + * RefDirectory#pack(Map)}), without deleting them afterwards. This covers a + * potential race where new loose refs were created after the initial packing + * step. If no new loose refs were created during this race, this step does not + * modify any files on disk. Keep the merged state in memory.
  6. + *
  7. Update the in-memory packed refs with the commands in the batch, possibly + * failing the whole batch if any old ref values do not match.
  8. + *
  9. If the update succeeds, lock {@code packed-refs} and commit by atomically + * renaming the lock file.
  10. + *
  11. Delete loose ref lock files.
  12. + *
+ * + * Because the packed-refs file format is a sorted list, this algorithm is + * linear in the total number of refs, regardless of the batch size. This can be + * a significant slowdown on repositories with large numbers of refs; callers + * that prefer speed over atomicity should use {@code setAtomic(false)}. As an + * optimization, an update containing a single ref update does not use the + * packed-refs protocol. + */ +class PackedBatchRefUpdate extends BatchRefUpdate { + private RefDirectory refdb; + + PackedBatchRefUpdate(RefDirectory refdb) { + super(refdb); + this.refdb = refdb; + } + + /** {@inheritDoc} */ + @Override + public void execute(RevWalk walk, ProgressMonitor monitor, + List options) throws IOException { + if (!isAtomic()) { + // Use default one-by-one implementation. + super.execute(walk, monitor, options); + return; + } + List pending = + ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED); + if (pending.isEmpty()) { + return; + } + if (pending.size() == 1) { + // Single-ref updates are always atomic, no need for packed-refs. + super.execute(walk, monitor, options); + return; + } + if (containsSymrefs(pending)) { + // packed-refs file cannot store symrefs + reject(pending.get(0), REJECTED_OTHER_REASON, + JGitText.get().atomicSymRefNotSupported, pending); + return; + } + + // Required implementation details copied from super.execute. + if (!blockUntilTimestamps(MAX_WAIT)) { + return; + } + if (options != null) { + setPushOptions(options); + } + // End required implementation details. + + // Check for conflicting names before attempting to acquire locks, since + // lockfile creation may fail on file/directory conflicts. + if (!checkConflictingNames(pending)) { + return; + } + + if (!checkObjectExistence(walk, pending)) { + return; + } + + if (!checkNonFastForwards(walk, pending)) { + return; + } + + // Pack refs normally, so we can create lock files even in the case where + // refs/x is deleted and refs/x/y is created in this batch. + try { + refdb.pack( + pending.stream().map(ReceiveCommand::getRefName).collect(toList())); + } catch (LockFailedException e) { + lockFailure(pending.get(0), pending); + return; + } + + Map locks = null; + refdb.inProcessPackedRefsLock.lock(); + try { + PackedRefList oldPackedList; + if (!refdb.isInClone()) { + locks = lockLooseRefs(pending); + if (locks == null) { + return; + } + oldPackedList = refdb.pack(locks); + } else { + // During clone locking isn't needed since no refs exist yet. + // This also helps to avoid problems with refs only differing in + // case on a case insensitive filesystem (bug 528497) + oldPackedList = refdb.getPackedRefs(); + } + RefList newRefs = applyUpdates(walk, oldPackedList, pending); + if (newRefs == null) { + return; + } + LockFile packedRefsLock = refdb.lockPackedRefs(); + if (packedRefsLock == null) { + lockFailure(pending.get(0), pending); + return; + } + // commitPackedRefs removes lock file (by renaming over real file). + refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList, + true); + } finally { + try { + unlockAll(locks); + } finally { + refdb.inProcessPackedRefsLock.unlock(); + } + } + + refdb.fireRefsChanged(); + pending.forEach(c -> c.setResult(ReceiveCommand.Result.OK)); + writeReflog(pending); + } + + private static boolean containsSymrefs(List commands) { + for (ReceiveCommand cmd : commands) { + if (cmd.getOldSymref() != null || cmd.getNewSymref() != null) { + return true; + } + } + return false; + } + + private boolean checkConflictingNames(List commands) + throws IOException { + Set takenNames = new HashSet<>(); + Set takenPrefixes = new HashSet<>(); + Set deletes = new HashSet<>(); + for (ReceiveCommand cmd : commands) { + if (cmd.getType() != ReceiveCommand.Type.DELETE) { + takenNames.add(cmd.getRefName()); + addPrefixesTo(cmd.getRefName(), takenPrefixes); + } else { + deletes.add(cmd.getRefName()); + } + } + Set initialRefs = refdb.getRefs(RefDatabase.ALL).keySet(); + for (String name : initialRefs) { + if (!deletes.contains(name)) { + takenNames.add(name); + addPrefixesTo(name, takenPrefixes); + } + } + + for (ReceiveCommand cmd : commands) { + if (cmd.getType() != ReceiveCommand.Type.DELETE && + takenPrefixes.contains(cmd.getRefName())) { + // This ref is a prefix of some other ref. This check doesn't apply when + // this command is a delete, because if the ref is deleted nobody will + // ever be creating a loose ref with that name. + lockFailure(cmd, commands); + return false; + } + for (String prefix : getPrefixes(cmd.getRefName())) { + if (takenNames.contains(prefix)) { + // A prefix of this ref is already a refname. This check does apply + // when this command is a delete, because we would need to create the + // refname as a directory in order to create a lockfile for the + // to-be-deleted ref. + lockFailure(cmd, commands); + return false; + } + } + } + return true; + } + + private boolean checkObjectExistence(RevWalk walk, + List commands) throws IOException { + for (ReceiveCommand cmd : commands) { + try { + if (!cmd.getNewId().equals(ObjectId.zeroId())) { + walk.parseAny(cmd.getNewId()); + } + } catch (MissingObjectException e) { + // ReceiveCommand#setResult(Result) converts REJECTED to + // REJECTED_NONFASTFORWARD, even though that result is also used for a + // missing object. Eagerly handle this case so we can set the right + // result. + reject(cmd, ReceiveCommand.Result.REJECTED_MISSING_OBJECT, commands); + return false; + } + } + return true; + } + + private boolean checkNonFastForwards(RevWalk walk, + List commands) throws IOException { + if (isAllowNonFastForwards()) { + return true; + } + for (ReceiveCommand cmd : commands) { + cmd.updateType(walk); + if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) { + reject(cmd, REJECTED_NONFASTFORWARD, commands); + return false; + } + } + return true; + } + + /** + * Lock loose refs corresponding to a list of commands. + * + * @param commands + * commands that we intend to execute. + * @return map of ref name in the input commands to lock file. Always contains + * one entry for each ref in the input list. All locks are acquired + * before returning. If any lock was not able to be acquired: the + * return value is null; no locks are held; and all commands that were + * pending are set to fail with {@code LOCK_FAILURE}. + * @throws IOException + * an error occurred other than a failure to acquire; no locks are + * held if this exception is thrown. + */ + @Nullable + private Map lockLooseRefs(List commands) + throws IOException { + ReceiveCommand failed = null; + Map locks = new HashMap<>(); + try { + RETRY: for (int ms : refdb.getRetrySleepMs()) { + failed = null; + // Release all locks before trying again, to prevent deadlock. + unlockAll(locks); + locks.clear(); + RefDirectory.sleep(ms); + + for (ReceiveCommand c : commands) { + String name = c.getRefName(); + LockFile lock = new LockFile(refdb.fileFor(name)); + if (locks.put(name, lock) != null) { + throw new IOException( + MessageFormat.format(JGitText.get().duplicateRef, name)); + } + if (!lock.lock()) { + failed = c; + continue RETRY; + } + } + Map result = locks; + locks = null; + return result; + } + } finally { + unlockAll(locks); + } + lockFailure(failed != null ? failed : commands.get(0), commands); + return null; + } + + private static RefList applyUpdates(RevWalk walk, RefList refs, + List commands) throws IOException { + // Construct a new RefList by merging the old list with the updates. + // This assumes that each ref occurs at most once as a ReceiveCommand. + Collections.sort(commands, new Comparator() { + @Override + public int compare(ReceiveCommand a, ReceiveCommand b) { + return a.getRefName().compareTo(b.getRefName()); + } + }); + + int delta = 0; + for (ReceiveCommand c : commands) { + switch (c.getType()) { + case DELETE: + delta--; + break; + case CREATE: + delta++; + break; + default: + } + } + + RefList.Builder b = new RefList.Builder<>(refs.size() + delta); + int refIdx = 0; + int cmdIdx = 0; + while (refIdx < refs.size() || cmdIdx < commands.size()) { + Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null; + ReceiveCommand cmd = (cmdIdx < commands.size()) + ? commands.get(cmdIdx) + : null; + int cmp = 0; + if (ref != null && cmd != null) { + cmp = ref.getName().compareTo(cmd.getRefName()); + } else if (ref == null) { + cmp = 1; + } else if (cmd == null) { + cmp = -1; + } + + if (cmp < 0) { + b.add(ref); + refIdx++; + } else if (cmp > 0) { + assert cmd != null; + if (cmd.getType() != ReceiveCommand.Type.CREATE) { + lockFailure(cmd, commands); + return null; + } + + b.add(peeledRef(walk, cmd)); + cmdIdx++; + } else { + assert cmd != null; + assert ref != null; + if (!cmd.getOldId().equals(ref.getObjectId())) { + lockFailure(cmd, commands); + return null; + } + + if (cmd.getType() != ReceiveCommand.Type.DELETE) { + b.add(peeledRef(walk, cmd)); + } + cmdIdx++; + refIdx++; + } + } + return b.toRefList(); + } + + private void writeReflog(List commands) { + PersonIdent ident = getRefLogIdent(); + if (ident == null) { + ident = new PersonIdent(refdb.getRepository()); + } + for (ReceiveCommand cmd : commands) { + // Assume any pending commands have already been executed atomically. + if (cmd.getResult() != ReceiveCommand.Result.OK) { + continue; + } + String name = cmd.getRefName(); + + if (cmd.getType() == ReceiveCommand.Type.DELETE) { + try { + RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name)); + } catch (IOException e) { + // Ignore failures, see below. + } + continue; + } + + if (isRefLogDisabled(cmd)) { + continue; + } + + String msg = getRefLogMessage(cmd); + if (isRefLogIncludingResult(cmd)) { + String strResult = toResultString(cmd); + if (strResult != null) { + msg = msg.isEmpty() + ? strResult : msg + ": " + strResult; //$NON-NLS-1$ + } + } + try { + new ReflogWriter(refdb, isForceRefLog(cmd)) + .log(name, cmd.getOldId(), cmd.getNewId(), ident, msg); + } catch (IOException e) { + // Ignore failures, but continue attempting to write more reflogs. + // + // In this storage format, it is impossible to atomically write the + // reflog with the ref updates, so we have to choose between: + // a. Propagating this exception and claiming failure, even though the + // actual ref updates succeeded. + // b. Ignoring failures writing the reflog, so we claim success if and + // only if the ref updates succeeded. + // We choose (b) in order to surprise callers the least. + // + // Possible future improvements: + // * Log a warning to a logger. + // * Retry a fixed number of times in case the error was transient. + } + } + } + + private String toResultString(ReceiveCommand cmd) { + switch (cmd.getType()) { + case CREATE: + return ReflogEntry.PREFIX_CREATED; + case UPDATE: + // Match the behavior of a single RefUpdate. In that case, setting the + // force bit completely bypasses the potentially expensive isMergedInto + // check, by design, so the reflog message may be inaccurate. + // + // Similarly, this class bypasses the isMergedInto checks when the force + // bit is set, meaning we can't actually distinguish between UPDATE and + // UPDATE_NONFASTFORWARD when isAllowNonFastForwards() returns true. + return isAllowNonFastForwards() + ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD; + case UPDATE_NONFASTFORWARD: + return ReflogEntry.PREFIX_FORCED_UPDATE; + default: + return null; + } + } + + private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd) + throws IOException { + ObjectId newId = cmd.getNewId().copy(); + RevObject obj = walk.parseAny(newId); + if (obj instanceof RevTag) { + return new ObjectIdRef.PeeledTag( + Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy()); + } + return new ObjectIdRef.PeeledNonTag( + Ref.Storage.PACKED, cmd.getRefName(), newId); + } + + private static void unlockAll(@Nullable Map locks) { + if (locks != null) { + locks.values().forEach(LockFile::unlock); + } + } + + private static void lockFailure(ReceiveCommand cmd, + List commands) { + reject(cmd, LOCK_FAILURE, commands); + } + + private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result, + List commands) { + reject(cmd, result, null, commands); + } + + private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result, + String why, List commands) { + cmd.setResult(result, why); + for (ReceiveCommand c2 : commands) { + if (c2.getResult() == ReceiveCommand.Result.OK) { + // Undo OK status so ReceiveCommand#abort aborts it. Assumes this method + // is always called before committing any updates to disk. + c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED); + } + } + ReceiveCommand.abort(commands); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,20 +47,25 @@ import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; +import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP; import java.io.EOFException; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InterruptedIOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel.MapMode; +import java.nio.file.AccessDeniedException; +import java.nio.file.NoSuchFileException; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -68,9 +73,13 @@ import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.NoPackSignatureException; import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.errors.UnpackException; +import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException; +import org.eclipse.jgit.errors.UnsupportedPackVersionException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.BinaryDelta; import org.eclipse.jgit.internal.storage.pack.ObjectToPack; @@ -93,6 +102,7 @@ public class PackFile implements Iterable { /** Sorts PackFiles to be most recently created to least recently created. */ public static final Comparator SORT = new Comparator() { + @Override public int compare(final PackFile a, final PackFile b) { return b.packLastModified - a.packLastModified; } @@ -119,15 +129,21 @@ private int activeCopyRawData; - private int packLastModified; + int packLastModified; + + private FileSnapshot fileSnapshot; private volatile boolean invalid; + private volatile Exception invalidatingCause; + private boolean invalidBitmap; + private AtomicInteger transientErrorCount = new AtomicInteger(); + private byte[] packChecksum; - private PackIndex loadedIdx; + private volatile PackIndex loadedIdx; private PackReverseIndex reverseIdx; @@ -152,7 +168,8 @@ */ public PackFile(final File packFile, int extensions) { this.packFile = packFile; - this.packLastModified = (int) (packFile.lastModified() >> 10); + this.fileSnapshot = FileSnapshot.save(packFile); + this.packLastModified = (int) (fileSnapshot.lastModified() >> 10); this.extensions = extensions; // Multiply by 31 here so we can more directly combine with another @@ -162,47 +179,69 @@ length = Long.MAX_VALUE; } - private synchronized PackIndex idx() throws IOException { - if (loadedIdx == null) { - if (invalid) - throw new PackInvalidException(packFile); - - try { - final PackIndex idx = PackIndex.open(extFile(INDEX)); + private PackIndex idx() throws IOException { + PackIndex idx = loadedIdx; + if (idx == null) { + synchronized (this) { + idx = loadedIdx; + if (idx == null) { + if (invalid) { + throw new PackInvalidException(packFile, invalidatingCause); + } + try { + idx = PackIndex.open(extFile(INDEX)); - if (packChecksum == null) { - packChecksum = idx.packChecksum; - } else if (!Arrays.equals(packChecksum, idx.packChecksum)) { - throw new PackMismatchException(MessageFormat.format( - JGitText.get().packChecksumMismatch, - packFile.getPath())); + if (packChecksum == null) { + packChecksum = idx.packChecksum; + } else if (!Arrays.equals(packChecksum, + idx.packChecksum)) { + throw new PackMismatchException(MessageFormat + .format(JGitText.get().packChecksumMismatch, + packFile.getPath(), + ObjectId.fromRaw(packChecksum) + .name(), + ObjectId.fromRaw(idx.packChecksum) + .name())); + } + loadedIdx = idx; + } catch (InterruptedIOException e) { + // don't invalidate the pack, we are interrupted from + // another thread + throw e; + } catch (IOException e) { + invalid = true; + invalidatingCause = e; + throw e; + } } - loadedIdx = idx; - } catch (InterruptedIOException e) { - // don't invalidate the pack, we are interrupted from another thread - throw e; - } catch (IOException e) { - invalid = true; - throw e; } } - return loadedIdx; + return idx; } - - /** @return the File object which locates this pack on disk. */ + /** + * Get the File object which locates this pack on disk. + * + * @return the File object which locates this pack on disk. + */ public File getPackFile() { return packFile; } /** + * Get the index for this pack file. + * * @return the index for this pack file. - * @throws IOException + * @throws java.io.IOException */ public PackIndex getIndex() throws IOException { return idx(); } - /** @return name extracted from {@code pack-*.pack} pattern. */ + /** + * Get name extracted from {@code pack-*.pack} pattern. + * + * @return name extracted from {@code pack-*.pack} pattern. + */ public String getPackName() { String name = packName; if (name == null) { @@ -226,7 +265,7 @@ * @param id * the object to look for. Must not be null. * @return true if the object is in this pack; false otherwise. - * @throws IOException + * @throws java.io.IOException * the index file cannot be loaded into memory. */ public boolean hasObject(final AnyObjectId id) throws IOException { @@ -241,7 +280,7 @@ */ public boolean shouldBeKept() { if (keepFile == null) - keepFile = new File(packFile.getPath() + ".keep"); //$NON-NLS-1$ + keepFile = extFile(KEEP); return keepFile.exists(); } @@ -280,6 +319,8 @@ } /** + * {@inheritDoc} + *

* Provide iterator over entries in associated pack index, that should also * exist in this pack file. Objects returned by such iterator are mutable * during iteration. @@ -287,10 +328,9 @@ * Iterator returns objects in SHA-1 lexicographical order. *

* - * @return iterator over entries of associated pack index - * * @see PackIndex#iterator() */ + @Override public Iterator iterator() { try { return idx().iterator(); @@ -325,6 +365,16 @@ return getReverseIdx().findObject(offset); } + /** + * Return the @{@link FileSnapshot} associated to the underlying packfile + * that has been used when the object was created. + * + * @return the packfile @{@link FileSnapshot} that the object is loaded from. + */ + FileSnapshot getFileSnapshot() { + return fileSnapshot; + } + private final byte[] decompress(final long position, final int sz, final WindowCursor curs) throws IOException, DataFormatException { byte[] dstbuf; @@ -492,19 +542,15 @@ CorruptObjectException corruptObject = new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackFile())); - corruptObject.initCause(dataFormat); + Long.valueOf(src.offset), getPackFile()), + dataFormat); - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(corruptObject); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(src, + corruptObject); } catch (IOException ioError) { - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(ioError); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(src, + ioError); } if (quickCopy != null) { @@ -568,6 +614,14 @@ invalid = true; } + int incrementTransientErrorCount() { + return transientErrorCount.incrementAndGet(); + } + + void resetTransientErrorCount() { + transientErrorCount.set(0); + } + private void readFully(final long position, final byte[] dstbuf, int dstoff, final int cnt, final WindowCursor curs) throws IOException { @@ -581,11 +635,8 @@ try { doOpen(); } catch (IOException thisPackNotValid) { - StoredObjectRepresentationNotAvailableException gone; - - gone = new StoredObjectRepresentationNotAvailableException(otp); - gone.initCause(thisPackNotValid); - throw gone; + throw new StoredObjectRepresentationNotAvailableException(otp, + thisPackNotValid); } } } @@ -612,9 +663,10 @@ } private void doOpen() throws IOException { + if (invalid) { + throw new PackInvalidException(packFile, invalidatingCause); + } try { - if (invalid) - throw new PackInvalidException(packFile); synchronized (readLock) { fd = new RandomAccessFile(packFile, "r"); //$NON-NLS-1$ length = fd.length(); @@ -622,24 +674,35 @@ } } catch (InterruptedIOException e) { // don't invalidate the pack, we are interrupted from another thread - openFail(false); + openFail(false, e); throw e; - } catch (IOException ioe) { - openFail(true); - throw ioe; - } catch (RuntimeException re) { - openFail(true); - throw re; - } catch (Error re) { - openFail(true); - throw re; + } catch (FileNotFoundException fn) { + // don't invalidate the pack if opening an existing file failed + // since it may be related to a temporary lack of resources (e.g. + // max open files) + openFail(!packFile.exists(), fn); + throw fn; + } catch (EOFException | AccessDeniedException | NoSuchFileException + | CorruptObjectException | NoPackSignatureException + | PackMismatchException | UnpackException + | UnsupportedPackIndexVersionException + | UnsupportedPackVersionException pe) { + // exceptions signaling permanent problems with a pack + openFail(true, pe); + throw pe; + } catch (IOException | RuntimeException ge) { + // generic exceptions could be transient so we should not mark the + // pack invalid to avoid false MissingObjectExceptions + openFail(false, ge); + throw ge; } } - private void openFail(boolean invalidate) { + private void openFail(boolean invalidate, Exception cause) { activeWindows = 0; activeCopyRawData = 0; invalid = invalidate; + invalidatingCause = cause; doClose(); } @@ -660,6 +723,14 @@ ByteArrayWindow read(final long pos, int size) throws IOException { synchronized (readLock) { + if (invalid || fd == null) { + // Due to concurrency between a read and another packfile invalidation thread + // one thread could come up to this point and then fail with NPE. + // Detect the situation and throw a proper exception so that can be properly + // managed by the main packfile search loop and the Git client won't receive + // any failures. + throw new PackInvalidException(packFile, invalidatingCause); + } if (length < pos + size) size = (int) (length - pos); final byte[] buf = new byte[size]; @@ -699,28 +770,31 @@ fd.seek(0); fd.readFully(buf, 0, 12); - if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) - throw new IOException(JGitText.get().notAPACKFile); + if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) { + throw new NoPackSignatureException(JGitText.get().notAPACKFile); + } final long vers = NB.decodeUInt32(buf, 4); final long packCnt = NB.decodeUInt32(buf, 8); - if (vers != 2 && vers != 3) - throw new IOException(MessageFormat.format( - JGitText.get().unsupportedPackVersion, Long.valueOf(vers))); + if (vers != 2 && vers != 3) { + throw new UnsupportedPackVersionException(vers); + } - if (packCnt != idx.getObjectCount()) + if (packCnt != idx.getObjectCount()) { throw new PackMismatchException(MessageFormat.format( JGitText.get().packObjectCountMismatch, Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()), getPackFile())); + } fd.seek(length - 20); fd.readFully(buf, 0, 20); - if (!Arrays.equals(buf, packChecksum)) + if (!Arrays.equals(buf, packChecksum)) { throw new PackMismatchException(MessageFormat.format( - JGitText.get().packObjectCountMismatch - , ObjectId.fromRaw(buf).name() - , ObjectId.fromRaw(idx.packChecksum).name() - , getPackFile())); + JGitText.get().packChecksumMismatch, + getPackFile(), + ObjectId.fromRaw(buf).name(), + ObjectId.fromRaw(idx.packChecksum).name())); + } } ObjectLoader load(final WindowCursor curs, long pos) @@ -858,12 +932,11 @@ return new ObjectLoader.SmallObject(type, data); } catch (DataFormatException dfe) { - CorruptObjectException coe = new CorruptObjectException( + throw new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(pos), getPackFile())); - coe.initCause(dfe); - throw coe; + Long.valueOf(pos), getPackFile()), + dfe); } } @@ -1074,8 +1147,17 @@ if (invalid || invalidBitmap) return null; if (bitmapIdx == null && hasExt(BITMAP_INDEX)) { - final PackBitmapIndex idx = PackBitmapIndex.open( - extFile(BITMAP_INDEX), idx(), getReverseIdx()); + final PackBitmapIndex idx; + try { + idx = PackBitmapIndex.open(extFile(BITMAP_INDEX), idx(), + getReverseIdx()); + } catch (FileNotFoundException e) { + // Once upon a time this bitmap file existed. Now it + // has been removed. Most likely an external gc has + // removed this packfile and the bitmap + invalidBitmap = true; + return null; + } // At this point, idx() will have set packChecksum. if (Arrays.equals(packChecksum, idx.packChecksum)) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,16 +55,19 @@ import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.NB; /** - * Access path to locate objects by {@link ObjectId} in a {@link PackFile}. + * Access path to locate objects by {@link org.eclipse.jgit.lib.ObjectId} in a + * {@link org.eclipse.jgit.internal.storage.file.PackFile}. *

* Indexes are strictly redundant information in that we can rebuild all of the * data held in the index file from the on disk representation of the pack file @@ -72,7 +75,8 @@ * by ObjectId. *

*/ -public abstract class PackIndex implements Iterable { +public abstract class PackIndex + implements Iterable, ObjectIdSet { /** * Open an existing pack .idx file for reading. *

@@ -86,7 +90,7 @@ * @return access implementation for the requested file. * @throws FileNotFoundException * the file does not exist. - * @throws IOException + * @throws java.io.IOException * the file exists but could not be read due to security errors, * unrecognized data version, or unexpected data corruption. */ @@ -95,11 +99,10 @@ try { return read(fd); } catch (IOException ioe) { - final String path = idxFile.getAbsolutePath(); - final IOException err; - err = new IOException(MessageFormat.format(JGitText.get().unreadablePackIndex, path)); - err.initCause(ioe); - throw err; + throw new IOException(MessageFormat + .format(JGitText.get().unreadablePackIndex, + idxFile.getAbsolutePath()), + ioe); } finally { try { fd.close(); @@ -121,9 +124,9 @@ * buffered as some small IOs are performed against the stream. * The caller is responsible for closing the stream. * @return a copy of the index in-memory. - * @throws IOException + * @throws java.io.IOException * the stream cannot be read. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the stream does not contain a valid pack index. */ public static PackIndex read(InputStream fd) throws IOException, @@ -136,9 +139,7 @@ case 2: return new PackIndexV2(fd); default: - throw new IOException(MessageFormat.format( - JGitText.get().unsupportedPackIndexVersion, - Integer.valueOf(v))); + throw new UnsupportedPackIndexVersionException(v); } } return new PackIndexV1(fd, hdr); @@ -166,7 +167,15 @@ return findOffset(id) != -1; } + /** {@inheritDoc} */ + @Override + public boolean contains(AnyObjectId id) { + return findOffset(id) != -1; + } + /** + * {@inheritDoc} + *

* Provide iterator that gives access to index entries. Note, that iterator * returns reference to mutable object, the same reference in each call - * for performance reason. If client needs immutable objects, it must copy @@ -174,9 +183,8 @@ *

* Iterator returns objects in SHA-1 lexicographical order. *

- * - * @return iterator over pack index entries */ + @Override public abstract Iterator iterator(); /** @@ -211,7 +219,8 @@ * @param nthPosition * position within the traversal of {@link #iterator()} that the * caller needs the object for. The first returned - * {@link MutableEntry} is 0, the second is 1, etc. + * {@link org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry} + * is 0, the second is 1, etc. * @return the ObjectId for the corresponding entry. */ public abstract ObjectId getObjectId(long nthPosition); @@ -232,8 +241,10 @@ * @param nthPosition * unsigned 32 bit position within the traversal of * {@link #iterator()} that the caller needs the object for. The - * first returned {@link MutableEntry} is 0, the second is 1, - * etc. Positions past 2**31-1 are negative, but still valid. + * first returned + * {@link org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry} + * is 0, the second is 1, etc. Positions past 2**31-1 are + * negative, but still valid. * @return the ObjectId for the corresponding entry. */ public final ObjectId getObjectId(final int nthPosition) { @@ -275,9 +286,9 @@ * @param objId * id of object to look for * @return CRC32 checksum of specified object (at 32 less significant bits) - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * when requested ObjectId was not found in this index - * @throws UnsupportedOperationException + * @throws java.lang.UnsupportedOperationException * when this index doesn't support CRC32 checksum */ public abstract long findCRC32(AnyObjectId objId) @@ -301,7 +312,7 @@ * @param matchLimit * maximum number of results to return. At most this many * ObjectIds should be added to matches before returning. - * @throws IOException + * @throws java.io.IOException * the index cannot be read. */ public abstract void resolve(Set matches, AbbreviatedObjectId id, @@ -359,6 +370,7 @@ protected abstract MutableEntry initEntry(); + @Override public boolean hasNext() { return returnedNumber < getObjectCount(); } @@ -367,8 +379,10 @@ * Implementation must update {@link #returnedNumber} before returning * element. */ + @Override public abstract MutableEntry next(); + @Override public void remove() { throw new UnsupportedOperationException(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,7 +67,7 @@ private final long[] idxHeader; - private byte[][] idxdata; + byte[][] idxdata; private long objectCnt; @@ -103,11 +103,13 @@ IO.readFully(fd, packChecksum, 0, packChecksum.length); } + /** {@inheritDoc} */ @Override public long getObjectCount() { return objectCnt; } + /** {@inheritDoc} */ @Override public long getOffset64Count() { long n64 = 0; @@ -140,6 +142,7 @@ return (int) (nthPosition - base); } + /** {@inheritDoc} */ @Override public ObjectId getObjectId(final long nthPosition) { final int levelOne = findLevelOne(nthPosition); @@ -156,6 +159,7 @@ return NB.decodeUInt32(idxdata[levelOne], p); } + /** {@inheritDoc} */ @Override public long findOffset(final AnyObjectId objId) { final int levelOne = objId.getFirstByte(); @@ -182,21 +186,25 @@ return -1; } + /** {@inheritDoc} */ @Override public long findCRC32(AnyObjectId objId) { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ @Override public boolean hasCRC32Support() { return false; } + /** {@inheritDoc} */ @Override public Iterator iterator() { return new IndexV1Iterator(); } + /** {@inheritDoc} */ @Override public void resolve(Set matches, AbbreviatedObjectId id, int matchLimit) throws IOException { @@ -233,13 +241,14 @@ } private class IndexV1Iterator extends EntriesIterator { - private int levelOne; + int levelOne; - private int levelTwo; + int levelTwo; @Override protected MutableEntry initEntry() { return new MutableEntry() { + @Override protected void ensureId() { idBuffer.fromRaw(idxdata[levelOne], levelTwo - Constants.OBJECT_ID_LENGTH); @@ -247,6 +256,7 @@ }; } + @Override public MutableEntry next() { for (; levelOne < idxdata.length; levelOne++) { if (idxdata[levelOne] == null) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,16 +75,16 @@ private final long[] fanoutTable; /** 256 arrays of contiguous object names. */ - private int[][] names; + int[][] names; /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ - private byte[][] offset32; + byte[][] offset32; /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ private byte[][] crc32; /** 64 bit offset table. */ - private byte[] offset64; + byte[] offset64; PackIndexV2(final InputStream fd) throws IOException { final byte[] fanoutRaw = new byte[4 * FANOUT]; @@ -164,11 +164,13 @@ IO.readFully(fd, packChecksum, 0, packChecksum.length); } + /** {@inheritDoc} */ @Override public long getObjectCount() { return objectCnt; } + /** {@inheritDoc} */ @Override public long getOffset64Count() { return offset64.length / 8; @@ -196,6 +198,7 @@ return (int) (nthPosition - base); } + /** {@inheritDoc} */ @Override public ObjectId getObjectId(final long nthPosition) { final int levelOne = findLevelOne(nthPosition); @@ -204,6 +207,7 @@ return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5 } + /** {@inheritDoc} */ @Override public long getOffset(final long nthPosition) { final int levelOne = findLevelOne(nthPosition); @@ -211,6 +215,7 @@ return getOffset(levelOne, levelTwo); } + /** {@inheritDoc} */ @Override public long findOffset(final AnyObjectId objId) { final int levelOne = objId.getFirstByte(); @@ -227,6 +232,7 @@ return p; } + /** {@inheritDoc} */ @Override public long findCRC32(AnyObjectId objId) throws MissingObjectException { final int levelOne = objId.getFirstByte(); @@ -236,16 +242,19 @@ return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); } + /** {@inheritDoc} */ @Override public boolean hasCRC32Support() { return true; } + /** {@inheritDoc} */ @Override public Iterator iterator() { return new EntriesIteratorV2(); } + /** {@inheritDoc} */ @Override public void resolve(Set matches, AbbreviatedObjectId id, int matchLimit) throws IOException { @@ -304,13 +313,14 @@ } private class EntriesIteratorV2 extends EntriesIterator { - private int levelOne; + int levelOne; - private int levelTwo; + int levelTwo; @Override protected MutableEntry initEntry() { return new MutableEntry() { + @Override protected void ensureId() { idBuffer.fromRaw(names[levelOne], levelTwo - Constants.OBJECT_ID_LENGTH / 4); @@ -318,6 +328,7 @@ }; } + @Override public MutableEntry next() { for (; levelOne < names.length; levelOne++) { if (levelTwo < names[levelOne].length) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,17 +53,16 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.transport.PackedObjectInfo; import org.eclipse.jgit.util.NB; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** - * Creates a table of contents to support random access by {@link PackFile}. + * Creates a table of contents to support random access by + * {@link org.eclipse.jgit.internal.storage.file.PackFile}. *

- * Pack index files (the .idx suffix in a pack file pair) - * provides random access to any object in the pack by associating an ObjectId - * to the byte offset within the pack where the object's data can be read. + * Pack index files (the .idx suffix in a pack file pair) provides + * random access to any object in the pack by associating an ObjectId to the + * byte offset within the pack where the object's data can be read. */ public abstract class PackIndexWriter { /** Magic constant indicating post-version 1 format. */ @@ -91,7 +90,7 @@ * will be examined until a format can be conclusively selected. * @return a new writer to output an index file of the requested format to * the supplied stream. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * no recognized pack index version can support the supplied * objects. This is likely a bug in the implementation. * @see #oldestPossibleFormat(List) @@ -118,7 +117,7 @@ * the objects the caller needs to store in the index. Entries * will be examined until a format can be conclusively selected. * @return the index format. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * no recognized pack index version can support the supplied * objects. This is likely a bug in the implementation. */ @@ -144,7 +143,7 @@ * this formatted version will be written. * @return a new writer to output an index file of the requested format to * the supplied stream. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the version requested is not supported by this * implementation. */ @@ -183,7 +182,7 @@ */ protected PackIndexWriter(final OutputStream dst) { out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst - : new SafeBufferedOutputStream(dst), + : new BufferedOutputStream(dst), Constants.newMessageDigest()); tmp = new byte[4 + Constants.OBJECT_ID_LENGTH]; } @@ -196,12 +195,13 @@ * * @param toStore * sorted list of objects to store in the index. The caller must - * have previously sorted the list using {@link PackedObjectInfo}'s - * native {@link Comparable} implementation. + * have previously sorted the list using + * {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native + * {@link java.lang.Comparable} implementation. * @param packDataChecksum * checksum signature of the entire pack data content. This is * traditionally the last 20 bytes of the pack file's own stream. - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream, or this * index format cannot store the object data supplied. */ @@ -231,7 +231,7 @@ * the {@link #entries} collection may be iterated over more than once if * necessary. Implementors therefore have complete control over the data. * - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream, or this * index format cannot store the object data supplied. */ @@ -247,7 +247,7 @@ * * @param version * version number of this index format being written. - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream. */ protected void writeTOC(final int version) throws IOException { @@ -261,10 +261,10 @@ *

* The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer * counts. Each count represents the number of objects within this index - * whose {@link ObjectId#getFirstByte()} matches the count's position in the - * fan-out table. + * whose {@link org.eclipse.jgit.lib.ObjectId#getFirstByte()} matches the + * count's position in the fan-out table. * - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream. */ protected void writeFanOutTable() throws IOException { @@ -289,7 +289,7 @@ * the pack data checksum above. * * - * @throws IOException + * @throws java.io.IOException * an error occurred while writing to the output stream. */ protected void writeChecksumFooter() throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,6 +68,7 @@ super(dst); } + /** {@inheritDoc} */ @Override protected void writeImpl() throws IOException { writeFanOutTable(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,6 +63,7 @@ super(dst); } + /** {@inheritDoc} */ @Override protected void writeImpl() throws IOException { writeTOC(2); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,6 +64,7 @@ wc.pin(pack, pos); } + /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { int n = wc.copy(pack, pos, b, off, len); @@ -71,6 +72,7 @@ return n; } + /** {@inheritDoc} */ @Override public int read() throws IOException { byte[] buf = new byte[1]; @@ -78,8 +80,9 @@ return n == 1 ? buf[0] & 0xff : -1; } + /** {@inheritDoc} */ @Override public void close() { wc.close(); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,717 @@ +/* + * Copyright (C) 2017, 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.internal.storage.file; + +import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA; +import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.Channels; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.CRC32; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.transport.PackParser; +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.BlockList; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.io.CountingOutputStream; +import org.eclipse.jgit.util.sha1.SHA1; + +/** + * Object inserter that inserts one pack per call to {@link #flush()}, and never + * inserts loose objects. + */ +public class PackInserter extends ObjectInserter { + /** Always produce version 2 indexes, to get CRC data. */ + private static final int INDEX_VERSION = 2; + + private final ObjectDirectory db; + + private List objectList; + private ObjectIdOwnerMap objectMap; + private boolean rollback; + private boolean checkExisting = true; + + private int compression = Deflater.BEST_COMPRESSION; + private File tmpPack; + private PackStream packOut; + private Inflater cachedInflater; + + PackInserter(ObjectDirectory db) { + this.db = db; + } + + /** + * Whether to check if objects exist in the repo + * + * @param check + * if {@code false}, will write out possibly-duplicate objects + * without first checking whether they exist in the repo; default + * is true. + */ + public void checkExisting(boolean check) { + checkExisting = check; + } + + /** + * Set compression level for zlib deflater. + * + * @param compression + * compression level for zlib deflater. + */ + public void setCompressionLevel(int compression) { + this.compression = compression; + } + + int getBufferSize() { + return buffer().length; + } + + /** {@inheritDoc} */ + @Override + public ObjectId insert(int type, byte[] data, int off, int len) + throws IOException { + ObjectId id = idFor(type, data, off, len); + if (objectMap != null && objectMap.contains(id)) { + return id; + } + // Ignore loose objects, which are potentially unreachable. + if (checkExisting && db.hasPackedObject(id)) { + return id; + } + + long offset = beginObject(type, len); + packOut.compress.write(data, off, len); + packOut.compress.finish(); + return endObject(id, offset); + } + + /** {@inheritDoc} */ + @Override + public ObjectId insert(int type, long len, InputStream in) + throws IOException { + byte[] buf = buffer(); + if (len <= buf.length) { + IO.readFully(in, buf, 0, (int) len); + return insert(type, buf, 0, (int) len); + } + + long offset = beginObject(type, len); + SHA1 md = digest(); + md.update(Constants.encodedTypeString(type)); + md.update((byte) ' '); + md.update(Constants.encodeASCII(len)); + md.update((byte) 0); + + while (0 < len) { + int n = in.read(buf, 0, (int) Math.min(buf.length, len)); + if (n <= 0) { + throw new EOFException(); + } + md.update(buf, 0, n); + packOut.compress.write(buf, 0, n); + len -= n; + } + packOut.compress.finish(); + return endObject(md.toObjectId(), offset); + } + + private long beginObject(int type, long len) throws IOException { + if (packOut == null) { + beginPack(); + } + long offset = packOut.getOffset(); + packOut.beginObject(type, len); + return offset; + } + + private ObjectId endObject(ObjectId id, long offset) { + PackedObjectInfo obj = new PackedObjectInfo(id); + obj.setOffset(offset); + obj.setCRC((int) packOut.crc32.getValue()); + objectList.add(obj); + objectMap.addIfAbsent(obj); + return id; + } + + private static File idxFor(File packFile) { + String p = packFile.getName(); + return new File( + packFile.getParentFile(), + p.substring(0, p.lastIndexOf('.')) + ".idx"); //$NON-NLS-1$ + } + + private void beginPack() throws IOException { + objectList = new BlockList<>(); + objectMap = new ObjectIdOwnerMap<>(); + + rollback = true; + tmpPack = File.createTempFile("insert_", ".pack", db.getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$ + packOut = new PackStream(tmpPack); + + // Write the header as though it were a single object pack. + packOut.write(packOut.hdrBuf, 0, writePackHeader(packOut.hdrBuf, 1)); + } + + private static int writePackHeader(byte[] buf, int objectCount) { + System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4); + NB.encodeInt32(buf, 4, 2); // Always use pack version 2. + NB.encodeInt32(buf, 8, objectCount); + return 12; + } + + /** {@inheritDoc} */ + @Override + public PackParser newPackParser(InputStream in) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @Override + public ObjectReader newReader() { + return new Reader(); + } + + /** {@inheritDoc} */ + @Override + public void flush() throws IOException { + if (tmpPack == null) { + return; + } + + if (packOut == null) { + throw new IOException(); + } + + byte[] packHash; + try { + packHash = packOut.finishPack(); + } finally { + packOut = null; + } + + Collections.sort(objectList); + File tmpIdx = idxFor(tmpPack); + writePackIndex(tmpIdx, packHash, objectList); + + File realPack = new File(db.getPackDirectory(), + "pack-" + computeName(objectList).name() + ".pack"); //$NON-NLS-1$ //$NON-NLS-2$ + db.closeAllPackHandles(realPack); + tmpPack.setReadOnly(); + FileUtils.rename(tmpPack, realPack, ATOMIC_MOVE); + + File realIdx = idxFor(realPack); + tmpIdx.setReadOnly(); + try { + FileUtils.rename(tmpIdx, realIdx, ATOMIC_MOVE); + } catch (IOException e) { + File newIdx = new File( + realIdx.getParentFile(), realIdx.getName() + ".new"); //$NON-NLS-1$ + try { + FileUtils.rename(tmpIdx, newIdx, ATOMIC_MOVE); + } catch (IOException e2) { + newIdx = tmpIdx; + e = e2; + } + throw new IOException(MessageFormat.format( + JGitText.get().panicCantRenameIndexFile, newIdx, + realIdx), e); + } + + db.openPack(realPack); + rollback = false; + clear(); + } + + private static void writePackIndex(File idx, byte[] packHash, + List list) throws IOException { + try (OutputStream os = new FileOutputStream(idx)) { + PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION); + w.write(list, packHash); + } + } + + private ObjectId computeName(List list) { + SHA1 md = digest().reset(); + byte[] buf = buffer(); + for (PackedObjectInfo otp : list) { + otp.copyRawTo(buf, 0); + md.update(buf, 0, OBJECT_ID_LENGTH); + } + return ObjectId.fromRaw(md.digest()); + } + + /** {@inheritDoc} */ + @Override + public void close() { + try { + if (packOut != null) { + try { + packOut.close(); + } catch (IOException err) { + // Ignore a close failure, the pack should be removed. + } + } + if (rollback && tmpPack != null) { + try { + FileUtils.delete(tmpPack); + } catch (IOException e) { + // Still delete idx. + } + try { + FileUtils.delete(idxFor(tmpPack)); + } catch (IOException e) { + // Ignore error deleting temp idx. + } + rollback = false; + } + } finally { + clear(); + try { + InflaterCache.release(cachedInflater); + } finally { + cachedInflater = null; + } + } + } + + private void clear() { + objectList = null; + objectMap = null; + tmpPack = null; + packOut = null; + } + + private Inflater inflater() { + if (cachedInflater == null) { + cachedInflater = InflaterCache.get(); + } else { + cachedInflater.reset(); + } + return cachedInflater; + } + + /** + * Stream that writes to a pack file. + *

+ * Backed by two views of the same open file descriptor: a random-access file, + * and an output stream. Seeking in the file causes subsequent writes to the + * output stream to occur wherever the file pointer is pointing, so we need to + * take care to always seek to the end of the file before writing a new + * object. + *

+ * Callers should always use {@link #seek(long)} to seek, rather than reaching + * into the file member. As long as this contract is followed, calls to {@link + * #write(byte[], int, int)} are guaranteed to write at the end of the file, + * even if there have been intermediate seeks. + */ + private class PackStream extends OutputStream { + final byte[] hdrBuf; + final CRC32 crc32; + final DeflaterOutputStream compress; + + private final RandomAccessFile file; + private final CountingOutputStream out; + private final Deflater deflater; + + private boolean atEnd; + + PackStream(File pack) throws IOException { + file = new RandomAccessFile(pack, "rw"); //$NON-NLS-1$ + out = new CountingOutputStream(new FileOutputStream(file.getFD())); + deflater = new Deflater(compression); + compress = new DeflaterOutputStream(this, deflater, 8192); + hdrBuf = new byte[32]; + crc32 = new CRC32(); + atEnd = true; + } + + long getOffset() { + // This value is accurate as long as we only ever write to the end of the + // file, and don't seek back to overwrite any previous segments. Although + // this is subtle, storing the stream counter this way is still preferable + // to returning file.length() here, as it avoids a syscall and possible + // IOException. + return out.getCount(); + } + + void seek(long offset) throws IOException { + file.seek(offset); + atEnd = false; + } + + void beginObject(int objectType, long length) throws IOException { + crc32.reset(); + deflater.reset(); + write(hdrBuf, 0, encodeTypeSize(objectType, length)); + } + + private int encodeTypeSize(int type, long rawLength) { + long nextLength = rawLength >>> 4; + hdrBuf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F)); + rawLength = nextLength; + int n = 1; + while (rawLength > 0) { + nextLength >>>= 7; + hdrBuf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F)); + rawLength = nextLength; + } + return n; + } + + @Override + public void write(final int b) throws IOException { + hdrBuf[0] = (byte) b; + write(hdrBuf, 0, 1); + } + + @Override + public void write(byte[] data, int off, int len) throws IOException { + crc32.update(data, off, len); + if (!atEnd) { + file.seek(file.length()); + atEnd = true; + } + out.write(data, off, len); + } + + byte[] finishPack() throws IOException { + // Overwrite placeholder header with actual object count, then hash. This + // method intentionally uses direct seek/write calls rather than the + // wrappers which keep track of atEnd. This leaves atEnd, the file + // pointer, and out's counter in an inconsistent state; that's ok, since + // this method closes the file anyway. + try { + file.seek(0); + out.write(hdrBuf, 0, writePackHeader(hdrBuf, objectList.size())); + + byte[] buf = buffer(); + SHA1 md = digest().reset(); + file.seek(0); + while (true) { + int r = file.read(buf); + if (r < 0) { + break; + } + md.update(buf, 0, r); + } + byte[] packHash = md.digest(); + out.write(packHash, 0, packHash.length); + return packHash; + } finally { + close(); + } + } + + @Override + public void close() throws IOException { + deflater.end(); + try { + out.close(); + } finally { + file.close(); + } + } + + byte[] inflate(long filePos, int len) throws IOException, DataFormatException { + byte[] dstbuf; + try { + dstbuf = new byte[len]; + } catch (OutOfMemoryError noMemory) { + return null; // Caller will switch to large object streaming. + } + + byte[] srcbuf = buffer(); + Inflater inf = inflater(); + filePos += setInput(filePos, inf, srcbuf); + for (int dstoff = 0;;) { + int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); + dstoff += n; + if (inf.finished()) { + return dstbuf; + } + if (inf.needsInput()) { + filePos += setInput(filePos, inf, srcbuf); + } else if (n == 0) { + throw new DataFormatException(); + } + } + } + + private int setInput(long filePos, Inflater inf, byte[] buf) + throws IOException { + if (file.getFilePointer() != filePos) { + seek(filePos); + } + int n = file.read(buf); + if (n < 0) { + throw new EOFException(JGitText.get().unexpectedEofInPack); + } + inf.setInput(buf, 0, n); + return n; + } + } + + private class Reader extends ObjectReader { + private final ObjectReader ctx; + + private Reader() { + ctx = db.newReader(); + setStreamFileThreshold(ctx.getStreamFileThreshold()); + } + + @Override + public ObjectReader newReader() { + return db.newReader(); + } + + @Override + public ObjectInserter getCreatedFromInserter() { + return PackInserter.this; + } + + @Override + public Collection resolve(AbbreviatedObjectId id) + throws IOException { + Collection stored = ctx.resolve(id); + if (objectList == null) { + return stored; + } + + Set r = new HashSet<>(stored.size() + 2); + r.addAll(stored); + for (PackedObjectInfo obj : objectList) { + if (id.prefixCompare(obj) == 0) { + r.add(obj.copy()); + } + } + return r; + } + + @Override + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + if (objectMap == null) { + return ctx.open(objectId, typeHint); + } + + PackedObjectInfo obj = objectMap.get(objectId); + if (obj == null) { + return ctx.open(objectId, typeHint); + } + + byte[] buf = buffer(); + packOut.seek(obj.getOffset()); + int cnt = packOut.file.read(buf, 0, 20); + if (cnt <= 0) { + throw new EOFException(JGitText.get().unexpectedEofInPack); + } + + int c = buf[0] & 0xff; + int type = (c >> 4) & 7; + if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { + throw new IOException(MessageFormat.format( + JGitText.get().cannotReadBackDelta, Integer.toString(type))); + } + if (typeHint != OBJ_ANY && type != typeHint) { + throw new IncorrectObjectTypeException(objectId.copy(), typeHint); + } + + long sz = c & 0x0f; + int ptr = 1; + int shift = 4; + while ((c & 0x80) != 0) { + if (ptr >= cnt) { + throw new EOFException(JGitText.get().unexpectedEofInPack); + } + c = buf[ptr++] & 0xff; + sz += ((long) (c & 0x7f)) << shift; + shift += 7; + } + + long zpos = obj.getOffset() + ptr; + if (sz < getStreamFileThreshold()) { + byte[] data = inflate(obj, zpos, (int) sz); + if (data != null) { + return new ObjectLoader.SmallObject(type, data); + } + } + return new StreamLoader(type, sz, zpos); + } + + private byte[] inflate(PackedObjectInfo obj, long zpos, int sz) + throws IOException, CorruptObjectException { + try { + return packOut.inflate(zpos, sz); + } catch (DataFormatException dfe) { + throw new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(obj.getOffset()), + tmpPack.getAbsolutePath()), + dfe); + } + } + + @Override + public Set getShallowCommits() throws IOException { + return ctx.getShallowCommits(); + } + + @Override + public void close() { + ctx.close(); + } + + private class StreamLoader extends ObjectLoader { + private final int type; + private final long size; + private final long pos; + + StreamLoader(int type, long size, long pos) { + this.type = type; + this.size = size; + this.pos = pos; + } + + @Override + public ObjectStream openStream() + throws MissingObjectException, IOException { + int bufsz = buffer().length; + packOut.seek(pos); + + InputStream fileStream = new FilterInputStream( + Channels.newInputStream(packOut.file.getChannel())) { + // atEnd was already set to false by the previous seek, but it's + // technically possible for a caller to call insert on the + // inserter in the middle of reading from this stream. Behavior is + // undefined in this case, so it would arguably be ok to ignore, + // but it's not hard to at least make an attempt to not corrupt + // the data. + @Override + public int read() throws IOException { + packOut.atEnd = false; + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException { + packOut.atEnd = false; + return super.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + packOut.atEnd = false; + return super.read(b,off,len); + } + + @Override + public void close() { + // Never close underlying RandomAccessFile, which lasts the + // lifetime of the enclosing PackStream. + } + }; + return new ObjectStream.Filter( + type, size, + new BufferedInputStream( + new InflaterInputStream(fileStream, inflater(), bufsz), bufsz)); + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return size; + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + throw new LargeObjectException.ExceedsLimit( + getStreamFileThreshold(), size); + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,10 +50,12 @@ import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; -/** Keeps track of a {@link PackFile}'s associated .keep file. */ +/** + * Keeps track of a {@link org.eclipse.jgit.internal.storage.file.PackFile}'s + * associated .keep file. + */ public class PackLock { private final File keepFile; - private final FS fs; /** * Create a new lock for a pack file. @@ -67,7 +69,6 @@ final File p = packFile.getParentFile(); final String n = packFile.getName(); keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); //$NON-NLS-1$ - this.fs = fs; } /** @@ -76,7 +77,7 @@ * @param msg * message to store in the file. * @return true if the keep file was successfully written; false otherwise. - * @throws IOException + * @throws java.io.IOException * the keep file could not be written. */ public boolean lock(String msg) throws IOException { @@ -84,7 +85,7 @@ return false; if (!msg.endsWith("\n")) //$NON-NLS-1$ msg += "\n"; //$NON-NLS-1$ - final LockFile lf = new LockFile(keepFile, fs); + final LockFile lf = new LockFile(keepFile); if (!lf.lock()) return false; lf.write(Constants.encode(msg)); @@ -94,7 +95,7 @@ /** * Remove the .keep file that holds this pack in place. * - * @throws IOException + * @throws java.io.IOException * if deletion of .keep file failed */ public void unlock() throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -174,7 +174,7 @@ * offset). * @return offset of the next object in a pack or maxOffset if provided * offset was the last one. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * when there is no object with the provided offset. */ public long findNextOffset(final long offset, final long maxOffset) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ import static org.eclipse.jgit.lib.Constants.CHARSET; import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.LOGS; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; import static org.eclipse.jgit.lib.Constants.PACKED_REFS; import static org.eclipse.jgit.lib.Constants.R_HEADS; @@ -63,22 +64,33 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.io.InterruptedIOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.DigestInputStream; import java.security.MessageDigest; import java.text.MessageFormat; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.ObjectWritingException; import org.eclipse.jgit.events.RefsChangedEvent; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; @@ -102,14 +114,14 @@ import org.slf4j.LoggerFactory; /** - * Traditional file system based {@link RefDatabase}. + * Traditional file system based {@link org.eclipse.jgit.lib.RefDatabase}. *

* This is the classical reference database representation for a Git repository. * References are stored in two formats: loose, and packed. *

* Loose references are stored as individual files within the {@code refs/} * directory. The file name matches the reference name and the file contents is - * the current {@link ObjectId} in string form. + * the current {@link org.eclipse.jgit.lib.ObjectId} in string form. *

* Packed references are stored in a single text file named {@code packed-refs}. * In the packed format, each reference is stored on its own line. This file @@ -134,15 +146,21 @@ Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD, Constants.CHERRY_PICK_HEAD }; + @SuppressWarnings("boxing") + private static final List RETRY_SLEEP_MS = + Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600)); + private final FileRepository parent; private final File gitDir; - private final File refsDir; + final File refsDir; + + final File packedRefsFile; - private final ReflogWriter logWriter; + final File logsDir; - private final File packedRefsFile; + final File logsRefsDir; /** * Immutable sorted list of loose references. @@ -152,10 +170,26 @@ * converted into resolved references during a get operation, ensuring the * live value is always returned. */ - private final AtomicReference> looseRefs = new AtomicReference>(); + private final AtomicReference> looseRefs = new AtomicReference<>(); /** Immutable sorted list of packed references. */ - private final AtomicReference packedRefs = new AtomicReference(); + final AtomicReference packedRefs = new AtomicReference<>(); + + /** + * Lock for coordinating operations within a single process that may contend + * on the {@code packed-refs} file. + *

+ * All operations that write {@code packed-refs} must still acquire a + * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired + * this lock, since there may be multiple {@link RefDirectory} instances or + * other processes operating on the same repo on disk. + *

+ * This lock exists so multiple threads in the same process can wait in a fair + * queue without trying, failing, and retrying to acquire the on-disk lock. If + * {@code RepositoryCache} is used, this lock instance will be used by all + * threads. + */ + final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true); /** * Number of modifications made to this database. @@ -173,49 +207,73 @@ */ private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); + private List retrySleepMs = RETRY_SLEEP_MS; + RefDirectory(final FileRepository db) { final FS fs = db.getFS(); parent = db; gitDir = db.getDirectory(); - logWriter = new ReflogWriter(db); refsDir = fs.resolve(gitDir, R_REFS); + logsDir = fs.resolve(gitDir, LOGS); + logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); packedRefsFile = fs.resolve(gitDir, PACKED_REFS); looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); + packedRefs.set(NO_PACKED_REFS); } Repository getRepository() { return parent; } - ReflogWriter getLogWriter() { - return logWriter; + ReflogWriter newLogWriter(boolean force) { + return new ReflogWriter(this, force); + } + + /** + * Locate the log file on disk for a single reference name. + * + * @param name + * name of the ref, relative to the Git repository top level + * directory (so typically starts with refs/). + * @return the log file location. + */ + public File logFor(String name) { + if (name.startsWith(R_REFS)) { + name = name.substring(R_REFS.length()); + return new File(logsRefsDir, name); + } + return new File(logsDir, name); } + /** {@inheritDoc} */ + @Override public void create() throws IOException { FileUtils.mkdir(refsDir); FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length()))); FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length()))); - logWriter.create(); + newLogWriter(false).create(); } + /** {@inheritDoc} */ @Override public void close() { - // We have no resources to close. + clearReferences(); } - void rescan() { + private void clearReferences() { looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); + packedRefs.set(NO_PACKED_REFS); } + /** {@inheritDoc} */ @Override public void refresh() { super.refresh(); - rescan(); + clearReferences(); } + /** {@inheritDoc} */ @Override public boolean isNameConflicting(String name) throws IOException { RefList packed = getPackedRefs(); @@ -261,6 +319,7 @@ return loose; } + /** {@inheritDoc} */ @Override public Ref exactRef(String name) throws IOException { RefList packed = getPackedRefs(); @@ -285,6 +344,7 @@ return ref; } + /** {@inheritDoc} */ @Override public Ref getRef(final String needle) throws IOException { final RefList packed = getPackedRefs(); @@ -294,6 +354,8 @@ ref = readRef(prefix + needle, packed); if (ref != null) { ref = resolve(ref, 0, null, null, packed); + } + if (ref != null) { break; } } catch (IOException e) { @@ -307,13 +369,13 @@ return ref; } + /** {@inheritDoc} */ @Override public Map getRefs(String prefix) throws IOException { - final RefList packed = getPackedRefs(); final RefList oldLoose = looseRefs.get(); - LooseScanner scan = new LooseScanner(oldLoose); scan.scan(prefix); + final RefList packed = getPackedRefs(); RefList loose; if (scan.newLoose != null) { @@ -347,9 +409,10 @@ return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList()); } + /** {@inheritDoc} */ @Override public List getAdditionalRefs() throws IOException { - List ret = new LinkedList(); + List ret = new LinkedList<>(); for (String name : additionalRefsNames) { Ref r = getRef(name); if (r != null) @@ -368,7 +431,7 @@ private int curIdx; - final RefList.Builder symbolic = new RefList.Builder(4); + final RefList.Builder symbolic = new RefList.Builder<>(4); RefList.Builder newLoose; @@ -481,6 +544,7 @@ } } + /** {@inheritDoc} */ @Override public Ref peel(final Ref ref) throws IOException { final Ref leaf = ref.getLeaf(); @@ -532,6 +596,8 @@ fireRefsChanged(); } + /** {@inheritDoc} */ + @Override public RefDirectoryUpdate newUpdate(String name, boolean detach) throws IOException { boolean detachingSymbolicRef = false; @@ -543,8 +609,6 @@ ref = new ObjectIdRef.Unpeeled(NEW, name, null); else { detachingSymbolicRef = detach && ref.isSymbolic(); - if (detachingSymbolicRef) - ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId()); } RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref); if (detachingSymbolicRef) @@ -552,6 +616,7 @@ return refDirUpdate; } + /** {@inheritDoc} */ @Override public RefDirectoryRename newRename(String fromName, String toName) throws IOException { @@ -560,6 +625,18 @@ return new RefDirectoryRename(from, to); } + /** {@inheritDoc} */ + @Override + public PackedBatchRefUpdate newBatchUpdate() { + return new PackedBatchRefUpdate(this); + } + + /** {@inheritDoc} */ + @Override + public boolean performsAtomicTransactions() { + return true; + } + void stored(RefDirectoryUpdate update, FileSnapshot snapshot) { final ObjectId target = update.getNewObjectId().copy(); final Ref leaf = update.getRef().getLeaf(); @@ -577,7 +654,10 @@ } void delete(RefDirectoryUpdate update) throws IOException { - Ref dst = update.getRef().getLeaf(); + Ref dst = update.getRef(); + if (!update.isDetachingSymbolicRef()) { + dst = dst.getLeaf(); + } String name = dst.getName(); // Write the packed-refs file using an atomic update. We might @@ -585,17 +665,20 @@ // we don't miss an edit made externally. final PackedRefList packed = getPackedRefs(); if (packed.contains(name)) { - LockFile lck = new LockFile(packedRefsFile, - update.getRepository().getFS()); - if (!lck.lock()) - throw new LockFailedException(packedRefsFile); + inProcessPackedRefsLock.lock(); try { - PackedRefList cur = readPackedRefs(); - int idx = cur.find(name); - if (0 <= idx) - commitPackedRefs(lck, cur.remove(idx), packed); + LockFile lck = lockPackedRefsOrThrow(); + try { + PackedRefList cur = readPackedRefs(); + int idx = cur.find(name); + if (0 <= idx) { + commitPackedRefs(lck, cur.remove(idx), packed, true); + } + } finally { + lck.unlock(); + } } finally { - lck.unlock(); + inProcessPackedRefsLock.unlock(); } } @@ -609,7 +692,7 @@ } while (!looseRefs.compareAndSet(curLoose, newLoose)); int levels = levelsIn(name) - 2; - delete(logWriter.logFor(name), levels); + delete(logFor(name), levels); if (dst.getStorage().isLoose()) { update.unlock(); delete(fileFor(name), levels); @@ -628,79 +711,148 @@ * * @param refs * the refs to be added. Must be fully qualified. - * @throws IOException + * @throws java.io.IOException */ public void pack(List refs) throws IOException { - if (refs.size() == 0) - return; + pack(refs, Collections.emptyMap()); + } + + PackedRefList pack(Map heldLocks) throws IOException { + return pack(heldLocks.keySet(), heldLocks); + } + + private PackedRefList pack(Collection refs, + Map heldLocks) throws IOException { + for (LockFile ol : heldLocks.values()) { + ol.requireLock(); + } + if (refs.size() == 0) { + return null; + } FS fs = parent.getFS(); // Lock the packed refs file and read the content - LockFile lck = new LockFile(packedRefsFile, fs); - if (!lck.lock()) - throw new IOException(MessageFormat.format( - JGitText.get().cannotLock, packedRefsFile)); - + inProcessPackedRefsLock.lock(); try { - final PackedRefList packed = getPackedRefs(); - RefList cur = readPackedRefs(); + LockFile lck = lockPackedRefsOrThrow(); + try { + final PackedRefList packed = getPackedRefs(); + RefList cur = readPackedRefs(); - // Iterate over all refs to be packed - for (String refName : refs) { - Ref ref = readRef(refName, cur); - if (ref.isSymbolic()) - continue; // can't pack symbolic refs - // Add/Update it to packed-refs - int idx = cur.find(refName); - if (idx >= 0) - cur = cur.set(idx, peeledPackedRef(ref)); - else - cur = cur.add(idx, peeledPackedRef(ref)); - } + // Iterate over all refs to be packed + boolean dirty = false; + for (String refName : refs) { + Ref oldRef = readRef(refName, cur); + if (oldRef == null) { + continue; // A non-existent ref is already correctly packed. + } + if (oldRef.isSymbolic()) { + continue; // can't pack symbolic refs + } + // Add/Update it to packed-refs + Ref newRef = peeledPackedRef(oldRef); + if (newRef == oldRef) { + // No-op; peeledPackedRef returns the input ref only if it's already + // packed, and readRef returns a packed ref only if there is no + // loose ref. + continue; + } - // The new content for packed-refs is collected. Persist it. - commitPackedRefs(lck, cur, packed); + dirty = true; + int idx = cur.find(refName); + if (idx >= 0) { + cur = cur.set(idx, newRef); + } else { + cur = cur.add(idx, newRef); + } + } + if (!dirty) { + // All requested refs were already packed accurately + return packed; + } - // Now delete the loose refs which are now packed - for (String refName : refs) { - // Lock the loose ref - File refFile = fileFor(refName); - if (!fs.exists(refFile)) - continue; - LockFile rLck = new LockFile(refFile, - parent.getFS()); - if (!rLck.lock()) - continue; - try { - LooseRef currentLooseRef = scanRef(null, refName); - if (currentLooseRef == null || currentLooseRef.isSymbolic()) + // The new content for packed-refs is collected. Persist it. + PackedRefList result = commitPackedRefs(lck, cur, packed, + false); + + // Now delete the loose refs which are now packed + for (String refName : refs) { + // Lock the loose ref + File refFile = fileFor(refName); + if (!fs.exists(refFile)) { continue; - Ref packedRef = cur.get(refName); - ObjectId clr_oid = currentLooseRef.getObjectId(); - if (clr_oid != null - && clr_oid.equals(packedRef.getObjectId())) { - RefList curLoose, newLoose; - do { - curLoose = looseRefs.get(); - int idx = curLoose.find(refName); - if (idx < 0) - break; - newLoose = curLoose.remove(idx); - } while (!looseRefs.compareAndSet(curLoose, newLoose)); - int levels = levelsIn(refName) - 2; - delete(fileFor(refName), levels); } - } finally { - rLck.unlock(); + + LockFile rLck = heldLocks.get(refName); + boolean shouldUnlock; + if (rLck == null) { + rLck = new LockFile(refFile); + if (!rLck.lock()) { + continue; + } + shouldUnlock = true; + } else { + shouldUnlock = false; + } + + try { + LooseRef currentLooseRef = scanRef(null, refName); + if (currentLooseRef == null || currentLooseRef.isSymbolic()) { + continue; + } + Ref packedRef = cur.get(refName); + ObjectId clr_oid = currentLooseRef.getObjectId(); + if (clr_oid != null + && clr_oid.equals(packedRef.getObjectId())) { + RefList curLoose, newLoose; + do { + curLoose = looseRefs.get(); + int idx = curLoose.find(refName); + if (idx < 0) { + break; + } + newLoose = curLoose.remove(idx); + } while (!looseRefs.compareAndSet(curLoose, newLoose)); + int levels = levelsIn(refName) - 2; + delete(refFile, levels, rLck); + } + } finally { + if (shouldUnlock) { + rLck.unlock(); + } + } } + // Don't fire refsChanged. The refs have not change, only their + // storage. + return result; + } finally { + lck.unlock(); } - // Don't fire refsChanged. The refs have not change, only their - // storage. } finally { - lck.unlock(); + inProcessPackedRefsLock.unlock(); } } + @Nullable + LockFile lockPackedRefs() throws IOException { + LockFile lck = new LockFile(packedRefsFile); + for (int ms : getRetrySleepMs()) { + sleep(ms); + if (lck.lock()) { + return lck; + } + } + return null; + } + + private LockFile lockPackedRefsOrThrow() throws IOException { + LockFile lck = lockPackedRefs(); + if (lck == null) { + throw new LockFailedException(packedRefsFile); + } + return lck; + } + /** * Make sure a ref is peeled and has the Storage PACKED. If the given ref * has this attributes simply return it. Otherwise create a new peeled @@ -713,21 +865,25 @@ */ private Ref peeledPackedRef(Ref f) throws MissingObjectException, IOException { - if (f.getStorage().isPacked() && f.isPeeled()) + if (f.getStorage().isPacked() && f.isPeeled()) { return f; - if (!f.isPeeled()) + } + if (!f.isPeeled()) { f = peel(f); - if (f.getPeeledObjectId() != null) + } + ObjectId peeledObjectId = f.getPeeledObjectId(); + if (peeledObjectId != null) { return new ObjectIdRef.PeeledTag(PACKED, f.getName(), - f.getObjectId(), f.getPeeledObjectId()); - else + f.getObjectId(), peeledObjectId); + } else { return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(), f.getObjectId()); + } } - void log(final RefUpdate update, final String msg, final boolean deref) + void log(boolean force, RefUpdate update, String msg, boolean deref) throws IOException { - logWriter.log(update, msg, deref); + newLogWriter(force).log(update, msg, deref); } private Ref resolve(final Ref ref, int depth, String prefix, @@ -762,15 +918,21 @@ return ref; } - private PackedRefList getPackedRefs() throws IOException { + PackedRefList getPackedRefs() throws IOException { + boolean trustFolderStat = getRepository().getConfig().getBoolean( + ConfigConstants.CONFIG_CORE_SECTION, + ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true); + final PackedRefList curList = packedRefs.get(); - if (!curList.snapshot.isModified(packedRefsFile)) + if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) { return curList; + } final PackedRefList newList = readPackedRefs(); if (packedRefs.compareAndSet(curList, newList) - && !curList.id.equals(newList.id)) + && !curList.id.equals(newList.id)) { modCnt.incrementAndGet(); + } return newList; } @@ -779,39 +941,40 @@ 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); + try (BufferedReader br = new BufferedReader(new InputStreamReader( + new DigestInputStream(new FileInputStream(packedRefsFile), + digest), + CHARSET))) { + try { + return new PackedRefList(parsePackedRefs(br), snapshot, + ObjectId.fromRaw(digest.digest())); + } catch (IOException e) { + if (FileUtils.isStaleFileHandleInCausalChain(e) + && retries < maxStaleRetries) { + if (LOG.isDebugEnabled()) { + LOG.debug(MessageFormat.format( + JGitText.get().packedRefsHandleIsStale, + Integer.valueOf(retries)), e); + } + retries++; + continue; } - retries++; - continue; + throw e; } - throw e; - } finally { - br.close(); + } catch (FileNotFoundException noPackedRefs) { + if (packedRefsFile.exists()) { + throw noPackedRefs; + } + // Ignore it and leave the new list empty. + return NO_PACKED_REFS; } } } private RefList parsePackedRefs(final BufferedReader br) throws IOException { - RefList.Builder all = new RefList.Builder(); + RefList.Builder all = new RefList.Builder<>(); Ref last = null; boolean peeled = false; boolean needSort = false; @@ -838,6 +1001,11 @@ } int sp = p.indexOf(' '); + if (sp < 0) { + throw new IOException(MessageFormat.format( + JGitText.get().packedRefsCorruptionDetected, + packedRefsFile.getAbsolutePath())); + } ObjectId id = ObjectId.fromString(p.substring(0, sp)); String name = copy(p, sp + 1, p.length()); ObjectIdRef cur; @@ -862,8 +1030,12 @@ return new StringBuilder(end - off).append(src, off, end).toString(); } - private void commitPackedRefs(final LockFile lck, final RefList refs, - final PackedRefList oldPackedList) throws IOException { + PackedRefList commitPackedRefs(final LockFile lck, final RefList refs, + final PackedRefList oldPackedList, boolean changed) + throws IOException { + // Can't just return packedRefs.get() from this method; it might have been + // updated again after writePackedRefs() returns. + AtomicReference result = new AtomicReference<>(); new RefWriter(refs) { @Override protected void writeFile(String name, byte[] content) @@ -885,10 +1057,31 @@ throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name)); byte[] digest = Constants.newMessageDigest().digest(content); - packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs, - lck.getCommitSnapshot(), ObjectId.fromRaw(digest))); + PackedRefList newPackedList = new PackedRefList( + refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest)); + + // This thread holds the file lock, so no other thread or process should + // be able to modify the packed-refs file on disk. If the list changed, + // it means something is very wrong, so throw an exception. + // + // However, we can't use a naive compareAndSet to check whether the + // update was successful, because another thread might _read_ the + // packed refs file that was written out by this thread while holding + // the lock, and update the packedRefs reference to point to that. So + // compare the actual contents instead. + PackedRefList afterUpdate = packedRefs.updateAndGet( + p -> p.id.equals(oldPackedList.id) ? newPackedList : p); + if (!afterUpdate.id.equals(newPackedList.id)) { + throw new ObjectWritingException( + MessageFormat.format(JGitText.get().unableToWrite, name)); + } + if (changed) { + modCnt.incrementAndGet(); + } + result.set(newPackedList); } }.writePackedRefs(); + return result.get(); } private Ref readRef(String name, RefList packed) throws IOException { @@ -925,7 +1118,7 @@ return n; } - private LooseRef scanRef(LooseRef ref, String name) throws IOException { + LooseRef scanRef(LooseRef ref, String name) throws IOException { final File path = fileFor(name); FileSnapshot currentSnapshot = null; @@ -942,7 +1135,10 @@ try { buf = IO.readSome(path, limit); } catch (FileNotFoundException noFile) { - return null; // doesn't exist; not a reference. + if (path.exists() && path.isFile()) { + throw noFile; + } + return null; // doesn't exist or no file; not a reference. } int n = buf.length; @@ -977,7 +1173,7 @@ try { id = ObjectId.fromString(buf, 0); if (ref != null && !ref.isSymbolic() - && ref.getTarget().getObjectId().equals(id)) { + && id.equals(ref.getTarget().getObjectId())) { assert(currentSnapshot != null); currentSnapshot.setClean(otherSnapshot); return ref; @@ -988,10 +1184,8 @@ n--; String content = RawParseUtils.decode(buf, 0, n); - IOException ioException = new IOException(MessageFormat.format(JGitText.get().notARef, - name, content)); - ioException.initCause(notRef); - throw ioException; + throw new IOException(MessageFormat.format(JGitText.get().notARef, + name, content), notRef); } return new LooseUnpeeled(otherSnapshot, name, id); } @@ -1006,8 +1200,33 @@ && buf[4] == ' '; } + /** + * Detect if we are in a clone command execution + * + * @return {@code true} if we are currently cloning a repository + * @throws IOException + */ + boolean isInClone() throws IOException { + return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef(); + } + + private boolean hasDanglingHead() throws IOException { + Ref head = exactRef(Constants.HEAD); + if (head != null) { + ObjectId id = head.getObjectId(); + return id == null || id.equals(ObjectId.zeroId()); + } + return false; + } + + private boolean hasLooseRef() throws IOException { + try (Stream stream = Files.walk(refsDir.toPath())) { + return stream.anyMatch(Files::isRegularFile); + } + } + /** If the parent should fire listeners, fires them. */ - private void fireRefsChanged() { + void fireRefsChanged() { final int last = lastNotifiedModCnt.get(); final int curr = modCnt.get(); if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0) @@ -1052,33 +1271,110 @@ } static void delete(final File file, final int depth) throws IOException { - if (!file.delete() && file.isFile()) - throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file)); + delete(file, depth, null); + } + + private static void delete(final File file, final int depth, LockFile rLck) + throws IOException { + if (!file.delete() && file.isFile()) { + throw new IOException(MessageFormat.format( + JGitText.get().fileCannotBeDeleted, file)); + } + if (rLck != null) { + rLck.unlock(); // otherwise cannot delete dir below + } File dir = file.getParentFile(); for (int i = 0; i < depth; ++i) { - if (!dir.delete()) - break; // ignore problem here + try { + Files.deleteIfExists(dir.toPath()); + } catch (DirectoryNotEmptyException e) { + // Don't log; normal case when there are other refs with the + // same prefix + break; + } catch (IOException e) { + LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath, + dir), e); + break; + } dir = dir.getParentFile(); } } - private static class PackedRefList extends RefList { - static final PackedRefList NO_PACKED_REFS = new PackedRefList( - RefList.emptyList(), FileSnapshot.MISSING_FILE, - ObjectId.zeroId()); + /** + * Get times to sleep while retrying a possibly contentious operation. + *

+ * For retrying an operation that might have high contention, such as locking + * the {@code packed-refs} file, the caller may implement a retry loop using + * the returned values: + * + *

+	 * for (int toSleepMs : getRetrySleepMs()) {
+	 *   sleep(toSleepMs);
+	 *   if (isSuccessful(doSomething())) {
+	 *     return success;
+	 *   }
+	 * }
+	 * return failure;
+	 * 
+ * + * The first value in the returned iterable is 0, and the caller should treat + * a fully-consumed iterator as a timeout. + * + * @return iterable of times, in milliseconds, that the caller should sleep + * before attempting an operation. + */ + Iterable getRetrySleepMs() { + return retrySleepMs; + } + + void setRetrySleepMs(List retrySleepMs) { + if (retrySleepMs == null || retrySleepMs.isEmpty() + || retrySleepMs.get(0).intValue() != 0) { + throw new IllegalArgumentException(); + } + this.retrySleepMs = retrySleepMs; + } + + /** + * Sleep with {@link Thread#sleep(long)}, converting {@link + * InterruptedException} to {@link InterruptedIOException}. + * + * @param ms + * time to sleep, in milliseconds; zero or negative is a no-op. + * @throws InterruptedIOException + * if sleeping was interrupted. + */ + static void sleep(long ms) throws InterruptedIOException { + if (ms <= 0) { + return; + } + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + InterruptedIOException ie = new InterruptedIOException(); + ie.initCause(e); + throw ie; + } + } - final FileSnapshot snapshot; + static class PackedRefList extends RefList { - final ObjectId id; + private final FileSnapshot snapshot; + + private final ObjectId id; - PackedRefList(RefList src, FileSnapshot s, ObjectId i) { + private PackedRefList(RefList src, FileSnapshot s, ObjectId i) { super(src); snapshot = s; id = i; } } + private static final PackedRefList NO_PACKED_REFS = new PackedRefList( + RefList.emptyList(), FileSnapshot.MISSING_FILE, + ObjectId.zeroId()); + private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot, String name, String target) { Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); @@ -1095,16 +1391,18 @@ implements LooseRef { private final FileSnapshot snapShot; - LoosePeeledTag(FileSnapshot snapshot, String refName, ObjectId id, - ObjectId p) { + LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName, + @NonNull ObjectId id, @NonNull ObjectId p) { super(LOOSE, refName, id, p); this.snapShot = snapshot; } + @Override public FileSnapshot getSnapShot() { return snapShot; } + @Override public LooseRef peel(ObjectIdRef newLeaf) { return this; } @@ -1114,15 +1412,18 @@ implements LooseRef { private final FileSnapshot snapShot; - LooseNonTag(FileSnapshot snapshot, String refName, ObjectId id) { + LooseNonTag(FileSnapshot snapshot, @NonNull String refName, + @NonNull ObjectId id) { super(LOOSE, refName, id); this.snapShot = snapshot; } + @Override public FileSnapshot getSnapShot() { return snapShot; } + @Override public LooseRef peel(ObjectIdRef newLeaf) { return this; } @@ -1132,22 +1433,36 @@ implements LooseRef { private FileSnapshot snapShot; - LooseUnpeeled(FileSnapshot snapShot, String refName, ObjectId id) { + LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName, + @NonNull ObjectId id) { super(LOOSE, refName, id); this.snapShot = snapShot; } + @Override public FileSnapshot getSnapShot() { return snapShot; } + @NonNull + @Override + public ObjectId getObjectId() { + ObjectId id = super.getObjectId(); + assert id != null; // checked in constructor + return id; + } + + @Override public LooseRef peel(ObjectIdRef newLeaf) { - if (newLeaf.getPeeledObjectId() != null) + ObjectId peeledObjectId = newLeaf.getPeeledObjectId(); + ObjectId objectId = getObjectId(); + if (peeledObjectId != null) { return new LoosePeeledTag(snapShot, getName(), - getObjectId(), newLeaf.getPeeledObjectId()); - else + objectId, peeledObjectId); + } else { return new LooseNonTag(snapShot, getName(), - getObjectId()); + objectId); + } } } @@ -1155,15 +1470,18 @@ LooseRef { private final FileSnapshot snapShot; - LooseSymbolicRef(FileSnapshot snapshot, String refName, Ref target) { + LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName, + @NonNull Ref target) { super(refName, target); this.snapShot = snapshot; } + @Override public FileSnapshot getSnapShot() { return snapShot; } + @Override public LooseRef peel(ObjectIdRef newLeaf) { // We should never try to peel the symbolic references. throw new UnsupportedOperationException(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.StandardCopyOption; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -54,6 +56,8 @@ import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Rename any reference stored by {@link RefDirectory}. @@ -66,6 +70,9 @@ * directory that happens to match the source name. */ class RefDirectoryRename extends RefRename { + private static final Logger LOG = LoggerFactory + .getLogger(RefDirectoryRename.class); + private final RefDirectory refdb; /** @@ -88,6 +95,7 @@ refdb = src.getRefDatabase(); } + /** {@inheritDoc} */ @Override protected Result doRename() throws IOException { if (source.getRef().isSymbolic()) @@ -181,8 +189,8 @@ } private boolean renameLog(RefUpdate src, RefUpdate dst) { - File srcLog = refdb.getLogWriter().logFor(src.getName()); - File dstLog = refdb.getLogWriter().logFor(dst.getName()); + File srcLog = refdb.logFor(src.getName()); + File dstLog = refdb.logFor(dst.getName()); if (!srcLog.exists()) return true; @@ -201,13 +209,25 @@ } private static boolean rename(File src, File dst) { - if (src.renameTo(dst)) + try { + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); return true; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + // ignore + } File dir = dst.getParentFile(); if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) return false; - return src.renameTo(dst); + try { + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); + return true; + } catch (IOException e) { + LOG.error(e.getMessage(), e); + return false; + } } private boolean linkHEAD(RefUpdate target) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,12 +50,14 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.Repository; /** Updates any reference stored by {@link RefDirectory}. */ class RefDirectoryUpdate extends RefUpdate { private final RefDirectory database; + private boolean shouldDeref; private LockFile lock; RefDirectoryUpdate(final RefDirectory r, final Ref ref) { @@ -63,23 +65,27 @@ database = r; } + /** {@inheritDoc} */ @Override protected RefDirectory getRefDatabase() { return database; } + /** {@inheritDoc} */ @Override protected Repository getRepository() { return database.getRepository(); } + /** {@inheritDoc} */ @Override protected boolean tryLock(boolean deref) throws IOException { + shouldDeref = deref; Ref dst = getRef(); if (deref) dst = dst.getLeaf(); String name = dst.getName(); - lock = new LockFile(database.fileFor(name), getRepository().getFS()); + lock = new LockFile(database.fileFor(name)); if (lock.lock()) { dst = database.getRef(name); setOldObjectId(dst != null ? dst.getObjectId() : null); @@ -89,6 +95,7 @@ } } + /** {@inheritDoc} */ @Override protected void unlock() { if (lock != null) { @@ -97,6 +104,7 @@ } } + /** {@inheritDoc} */ @Override protected Result doUpdate(final Result status) throws IOException { WriteConfig wc = database.getRepository().getConfig() @@ -117,7 +125,7 @@ msg = strResult; } } - database.log(this, msg, true); + database.log(isForceRefLog(), this, msg, shouldDeref); } if (!lock.commit()) return Result.LOCK_FAILURE; @@ -125,26 +133,28 @@ return status; } - private String toResultString(final Result status) { + private String toResultString(Result status) { switch (status) { case FORCED: - return "forced-update"; //$NON-NLS-1$ + return ReflogEntry.PREFIX_FORCED_UPDATE; case FAST_FORWARD: - return "fast forward"; //$NON-NLS-1$ + return ReflogEntry.PREFIX_FAST_FORWARD; case NEW: - return "created"; //$NON-NLS-1$ + return ReflogEntry.PREFIX_CREATED; default: return null; } } + /** {@inheritDoc} */ @Override protected Result doDelete(final Result status) throws IOException { - if (getRef().getLeaf().getStorage() != Ref.Storage.NEW) + if (getRef().getStorage() != Ref.Storage.NEW) database.delete(this); return status; } + /** {@inheritDoc} */ @Override protected Result doLink(final String target) throws IOException { WriteConfig wc = database.getRepository().getConfig() @@ -156,7 +166,7 @@ String msg = getRefLogMessage(); if (msg != null) - database.log(this, msg, false); + database.log(isForceRefLog(), this, msg, false); if (!lock.commit()) return Result.LOCK_FAILURE; database.storedSymbolicRef(this, lock.getCommitSnapshot(), target); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,6 +93,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getOldId() */ + /** {@inheritDoc} */ + @Override public ObjectId getOldId() { return oldId; } @@ -100,6 +102,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getNewId() */ + /** {@inheritDoc} */ + @Override public ObjectId getNewId() { return newId; } @@ -107,6 +111,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getWho() */ + /** {@inheritDoc} */ + @Override public PersonIdent getWho() { return who; } @@ -114,10 +120,13 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getComment() */ + /** {@inheritDoc} */ + @Override public String getComment() { return comment; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -128,10 +137,12 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#parseCheckout() */ + /** {@inheritDoc} */ + @Override public CheckoutEntry parseCheckout() { if (getComment().startsWith(CheckoutEntryImpl.CHECKOUT_MOVING_FROM)) return new CheckoutEntryImpl(this); else return null; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,6 +74,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getLastEntry() */ + /** {@inheritDoc} */ + @Override public ReflogEntry getLastEntry() throws IOException { return getReverseEntry(0); } @@ -81,6 +83,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries() */ + /** {@inheritDoc} */ + @Override public List getReverseEntries() throws IOException { return getReverseEntries(Integer.MAX_VALUE); } @@ -88,6 +92,8 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntry(int) */ + /** {@inheritDoc} */ + @Override public ReflogEntry getReverseEntry(int number) throws IOException { if (number < 0) throw new IllegalArgumentException(); @@ -96,6 +102,9 @@ try { log = IO.readFully(logName); } catch (FileNotFoundException e) { + if (logName.exists()) { + throw e; + } return null; } @@ -113,16 +122,21 @@ /* (non-Javadoc) * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries(int) */ + /** {@inheritDoc} */ + @Override public List getReverseEntries(int max) throws IOException { final byte[] log; try { log = IO.readFully(logName); } catch (FileNotFoundException e) { + if (logName.exists()) { + throw e; + } return Collections.emptyList(); } int rs = RawParseUtils.prevLF(log, log.length); - List ret = new ArrayList(); + List ret = new ArrayList<>(); while (rs >= 0 && max-- > 0) { rs = RawParseUtils.prevLF(log, rs); ReflogEntry entry = new ReflogEntryImpl(log, rs < 0 ? 0 : rs + 2); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,11 +46,11 @@ package org.eclipse.jgit.internal.storage.file; import static org.eclipse.jgit.lib.Constants.HEAD; -import static org.eclipse.jgit.lib.Constants.LOGS; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_NOTES; import static org.eclipse.jgit.lib.Constants.R_REFS; import static org.eclipse.jgit.lib.Constants.R_REMOTES; -import static org.eclipse.jgit.lib.Constants.R_STASH; import java.io.File; import java.io.FileNotFoundException; @@ -68,112 +68,81 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogEntry; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; /** - * Utility for writing reflog entries - * - * @since 2.0 + * Utility for writing reflog entries using the traditional one-file-per-log + * format. */ public class ReflogWriter { /** - * Get the ref name to be used for when locking a ref's log for rewriting + * Get the ref name to be used for when locking a ref's log for rewriting. * * @param name * name of the ref, relative to the Git repository top level * directory (so typically starts with refs/). - * @return the name of the ref's lock ref + * @return the name of the ref's lock ref. */ - public static String refLockFor(final String name) { - return name + LockFile.SUFFIX; + public static String refLockFor(String name) { + return name + LOCK_SUFFIX; } - private final Repository parent; - - private final File logsDir; - - private final File logsRefsDir; + private final RefDirectory refdb; private final boolean forceWrite; /** - * Create write for repository + * Create writer for ref directory. * - * @param repository + * @param refdb + * a {@link org.eclipse.jgit.internal.storage.file.RefDirectory} + * object. */ - public ReflogWriter(final Repository repository) { - this(repository, false); + public ReflogWriter(RefDirectory refdb) { + this(refdb, false); } /** - * Create write for repository + * Create writer for ref directory. * - * @param repository + * @param refdb + * a {@link org.eclipse.jgit.internal.storage.file.RefDirectory} + * object. * @param forceWrite * true to write to disk all entries logged, false to respect the - * repository's config and current log file status + * repository's config and current log file status. */ - public ReflogWriter(final Repository repository, final boolean forceWrite) { - final FS fs = repository.getFS(); - parent = repository; - File gitDir = repository.getDirectory(); - logsDir = fs.resolve(gitDir, LOGS); - logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); + public ReflogWriter(RefDirectory refdb, boolean forceWrite) { + this.refdb = refdb; this.forceWrite = forceWrite; } /** - * Get repository that reflog is being written for - * - * @return file repository - */ - public Repository getRepository() { - return parent; - } - - /** - * Create the log directories + * Create the log directories. * - * @throws IOException - * @return this writer + * @throws java.io.IOException + * @return this writer. */ public ReflogWriter create() throws IOException { - FileUtils.mkdir(logsDir); - FileUtils.mkdir(logsRefsDir); - FileUtils.mkdir(new File(logsRefsDir, - R_HEADS.substring(R_REFS.length()))); + FileUtils.mkdir(refdb.logsDir); + FileUtils.mkdir(refdb.logsRefsDir); + FileUtils.mkdir( + new File(refdb.logsRefsDir, R_HEADS.substring(R_REFS.length()))); return this; } /** - * Locate the log file on disk for a single reference name. - * - * @param name - * name of the ref, relative to the Git repository top level - * directory (so typically starts with refs/). - * @return the log file location. - */ - public File logFor(String name) { - if (name.startsWith(R_REFS)) { - name = name.substring(R_REFS.length()); - return new File(logsRefsDir, name); - } - return new File(logsDir, name); - } - - /** - * Write the given {@link ReflogEntry} entry to the ref's log + * Write the given entry to the ref's log. * * @param refName - * + * a {@link java.lang.String} object. * @param entry + * a {@link org.eclipse.jgit.lib.ReflogEntry} object. * @return this writer - * @throws IOException + * @throws java.io.IOException */ - public ReflogWriter log(final String refName, final ReflogEntry entry) + public ReflogWriter log(String refName, ReflogEntry entry) throws IOException { return log(refName, entry.getOldId(), entry.getNewId(), entry.getWho(), entry.getComment()); @@ -183,42 +152,49 @@ * Write the given entry information to the ref's log * * @param refName + * ref name * @param oldId + * old object id * @param newId + * new object id * @param ident + * a {@link org.eclipse.jgit.lib.PersonIdent} * @param message + * reflog message * @return this writer - * @throws IOException + * @throws java.io.IOException */ - public ReflogWriter log(final String refName, final ObjectId oldId, - final ObjectId newId, final PersonIdent ident, final String message) - throws IOException { + public ReflogWriter log(String refName, ObjectId oldId, + ObjectId newId, PersonIdent ident, String message) throws IOException { byte[] encoded = encode(oldId, newId, ident, message); return log(refName, encoded); } /** - * Write the given ref update to the ref's log + * Write the given ref update to the ref's log. * * @param update + * a {@link org.eclipse.jgit.lib.RefUpdate} * @param msg + * reflog message * @param deref + * whether to dereference symbolic refs * @return this writer - * @throws IOException + * @throws java.io.IOException */ - public ReflogWriter log(final RefUpdate update, final String msg, - final boolean deref) throws IOException { - final ObjectId oldId = update.getOldObjectId(); - final ObjectId newId = update.getNewObjectId(); - final Ref ref = update.getRef(); + public ReflogWriter log(RefUpdate update, String msg, + boolean deref) throws IOException { + ObjectId oldId = update.getOldObjectId(); + ObjectId newId = update.getNewObjectId(); + Ref ref = update.getRef(); PersonIdent ident = update.getRefLogIdent(); if (ident == null) - ident = new PersonIdent(parent); + ident = new PersonIdent(refdb.getRepository()); else ident = new PersonIdent(ident); - final byte[] rec = encode(oldId, newId, ident, msg); + byte[] rec = encode(oldId, newId, ident, msg); if (deref && ref.isSymbolic()) { log(ref.getName(), rec); log(ref.getLeaf().getName(), rec); @@ -230,33 +206,34 @@ private byte[] encode(ObjectId oldId, ObjectId newId, PersonIdent ident, String message) { - final StringBuilder r = new StringBuilder(); + StringBuilder r = new StringBuilder(); r.append(ObjectId.toString(oldId)); r.append(' '); r.append(ObjectId.toString(newId)); r.append(' '); r.append(ident.toExternalString()); r.append('\t'); - r.append(message.replace("\r\n", " ").replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + r.append( + message.replace("\r\n", " ") //$NON-NLS-1$ //$NON-NLS-2$ + .replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$ r.append('\n'); return Constants.encode(r.toString()); } - private ReflogWriter log(final String refName, final byte[] rec) - throws IOException { - final File log = logFor(refName); - final boolean write = forceWrite + private ReflogWriter log(String refName, byte[] rec) throws IOException { + File log = refdb.logFor(refName); + boolean write = forceWrite || (isLogAllRefUpdates() && shouldAutoCreateLog(refName)) || log.isFile(); if (!write) return this; - WriteConfig wc = getRepository().getConfig().get(WriteConfig.KEY); + WriteConfig wc = refdb.getRepository().getConfig().get(WriteConfig.KEY); FileOutputStream out; try { out = new FileOutputStream(log, true); } catch (FileNotFoundException err) { - final File dir = log.getParentFile(); + File dir = log.getParentFile(); if (dir.exists()) throw err; if (!dir.mkdirs() && !dir.isDirectory()) @@ -280,13 +257,14 @@ } private boolean isLogAllRefUpdates() { - return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); + return refdb.getRepository().getConfig().get(CoreConfig.KEY) + .isLogAllRefUpdates(); } - private boolean shouldAutoCreateLog(final String refName) { - return refName.equals(HEAD) // - || refName.startsWith(R_HEADS) // - || refName.startsWith(R_REMOTES) // - || refName.equals(R_STASH); + private boolean shouldAutoCreateLog(String refName) { + return refName.equals(HEAD) + || refName.startsWith(R_HEADS) + || refName.startsWith(R_REMOTES) + || refName.startsWith(R_NOTES); } } \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,69 +64,106 @@ this.fd = fd; } + /** {@inheritDoc} */ + @Override public int readInt() throws IOException { readFully(buf, 0, 4); return NB.decodeInt32(buf, 0); } + /** {@inheritDoc} */ + @Override public long readLong() throws IOException { readFully(buf, 0, 8); return NB.decodeInt64(buf, 0); } + /** + * Read unsigned int + * + * @return a long. + * @throws java.io.IOException + * if any. + */ public long readUnsignedInt() throws IOException { readFully(buf, 0, 4); return NB.decodeUInt32(buf, 0); } + /** {@inheritDoc} */ + @Override public void readFully(byte[] b) throws IOException { readFully(b, 0, b.length); } + /** {@inheritDoc} */ + @Override public void readFully(byte[] b, int off, int len) throws IOException { IO.readFully(fd, b, off, len); } + /** {@inheritDoc} */ + @Override public int skipBytes(int n) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public boolean readBoolean() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public byte readByte() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public int readUnsignedByte() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public short readShort() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public int readUnsignedShort() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public char readChar() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public float readFloat() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public double readDouble() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public String readLine() throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public String readUTF() throws IOException { throw new UnsupportedOperationException(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,62 +64,90 @@ this.fd = fd; } + /** {@inheritDoc} */ + @Override public void writeShort(int v) throws IOException { NB.encodeInt16(buf, 0, v); fd.write(buf, 0, 2); } + /** {@inheritDoc} */ + @Override public void writeInt(int v) throws IOException { NB.encodeInt32(buf, 0, v); fd.write(buf, 0, 4); } + /** {@inheritDoc} */ + @Override public void writeLong(long v) throws IOException { NB.encodeInt64(buf, 0, v); fd.write(buf, 0, 8); } + /** {@inheritDoc} */ + @Override public void write(int b) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void write(byte[] b) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void write(byte[] b, int off, int len) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeBoolean(boolean v) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeByte(int v) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeChar(int v) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeFloat(float v) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeDouble(double v) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeBytes(String s) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeChars(String s) throws IOException { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ + @Override public void writeUTF(String s) throws IOException { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -100,7 +100,7 @@ final int bits; Table(int bits) { - this.ids = new AtomicReferenceArray(1 << bits); + this.ids = new AtomicReferenceArray<>(1 << bits); this.shift = 32 - bits; this.bits = bits; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -86,7 +86,7 @@ * expected ObjectId of the object, used only for error reporting * in exceptions. * @return loader to read the inflated contents. - * @throws IOException + * @throws java.io.IOException * the object cannot be parsed. */ public static ObjectLoader parse(byte[] raw, AnyObjectId id) @@ -232,7 +232,7 @@ } } - private static void checkValidEndOfStream(InputStream in, Inflater inf, + static void checkValidEndOfStream(InputStream in, Inflater inf, AnyObjectId id, final byte[] buf) throws IOException, CorruptObjectException { for (;;) { @@ -266,7 +266,7 @@ } } - private static boolean isStandardFormat(final byte[] hdr) { + static boolean isStandardFormat(final byte[] hdr) { /* * We must determine if the buffer contains the standard * zlib-deflated stream or the experimental format based @@ -298,7 +298,7 @@ return (fb & 0x8f) == 0x08 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0; } - private static InputStream inflate(final InputStream in, final long size, + static InputStream inflate(final InputStream in, final long size, final ObjectId id) { final Inflater inf = InflaterCache.get(); return new InflaterInputStream(in, inf) { @@ -334,11 +334,11 @@ return new InflaterInputStream(in, inf, BUFFER_SIZE); } - private static BufferedInputStream buffer(InputStream in) { + static BufferedInputStream buffer(InputStream in) { return new BufferedInputStream(in, BUFFER_SIZE); } - private static int readSome(InputStream in, final byte[] hdr, int off, + static int readSome(InputStream in, final byte[] hdr, int off, int cnt) throws IOException { int avail = 0; while (0 < cnt) { @@ -363,7 +363,7 @@ private final FileObjectDatabase source; - private LargeObject(int type, long size, File path, AnyObjectId id, + LargeObject(int type, long size, File path, AnyObjectId id, FileObjectDatabase db) { this.type = type; this.size = size; @@ -399,6 +399,9 @@ try { in = buffer(new FileInputStream(path)); } catch (FileNotFoundException gone) { + if (path.exists()) { + throw gone; + } // If the loose file no longer exists, it may have been // moved into a pack file in the mean time. Try again // to locate the object. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,8 @@ import org.eclipse.jgit.storage.file.WindowCacheConfig; /** - * Caches slices of a {@link PackFile} in memory for faster read access. + * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in + * memory for faster read access. *

* The WindowCache serves as a Java based "buffer cache", loading segments of a * PackFile into the JVM heap prior to use. As JGit often wants to do reads of @@ -111,8 +112,9 @@ * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching * decrements during {@link #clear(Ref)}. Implementors may need to override * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional - * accounting information into an implementation specific {@link Ref} subclass, - * as the cached entity may have already been evicted by the JRE's garbage + * accounting information into an implementation specific + * {@link org.eclipse.jgit.internal.storage.file.WindowCache.Ref} subclass, as + * the cached entity may have already been evicted by the JRE's garbage * collector. *

* To maintain higher concurrency workloads, during eviction only one thread @@ -150,7 +152,7 @@ * @deprecated use {@code cfg.install()} to avoid internal reference. * @param cfg * the new window cache configuration. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the cache configuration contains one or more invalid * settings, usually too low of a limit. */ @@ -169,7 +171,10 @@ return streamFileThreshold; } - static WindowCache getInstance() { + /** + * @return the cached instance. + */ + public static WindowCache getInstance() { return cache; } @@ -235,9 +240,9 @@ if (lockCount < 1) throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1); - queue = new ReferenceQueue(); + queue = new ReferenceQueue<>(); clock = new AtomicLong(1); - table = new AtomicReferenceArray(tableSize); + table = new AtomicReferenceArray<>(tableSize); locks = new Lock[lockCount]; for (int i = 0; i < locks.length; i++) locks[i] = new Lock(); @@ -267,11 +272,17 @@ throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); } - int getOpenFiles() { + /** + * @return the number of open files. + */ + public int getOpenFiles() { return openFiles.get(); } - long getOpenBytes() { + /** + * @return the number of open bytes. + */ + public long getOpenBytes() { return openBytes.get(); } @@ -496,31 +507,16 @@ private void gc() { Ref r; while ((r = (Ref) queue.poll()) != null) { - // Sun's Java 5 and 6 implementation have a bug where a Reference - // can be enqueued and dequeued twice on the same reference queue - // due to a race condition within ReferenceQueue.enqueue(Reference). - // - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858 - // - // We CANNOT permit a Reference to come through us twice, as it will - // skew the resource counters we maintain. Our canClear() check here - // provides a way to skip the redundant dequeues, if any. - // - if (r.canClear()) { - clear(r); + clear(r); - boolean found = false; - final int s = slot(r.pack, r.position); - final Entry e1 = table.get(s); - for (Entry n = e1; n != null; n = n.next) { - if (n.ref == r) { - n.dead = true; - found = true; - break; - } - } - if (found) + final int s = slot(r.pack, r.position); + final Entry e1 = table.get(s); + for (Entry n = e1; n != null; n = n.next) { + if (n.ref == r) { + n.dead = true; table.compareAndSet(s, e1, clean(e1)); + break; + } } } } @@ -581,8 +577,6 @@ long lastAccess; - private boolean cleared; - protected Ref(final PackFile pack, final long position, final ByteWindow v, final ReferenceQueue queue) { super(v, queue); @@ -590,13 +584,6 @@ this.position = position; this.size = v.size(); } - - final synchronized boolean canClear() { - if (cleared) - return false; - cleared = true; - return true; - } } private static final class Lock { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import java.util.zip.DataFormatException; import java.util.zip.Inflater; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; @@ -69,6 +70,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; 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.ProgressMonitor; @@ -84,10 +86,22 @@ private DeltaBaseCache baseCache; + @Nullable + private final ObjectInserter createdFromInserter; + final FileObjectDatabase db; WindowCursor(FileObjectDatabase db) { this.db = db; + this.createdFromInserter = null; + this.streamFileThreshold = WindowCache.getStreamFileThreshold(); + } + + WindowCursor(FileObjectDatabase db, + @Nullable ObjectDirectoryInserter createdFromInserter) { + this.db = db; + this.createdFromInserter = createdFromInserter; + this.streamFileThreshold = WindowCache.getStreamFileThreshold(); } DeltaBaseCache getDeltaBaseCache() { @@ -96,11 +110,13 @@ return baseCache; } + /** {@inheritDoc} */ @Override public ObjectReader newReader() { return new WindowCursor(db); } + /** {@inheritDoc} */ @Override public BitmapIndex getBitmapIndex() throws IOException { for (PackFile pack : db.getPacks()) { @@ -111,6 +127,8 @@ return null; } + /** {@inheritDoc} */ + @Override public Collection getCachedPacksAndUpdate( BitmapBuilder needBitmap) throws IOException { for (PackFile pack : db.getPacks()) { @@ -122,20 +140,25 @@ return Collections.emptyList(); } + /** {@inheritDoc} */ @Override public Collection resolve(AbbreviatedObjectId id) throws IOException { if (id.isComplete()) return Collections.singleton(id.toObjectId()); - HashSet matches = new HashSet(4); + HashSet matches = new HashSet<>(4); db.resolve(matches, id); return matches; } + /** {@inheritDoc} */ + @Override public boolean has(AnyObjectId objectId) throws IOException { return db.has(objectId); } + /** {@inheritDoc} */ + @Override public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -151,11 +174,14 @@ return ldr; } + /** {@inheritDoc} */ @Override public Set getShallowCommits() throws IOException { return db.getShallowCommits(); } + /** {@inheritDoc} */ + @Override public long getObjectSize(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -169,10 +195,14 @@ return sz; } + /** {@inheritDoc} */ + @Override public LocalObjectToPack newObjectToPack(AnyObjectId objectId, int type) { return new LocalObjectToPack(objectId, type); } + /** {@inheritDoc} */ + @Override public void selectObjectRepresentation(PackWriter packer, ProgressMonitor monitor, Iterable objects) throws IOException, MissingObjectException { @@ -182,6 +212,8 @@ } } + /** {@inheritDoc} */ + @Override public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp, boolean validate) throws IOException, StoredObjectRepresentationNotAvailableException { @@ -189,6 +221,8 @@ src.pack.copyAsIs(out, src, validate, this); } + /** {@inheritDoc} */ + @Override public void writeObjects(PackOutputStream out, List list) throws IOException { for (ObjectToPack otp : list) @@ -231,6 +265,8 @@ return cnt - need; } + /** {@inheritDoc} */ + @Override public void copyPackAsIs(PackOutputStream out, CachedPack pack) throws IOException { ((LocalCachedPack) pack).copyAsIs(out, this); @@ -325,11 +361,18 @@ } } - int getStreamFileThreshold() { - return WindowCache.getStreamFileThreshold(); + /** {@inheritDoc} */ + @Override + @Nullable + public ObjectInserter getCreatedFromInserter() { + return createdFromInserter; } - /** Release the current window cursor. */ + /** + * {@inheritDoc} + *

+ * Release the current window cursor. + */ @Override public void close() { window = null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,11 +49,7 @@ class WriteConfig { /** Key for {@link Config#get(SectionParser)}. */ - static final Config.SectionParser KEY = new SectionParser() { - public WriteConfig parse(final Config cfg) { - return new WriteConfig(cfg); - } - }; + static final Config.SectionParser KEY = WriteConfig::new; private final int compression; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017, 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.internal.storage.io; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * Provides content blocks of file. + *

+ * {@code BlockSource} implementations must decide if they will be thread-safe, + * or not. + */ +public abstract class BlockSource implements AutoCloseable { + /** + * Wrap a byte array as a {@code BlockSource}. + * + * @param content + * input file. + * @return block source to read from {@code content}. + */ + public static BlockSource from(byte[] content) { + return new BlockSource() { + @Override + public ByteBuffer read(long pos, int cnt) { + ByteBuffer buf = ByteBuffer.allocate(cnt); + if (pos < content.length) { + int p = (int) pos; + int n = Math.min(cnt, content.length - p); + buf.put(content, p, n); + } + return buf; + } + + @Override + public long size() { + return content.length; + } + + @Override + public void close() { + // Do nothing. + } + }; + } + + /** + * Read from a {@code FileInputStream}. + *

+ * The returned {@code BlockSource} is not thread-safe, as it must seek the + * file channel to read a block. + * + * @param in + * the file. The {@code BlockSource} will close {@code in}. + * @return wrapper for {@code in}. + */ + public static BlockSource from(FileInputStream in) { + return from(in.getChannel()); + } + + /** + * Read from a {@code FileChannel}. + *

+ * The returned {@code BlockSource} is not thread-safe, as it must seek the + * file channel to read a block. + * + * @param ch + * the file. The {@code BlockSource} will close {@code ch}. + * @return wrapper for {@code ch}. + */ + public static BlockSource from(FileChannel ch) { + return new BlockSource() { + @Override + public ByteBuffer read(long pos, int blockSize) throws IOException { + ByteBuffer b = ByteBuffer.allocate(blockSize); + ch.position(pos); + int n; + do { + n = ch.read(b); + } while (n > 0 && b.position() < blockSize); + return b; + } + + @Override + public long size() throws IOException { + return ch.size(); + } + + @Override + public void close() { + try { + ch.close(); + } catch (IOException e) { + // Ignore close failures of read-only files. + } + } + }; + } + + /** + * Read a block from the file. + *

+ * To reduce copying, the returned ByteBuffer should have an accessible + * array and {@code arrayOffset() == 0}. The caller will discard the + * ByteBuffer and directly use the backing array. + * + * @param position + * position of the block in the file, specified in bytes from the + * beginning of the file. + * @param blockSize + * size to read. + * @return buffer containing the block content. + * @throws java.io.IOException + * if block cannot be read. + */ + public abstract ByteBuffer read(long position, int blockSize) + throws IOException; + + /** + * Determine the size of the file. + * + * @return total number of bytes in the file. + * @throws java.io.IOException + * if size cannot be obtained. + */ + public abstract long size() throws IOException; + + /** + * Advise the {@code BlockSource} a sequential scan is starting. + * + * @param startPos + * starting position. + * @param endPos + * ending position. + */ + public void adviseSequentialRead(long startPos, long endPos) { + // Do nothing by default. + } + + /** {@inheritDoc} */ + @Override + public abstract void close(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java 2019-09-03 12:37:49.000000000 +0000 @@ -96,7 +96,7 @@ edgeObjects = edges; alreadyProcessed = new IntSet(); - treeCache = new ObjectIdOwnerMap(); + treeCache = new ObjectIdOwnerMap<>(); parser = new CanonicalTreeParser(); idBuf = new MutableObjectId(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,13 +45,17 @@ import java.io.IOException; -/** Describes a pack file {@link ObjectReuseAsIs} can append onto a stream. */ +/** + * Describes a pack file + * {@link org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs} can append + * onto a stream. + */ public abstract class CachedPack { /** * Get the number of objects in this pack. * * @return the total object count for the pack. - * @throws IOException + * @throws java.io.IOException * if the object count cannot be read. */ public abstract long getObjectCount() throws IOException; @@ -70,7 +74,7 @@ * * @return the number of deltas; 0 if the number is not known or there are * no deltas. - * @throws IOException + * @throws java.io.IOException * if the delta count cannot be read. */ public long getDeltaCount() throws IOException { @@ -88,15 +92,16 @@ * only and using its internal state to decide if this object is within this * pack. Implementors should ensure a representation from this cached pack * is tested as part of - * {@link ObjectReuseAsIs#selectObjectRepresentation(PackWriter, org.eclipse.jgit.lib.ProgressMonitor, Iterable)} + * {@link org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs#selectObjectRepresentation(PackWriter, org.eclipse.jgit.lib.ProgressMonitor, Iterable)} * , ensuring this method would eventually return true if the object would * be included by this cached pack. * * @param obj * the object being packed. Can be used as an ObjectId. * @param rep - * representation from the {@link ObjectReuseAsIs} instance that - * originally supplied this CachedPack. + * representation from the + * {@link org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs} + * instance that originally supplied this CachedPack. * @return true if this pack contains this object. */ public abstract boolean hasObject(ObjectToPack obj, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,7 +60,7 @@ DeltaCache(PackConfig pc) { size = pc.getDeltaCacheSize(); entryLimit = pc.getDeltaCacheLimit(); - queue = new ReferenceQueue(); + queue = new ReferenceQueue<>(); } boolean canCache(int length, ObjectToPack src, ObjectToPack res) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,10 @@ import org.eclipse.jgit.lib.Constants; -/** Encodes an instruction stream for {@link BinaryDelta}. */ +/** + * Encodes an instruction stream for + * {@link org.eclipse.jgit.internal.storage.pack.BinaryDelta}. + */ public class DeltaEncoder { /** * Maximum number of bytes to be copied in pack v2 format. @@ -91,7 +94,7 @@ * @param resultSize * size of the resulting object, after applying this instruction * stream to the base object, in bytes. - * @throws IOException + * @throws java.io.IOException * the output buffer cannot store the instruction stream's * header with the size fields. */ @@ -114,7 +117,7 @@ * maximum number of bytes to write to the out buffer declaring * the stream is over limit and should be discarded. May be 0 to * specify an infinite limit. - * @throws IOException + * @throws java.io.IOException * the output buffer cannot store the instruction stream's * header with the size fields. */ @@ -138,7 +141,11 @@ out.write(buf, 0, p); } - /** @return current size of the delta stream, in bytes. */ + /** + * Get current size of the delta stream, in bytes. + * + * @return current size of the delta stream, in bytes. + */ public int getSize() { return size; } @@ -150,7 +157,7 @@ * the string to insert. * @return true if the insert fits within the limit; false if the insert * would cause the instruction stream to exceed the limit. - * @throws IOException + * @throws java.io.IOException * the instruction buffer can't store the instructions. */ public boolean insert(String text) throws IOException { @@ -164,7 +171,7 @@ * the binary to insert. * @return true if the insert fits within the limit; false if the insert * would cause the instruction stream to exceed the limit. - * @throws IOException + * @throws java.io.IOException * the instruction buffer can't store the instructions. */ public boolean insert(byte[] text) throws IOException { @@ -182,7 +189,7 @@ * number of bytes to insert. * @return true if the insert fits within the limit; false if the insert * would cause the instruction stream to exceed the limit. - * @throws IOException + * @throws java.io.IOException * the instruction buffer can't store the instructions. */ public boolean insert(byte[] text, int off, int cnt) @@ -217,7 +224,7 @@ * number of bytes to copy. * @return true if the copy fits within the limit; false if the copy * would cause the instruction stream to exceed the limit. - * @throws IOException + * @throws java.io.IOException * the instruction buffer cannot store the instructions. */ public boolean copy(long offset, int cnt) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,8 +51,9 @@ *

* The index can be passed a result buffer, and output an instruction sequence * that transforms the source buffer used by the index into the result buffer. - * The instruction sequence can be executed by {@link BinaryDelta} to recreate - * the result buffer. + * The instruction sequence can be executed by + * {@link org.eclipse.jgit.internal.storage.pack.BinaryDelta} to recreate the + * result buffer. *

* An index stores the entire contents of the source buffer, but also a table of * block identities mapped to locations where the block appears in the source @@ -191,7 +192,11 @@ } } - /** @return size of the source buffer this index has scanned. */ + /** + * Get size of the source buffer this index has scanned. + * + * @return size of the source buffer this index has scanned. + */ public long getSourceSize() { return src.length; } @@ -244,7 +249,7 @@ * the desired result buffer. The generated instructions will * recreate this buffer when applied to the source buffer stored * within this index. - * @throws IOException + * @throws java.io.IOException * the output stream refused to write the instructions. */ public void encode(OutputStream out, byte[] res) throws IOException { @@ -274,7 +279,7 @@ * @return true if the delta is smaller than deltaSizeLimit; false if the * encoder aborted because the encoded delta instructions would be * longer than deltaSizeLimit bytes. - * @throws IOException + * @throws java.io.IOException * the output stream refused to write the instructions. */ public boolean encode(OutputStream out, byte[] res, int deltaSizeLimit) @@ -421,6 +426,8 @@ return start - resPtr; } + /** {@inheritDoc} */ + @Override @SuppressWarnings("nls") public String toString() { String[] units = { "bytes", "KiB", "MiB", "GiB" }; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java 2019-09-03 12:37:49.000000000 +0000 @@ -127,4 +127,4 @@ sz <<= 1; return sz; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,12 +73,12 @@ final int endIndex; private long totalWeight; - private long bytesPerUnit; + long bytesPerUnit; Block(int threads, PackConfig config, ObjectReader reader, DeltaCache dc, ThreadSafeProgressMonitor pm, ObjectToPack[] list, int begin, int end) { - this.tasks = new ArrayList(threads); + this.tasks = new ArrayList<>(threads); this.threads = threads; this.config = config; this.templateReader = reader; @@ -110,10 +110,12 @@ maxWork = s.size(); } } - if (maxTask == null) + if (maxTask == null) { return null; - if (maxTask.tryStealWork(maxSlice)) + } + if (maxTask.tryStealWork(maxSlice)) { return forThread.initWindow(maxSlice); + } } } @@ -138,26 +140,30 @@ for (; w < weightPerThread && i < endIndex;) { if (nextTop < topPaths.size() && i == topPaths.get(nextTop).slice.beginIndex) { - if (s < i) + if (s < i) { task.add(new Slice(s, i)); + } s = i = topPaths.get(nextTop++).slice.endIndex; - } else - w += list[i++].getWeight(); + } else { + w += getAdjustedWeight(list[i++]); + } } // Round up the slice to the end of a path. if (s < i) { int h = list[i - 1].getPathHash(); while (i < endIndex) { - if (h == list[i].getPathHash()) + if (h == list[i].getPathHash()) { i++; - else + } else { break; + } } task.add(new Slice(s, i)); } - if (!task.slices.isEmpty()) + if (!task.slices.isEmpty()) { tasks.add(task); + } } while (topPathItr.hasNext()) { WeightedPath p = topPathItr.next(); @@ -170,12 +176,12 @@ } private ArrayList computeTopPaths() { - ArrayList topPaths = new ArrayList( + ArrayList topPaths = new ArrayList<>( threads); int cp = beginIndex; int ch = list[cp].getPathHash(); - long cw = list[cp].getWeight(); - totalWeight = list[cp].getWeight(); + long cw = getAdjustedWeight(list[cp]); + totalWeight = cw; for (int i = cp + 1; i < endIndex; i++) { ObjectToPack o = list[i]; @@ -184,40 +190,52 @@ if (topPaths.size() < threads) { Slice s = new Slice(cp, i); topPaths.add(new WeightedPath(cw, s)); - if (topPaths.size() == threads) + if (topPaths.size() == threads) { Collections.sort(topPaths); + } } else if (topPaths.get(0).weight < cw) { Slice s = new Slice(cp, i); WeightedPath p = new WeightedPath(cw, s); topPaths.set(0, p); - if (p.compareTo(topPaths.get(1)) > 0) + if (p.compareTo(topPaths.get(1)) > 0) { Collections.sort(topPaths); + } } } cp = i; ch = o.getPathHash(); cw = 0; } - if (o.isEdge() || o.doNotAttemptDelta()) - continue; - cw += o.getWeight(); - totalWeight += o.getWeight(); + int weight = getAdjustedWeight(o); + cw += weight; + totalWeight += weight; } // Sort by starting index to identify gaps later. Collections.sort(topPaths, new Comparator() { + @Override public int compare(WeightedPath a, WeightedPath b) { return a.slice.beginIndex - b.slice.beginIndex; } }); bytesPerUnit = 1; - while (MAX_METER <= (totalWeight / bytesPerUnit)) + while (MAX_METER <= (totalWeight / bytesPerUnit)) { bytesPerUnit <<= 10; + } return topPaths; } } + static int getAdjustedWeight(ObjectToPack o) { + // Edge objects and those with reused deltas do not need to be + // compressed. For compression calculations, ignore their weights. + if (o.isEdge() || o.doNotAttemptDelta()) { + return 0; + } + return o.getWeight(); + } + static final class WeightedPath implements Comparable { final long weight; final Slice slice; @@ -227,10 +245,12 @@ this.slice = s; } + @Override public int compareTo(WeightedPath o) { int cmp = Long.signum(weight - o.weight); - if (cmp != 0) + if (cmp != 0) { return cmp; + } return slice.beginIndex - o.slice.beginIndex; } } @@ -250,14 +270,14 @@ } private final Block block; - private final LinkedList slices; + final LinkedList slices; private ObjectReader or; private DeltaWindow dw; DeltaTask(Block b) { this.block = b; - this.slices = new LinkedList(); + this.slices = new LinkedList<>(); } void add(Slice s) { @@ -272,20 +292,24 @@ slices.add(s); } + /** {@inheritDoc} */ + @Override public Object call() throws Exception { or = block.templateReader.newReader(); try { DeltaWindow w; for (;;) { synchronized (this) { - if (slices.isEmpty()) + if (slices.isEmpty()) { break; + } w = initWindow(slices.removeFirst()); } runWindow(w); } - while ((w = block.stealWork(this)) != null) + while ((w = block.stealWork(this)) != null) { runWindow(w); + } } finally { block.pm.endWorker(); or.close(); @@ -315,8 +339,9 @@ } synchronized Slice remaining() { - if (!slices.isEmpty()) + if (!slices.isEmpty()) { return slices.getLast(); + } DeltaWindow d = dw; return d != null ? d.remaining() : null; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java 2019-09-03 12:37:49.000000000 +0000 @@ -160,6 +160,7 @@ clear(n); } res.set(next); + clearWindowOnTypeSwitch(); if (res.object.isEdge() || res.object.doNotAttemptDelta()) { // We don't actually want to make a delta for @@ -194,6 +195,15 @@ return DeltaIndex.estimateIndexSize(len) - len; } + private void clearWindowOnTypeSwitch() { + DeltaWindowEntry p = res.prev; + if (!p.empty() && res.type() != p.type()) { + for (; p != res; p = p.prev) { + clear(p); + } + } + } + private void clear(DeltaWindowEntry ent) { if (ent.index != null) loaded -= ent.index.getIndexSize(); @@ -258,12 +268,6 @@ private boolean delta(final DeltaWindowEntry src) throws IOException { - // Objects must use only the same type as their delta base. - if (src.type() != res.type()) { - keepInWindow(); - return NEXT_RES; - } - // If the sizes are radically different, this is a bad pairing. if (res.size() < src.size() >>> 4) return NEXT_SRC; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,26 +51,27 @@ import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; -import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; /** - * Extension of {@link ObjectReader} that supports reusing objects in packs. + * Extension of {@link org.eclipse.jgit.lib.ObjectReader} that supports reusing + * objects in packs. *

* {@code ObjectReader} implementations may also optionally implement this - * interface to support {@link PackWriter} with a means of copying an object - * that is already in pack encoding format directly into the output stream, - * without incurring decompression and recompression overheads. + * interface to support + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} with a means of + * copying an object that is already in pack encoding format directly into the + * output stream, without incurring decompression and recompression overheads. */ public interface ObjectReuseAsIs { /** * Allocate a new {@code PackWriter} state structure for an object. *

- * {@link PackWriter} allocates these objects to keep track of the - * per-object state, and how to load the objects efficiently into the - * generated stream. Implementers may subclass this type with additional - * object state, such as to remember what file and offset contains the - * object's pack encoded data. + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} allocates these + * objects to keep track of the per-object state, and how to load the + * objects efficiently into the generated stream. Implementers may subclass + * this type with additional object state, such as to remember what file and + * offset contains the object's pack encoded data. * * @param objectId * the id of the object that will be packed. @@ -85,15 +86,16 @@ *

* Implementations should iterate through all available representations of * an object, and pass them in turn to the PackWriter though - * {@link PackWriter#select(ObjectToPack, StoredObjectRepresentation)} so - * the writer can select the most suitable representation to reuse into the - * output stream. - *

- * If the implementation returns CachedPack from {@link #getCachedPacksAndUpdate(BitmapBuilder)} - * it must consider the representation of any object that is stored in any - * of the offered CachedPacks. PackWriter relies on this behavior to prune - * duplicate objects out of the pack stream when it selects a CachedPack and - * the object was also reached through the thin-pack enumeration. + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#select(ObjectToPack, StoredObjectRepresentation)} + * so the writer can select the most suitable representation to reuse into + * the output stream. + *

+ * If the implementation returns CachedPack from + * {@link #getCachedPacksAndUpdate(BitmapBuilder)} it must consider the + * representation of any object that is stored in any of the offered + * CachedPacks. PackWriter relies on this behavior to prune duplicate + * objects out of the pack stream when it selects a CachedPack and the + * object was also reached through the thin-pack enumeration. *

* The implementation may choose to consider multiple objects at once on * concurrent threads, but must evaluate all representations of an object @@ -106,10 +108,10 @@ * once for each item in the iteration when selection is done. * @param objects * the objects that are being packed. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * there is no representation available for the object, as it is * no longer in the repository. Packing will abort. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed. Packing will abort. */ public void selectObjectRepresentation(PackWriter packer, @@ -136,7 +138,9 @@ * format to reach a delta base that is later in the stream. It may also * reduce data locality for the reader, slowing down data access. * - * Invoking {@link PackOutputStream#writeObject(ObjectToPack)} will cause + * Invoking + * {@link org.eclipse.jgit.internal.storage.pack.PackOutputStream#writeObject(ObjectToPack)} + * will cause * {@link #copyObjectAsIs(PackOutputStream, ObjectToPack, boolean)} to be * invoked recursively on {@code this} if the current object is scheduled * for reuse. @@ -147,7 +151,7 @@ * the list of objects to write. Objects should be written in * approximately this order. Implementors may resort the list * elements in-place during writing if desired. - * @throws IOException + * @throws java.io.IOException * the stream cannot be written to, or one or more required * objects cannot be accessed from the object database. */ @@ -186,13 +190,13 @@ * corrupt before being reused. If false, validation may be * skipped as it will be performed elsewhere in the processing * pipeline. - * @throws StoredObjectRepresentationNotAvailableException + * @throws org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException * the previously selected representation is no longer * available. If thrown before {@code out.writeHeader} the pack * writer will try to find another representation, and write * that one instead. If throw after {@code out.writeHeader}, * packing will abort. - * @throws IOException + * @throws java.io.IOException * the stream's write method threw an exception. Packing will * abort. */ @@ -209,7 +213,7 @@ * stream to append the pack onto. * @param pack * the cached pack to send. - * @throws IOException + * @throws java.io.IOException * the pack cannot be read, or stream did not accept a write. */ public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack) @@ -225,7 +229,7 @@ * @param needBitmap * the bitmap that contains all of the objects the client wants. * @return the available cached packs. - * @throws IOException + * @throws java.io.IOException * the cached packs cannot be listed from the repository. * Callers may choose to ignore this and continue as-if there * were no cached packs. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,8 @@ import org.eclipse.jgit.transport.PackedObjectInfo; /** - * Per-object state used by {@link PackWriter}. + * Per-object state used by + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}. *

* {@code PackWriter} uses this class to track the things it needs to include in * the newly generated pack file, and how to efficiently obtain the raw data for @@ -108,19 +109,25 @@ } /** + * Get delta base object id if object is going to be packed in delta + * representation + * * @return delta base object id if object is going to be packed in delta - * representation; null otherwise - if going to be packed as a - * whole object. + * representation; null otherwise - if going to be packed as a whole + * object. */ public final ObjectId getDeltaBaseId() { return deltaBase; } /** + * Get delta base object to pack if object is going to be packed in delta + * representation and delta is specified as object to pack + * * @return delta base object to pack if object is going to be packed in - * delta representation and delta is specified as object to - * pack; null otherwise - if going to be packed as a whole - * object or delta base is specified only as id. + * delta representation and delta is specified as object to pack; + * null otherwise - if going to be packed as a whole object or delta + * base is specified only as id. */ public final ObjectToPack getDeltaBase() { if (deltaBase instanceof ObjectToPack) @@ -164,8 +171,9 @@ } /** - * @return true if object is going to be written as delta; false - * otherwise. + * Whether object is going to be written as delta + * + * @return true if object is going to be written as delta; false otherwise. */ public final boolean isDeltaRepresentation() { return deltaBase != null; @@ -181,7 +189,8 @@ return 1 < getOffset(); // markWantWrite sets 1. } - /** @return the type of this object. */ + /** {@inheritDoc} */ + @Override public final int getType() { return (flags >> TYPE_SHIFT) & 0x7; } @@ -215,6 +224,9 @@ } /** + * Whether an existing representation was selected to be reused as-is into + * the pack stream. + * * @return true if an existing representation was selected to be reused * as-is into the pack stream. */ @@ -265,7 +277,11 @@ flags &= ~DELTA_ATTEMPTED; } - /** @return the extended flags on this object, in the range [0x0, 0xf]. */ + /** + * Get the extended flags on this object, in the range [0x0, 0xf]. + * + * @return the extended flags on this object, in the range [0x0, 0xf]. + */ protected final int getExtendedFlags() { return (flags >>> EXT_SHIFT) & EXT_MASK; } @@ -362,9 +378,10 @@ * Remember a specific representation for reuse at a later time. *

* Implementers should remember the representation chosen, so it can be - * reused at a later time. {@link PackWriter} may invoke this method - * multiple times for the same object, each time saving the current best - * representation found. + * reused at a later time. + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} may invoke this + * method multiple times for the same object, each time saving the current + * best representation found. * * @param ref * the object representation. @@ -373,6 +390,7 @@ // Empty by default. } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.internal.storage.pack; -/** A pack file extension. */ +/** + * A pack file extension. + */ public class PackExt { private static volatile PackExt[] VALUES = new PackExt[] {}; @@ -53,10 +55,20 @@ /** A pack index file extension. */ public static final PackExt INDEX = newPackExt("idx"); //$NON-NLS-1$ + /** A keep pack file extension. */ + public static final PackExt KEEP = newPackExt("keep"); //$NON-NLS-1$ + /** A pack bitmap index file extension. */ public static final PackExt BITMAP_INDEX = newPackExt("bitmap"); //$NON-NLS-1$ - /** @return all of the PackExt values. */ + /** A reftable file. */ + public static final PackExt REFTABLE = newPackExt("ref"); //$NON-NLS-1$ + + /** + * Get all of the PackExt values. + * + * @return all of the PackExt values. + */ public static PackExt[] values() { return VALUES; } @@ -96,21 +108,34 @@ this.pos = pos; } - /** @return the file extension. */ + /** + * Get the file extension. + * + * @return the file extension. + */ public String getExtension() { return ext; } - /** @return the position of the extension in the values array. */ + /** + * Get the position of the extension in the values array. + * + * @return the position of the extension in the values array. + */ public int getPosition() { return pos; } - /** @return the bit mask of the extension e.g {@code 1 << getPosition()}. */ + /** + * Get the bit mask of the extension e.g {@code 1 << getPosition()}. + * + * @return the bit mask of the extension e.g {@code 1 << getPosition()}. + */ public int getBit() { return 1 << getPosition(); } + /** {@inheritDoc} */ @Override public String toString() { return String.format("PackExt[%s, bit=0x%s]", getExtension(), //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,10 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.util.NB; -/** Custom output stream to support {@link PackWriter}. */ +/** + * Custom output stream to support + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}. + */ public final class PackOutputStream extends OutputStream { private static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024; @@ -84,7 +87,8 @@ *

* This constructor is exposed to support debugging the JGit library only. * Application or storage level code should not create a PackOutputStream, - * instead use {@link PackWriter}, and let the writer create the stream. + * instead use {@link org.eclipse.jgit.internal.storage.pack.PackWriter}, + * and let the writer create the stream. * * @param writeMonitor * monitor to update on object output progress. @@ -101,6 +105,7 @@ this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK; } + /** {@inheritDoc} */ @Override public final void write(final int b) throws IOException { count++; @@ -108,6 +113,7 @@ md.update((byte) b); } + /** {@inheritDoc} */ @Override public final void write(final byte[] b, int off, int len) throws IOException { @@ -131,6 +137,7 @@ } } + /** {@inheritDoc} */ @Override public void flush() throws IOException { out.flush(); @@ -154,7 +161,7 @@ * * @param otp * the object to write. - * @throws IOException + * @throws java.io.IOException * the object cannot be read from the object reader, or the * output stream is no longer accepting output. Caller must * examine the type of exception and possibly its message to @@ -177,13 +184,13 @@ * in whole object format, this is the same as the object size. * For an object that is in a delta format, this is the size of * the inflated delta instruction stream. - * @throws IOException + * @throws java.io.IOException * the underlying stream refused to accept the header. */ public final void writeHeader(ObjectToPack otp, long rawLength) throws IOException { ObjectToPack b = otp.getDeltaBase(); - if (b != null && (b.isWritten() & ofsDelta)) { + if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer); n = ofsDelta(count - b.getOffset(), headerBuffer, n); write(headerBuffer, 0, n); @@ -224,7 +231,11 @@ return n; } - /** @return a temporary buffer writers can use to copy data with. */ + /** + * Get a temporary buffer writers can use to copy data with. + * + * @return a temporary buffer writers can use to copy data with. + */ public final byte[] getCopyBuffer() { return copyBuffer; } @@ -233,7 +244,11 @@ writeMonitor.update(1); } - /** @return total number of bytes written since stream start. */ + /** + * Get total number of bytes written since stream start. + * + * @return total number of bytes written since stream start. + */ public final long length() { return count; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.internal.storage.pack; import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE; +import static org.eclipse.jgit.revwalk.RevFlag.SEEN; import java.io.IOException; import java.util.ArrayList; @@ -55,34 +56,46 @@ import java.util.List; import java.util.Set; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.revwalk.AddUnseenToBitmapFilter; import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl; +import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap; import org.eclipse.jgit.internal.storage.file.PackBitmapIndex; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; +import org.eclipse.jgit.revwalk.BitmapWalker; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.BlockList; +import org.eclipse.jgit.util.SystemReader; -/** Helper class for the PackWriter to select commits for pack index bitmaps. */ +import com.googlecode.javaewah.EWAHCompressedBitmap; + +/** + * Helper class for the {@link PackWriter} to select commits for which to build + * pack index bitmaps. + */ class PackWriterBitmapPreparer { - private static final Comparator BUILDER_BY_CARDINALITY_DSC = - new Comparator() { - public int compare(BitmapBuilder a, BitmapBuilder b) { - return Integer.signum(b.cardinality() - a.cardinality()); + private static final int DAY_IN_SECONDS = 24 * 60 * 60; + + private static final Comparator ORDER_BY_CARDINALITY = new Comparator() { + @Override + public int compare(BitmapBuilderEntry a, BitmapBuilderEntry b) { + return Integer.signum(a.getBuilder().cardinality() + - b.getBuilder().cardinality()); } }; @@ -93,12 +106,18 @@ private final BitmapIndexImpl commitBitmapIndex; private final PackBitmapIndexRemapper bitmapRemapper; private final BitmapIndexImpl bitmapIndex; - private final int minCommits = 100; - private final int maxCommits = 5000; + + private final int contiguousCommitCount; + private final int recentCommitCount; + private final int recentCommitSpan; + private final int distantCommitSpan; + private final int excessiveBranchCount; + private final long inactiveBranchTimestamp; PackWriterBitmapPreparer(ObjectReader reader, PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm, - Set want) throws IOException { + Set want, PackConfig config) + throws IOException { this.reader = reader; this.writeBitmaps = writeBitmaps; this.pm = pm; @@ -107,221 +126,402 @@ this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex( reader.getBitmapIndex(), writeBitmaps); this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper); + this.contiguousCommitCount = config.getBitmapContiguousCommitCount(); + this.recentCommitCount = config.getBitmapRecentCommitCount(); + this.recentCommitSpan = config.getBitmapRecentCommitSpan(); + this.distantCommitSpan = config.getBitmapDistantCommitSpan(); + this.excessiveBranchCount = config.getBitmapExcessiveBranchCount(); + long now = SystemReader.getInstance().getCurrentTime(); + long ageInSeconds = config.getBitmapInactiveBranchAgeInDays() + * DAY_IN_SECONDS; + this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds; } - Collection doCommitSelection(int expectedNumCommits) - throws MissingObjectException, IncorrectObjectTypeException, - IOException { + /** + * Returns the commit objects for which bitmap indices should be built. + * + * @param expectedCommitCount + * count of commits in the pack + * @param excludeFromBitmapSelection + * commits that should be excluded from bitmap selection + * @return commit objects for which bitmap indices should be built + * @throws IncorrectObjectTypeException + * if any of the processed objects is not a commit + * @throws IOException + * on errors reading pack or index files + * @throws MissingObjectException + * if an expected object is missing + */ + Collection selectCommits(int expectedCommitCount, + Set excludeFromBitmapSelection) + throws IncorrectObjectTypeException, IOException, + MissingObjectException { + /* + * Thinking of bitmap indices as a cache, if we find bitmaps at or at a + * close ancestor to 'old' and 'new' when calculating old..new, then all + * objects can be calculated with minimal graph walking. A distribution + * that favors creating bitmaps for the most recent commits maximizes + * the cache hits for clients that are close to HEAD, which is the + * majority of calculations performed. + */ pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN); RevWalk rw = new RevWalk(reader); rw.setRetainBody(false); - WalkResult result = findPaths(rw, expectedNumCommits); + CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw, + expectedCommitCount, excludeFromBitmapSelection); pm.endTask(); - int totCommits = result.commitsByOldest.length - result.commitStartPos; - BlockList selections = new BlockList( - totCommits / minCommits + 1); - for (BitmapCommit reuse : result.reuse) + int totCommits = selectionHelper.getCommitCount(); + BlockList selections = new BlockList<>( + totCommits / recentCommitSpan + 1); + for (BitmapCommit reuse : selectionHelper.reusedCommits) { selections.add(reuse); + } if (totCommits == 0) { - for (AnyObjectId id : result.peeledWant) + for (AnyObjectId id : selectionHelper.peeledWants) { selections.add(new BitmapCommit(id, false, 0)); + } return selections; } pm.beginTask(JGitText.get().selectingCommits, totCommits); + int totalWants = selectionHelper.peeledWants.size(); - for (BitmapBuilder bitmapableCommits : result.paths) { - int cardinality = bitmapableCommits.cardinality(); - - List> running = new ArrayList< - List>(); + for (BitmapBuilderEntry entry : selectionHelper.tipCommitBitmaps) { + BitmapBuilder bitmap = entry.getBuilder(); + int cardinality = bitmap.cardinality(); + + // Within this branch, keep ordered lists of commits representing + // chains in its history, where each chain is a "sub-branch". + // Ordering commits by these chains makes for fewer differences + // between consecutive selected commits, which in turn provides + // better compression/on the run-length encoding of the XORs between + // them. + List> chains = + new ArrayList<>(); + + // Mark the current branch as inactive if its tip commit isn't + // recent and there are an excessive number of branches, to + // prevent memory bloat of computing too many bitmaps for stale + // branches. + boolean isActiveBranch = true; + if (totalWants > excessiveBranchCount + && !isRecentCommit(entry.getCommit())) { + isActiveBranch = false; + } // Insert bitmaps at the offsets suggested by the - // nextSelectionDistance() heuristic. + // nextSelectionDistance() heuristic. Only reuse bitmaps created + // for more distant commits. int index = -1; - int nextIn = nextSelectionDistance(0, cardinality); - int nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; - boolean mustPick = nextIn == 0; - for (RevCommit c : result) { - if (!bitmapableCommits.contains(c)) + int nextIn = nextSpan(cardinality); + int nextFlg = nextIn == distantCommitSpan + ? PackBitmapIndex.FLAG_REUSE : 0; + + // For the current branch, iterate through all commits from oldest + // to newest. + for (RevCommit c : selectionHelper) { + // Optimization: if we have found all the commits for this + // branch, stop searching + int distanceFromTip = cardinality - index - 1; + if (distanceFromTip == 0) { + break; + } + + // Ignore commits that are not in this branch + if (!bitmap.contains(c)) { continue; + } index++; nextIn--; pm.update(1); - // Always pick the items in want and prefer merge commits. - if (result.peeledWant.remove(c)) { - if (nextIn > 0) + // Always pick the items in wants, prefer merge commits. + if (selectionHelper.peeledWants.remove(c)) { + if (nextIn > 0) { nextFlg = 0; - } else if (!mustPick && ((nextIn > 0) - || (c.getParentCount() <= 1 && nextIn > -minCommits))) { - continue; + } + } else { + boolean stillInSpan = nextIn >= 0; + boolean isMergeCommit = c.getParentCount() > 1; + // Force selection if: + // a) we have exhausted the window looking for merges + // b) we are in the top commits of an active branch + // c) we are at a branch tip + boolean mustPick = (nextIn <= -recentCommitSpan) + || (isActiveBranch + && (distanceFromTip <= contiguousCommitCount)) + || (distanceFromTip == 1); // most recent commit + if (!mustPick && (stillInSpan || !isMergeCommit)) { + continue; + } } + // This commit is selected. + // Calculate where to look for the next one. int flags = nextFlg; - nextIn = nextSelectionDistance(index, cardinality); - nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; - mustPick = nextIn == 0; + nextIn = nextSpan(distanceFromTip); + nextFlg = nextIn == distantCommitSpan + ? PackBitmapIndex.FLAG_REUSE : 0; BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder(); rw.reset(); rw.markStart(c); - for (AnyObjectId objectId : result.reuse) - rw.markUninteresting(rw.parseCommit(objectId)); - rw.setRevFilter( - PackWriterBitmapWalker.newRevFilter(null, fullBitmap)); + rw.setRevFilter(new AddUnseenToBitmapFilter( + selectionHelper.reusedCommitsBitmap, fullBitmap)); while (rw.next() != null) { - // Work is done in the RevFilter. + // The RevFilter adds the reachable commits from this + // selected commit to fullBitmap. } - List> matches = new ArrayList< - List>(); - for (List list : running) { - BitmapCommit last = list.get(list.size() - 1); - if (fullBitmap.contains(last)) - matches.add(list); + // Sort the commits by independent chains in this branch's + // history, yielding better compression when building bitmaps. + List longestAncestorChain = null; + for (List chain : chains) { + BitmapCommit mostRecentCommit = chain.get(chain.size() - 1); + if (fullBitmap.contains(mostRecentCommit)) { + if (longestAncestorChain == null + || longestAncestorChain.size() < chain.size()) { + longestAncestorChain = chain; + } + } } - List match; - if (matches.isEmpty()) { - match = new ArrayList(); - running.add(match); - } else { - match = matches.get(0); - // Append to longest - for (List list : matches) { - if (list.size() > match.size()) - match = list; - } + if (longestAncestorChain == null) { + longestAncestorChain = new ArrayList<>(); + chains.add(longestAncestorChain); } - match.add(new BitmapCommit(c, !match.isEmpty(), flags)); + longestAncestorChain.add(new BitmapCommit( + c, !longestAncestorChain.isEmpty(), flags)); writeBitmaps.addBitmap(c, fullBitmap, 0); } - for (List list : running) - selections.addAll(list); + for (List chain : chains) { + selections.addAll(chain); + } } writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps. // Add the remaining peeledWant - for (AnyObjectId remainingWant : result.peeledWant) + for (AnyObjectId remainingWant : selectionHelper.peeledWants) { selections.add(new BitmapCommit(remainingWant, false, 0)); + } pm.endTask(); return selections; } - private WalkResult findPaths(RevWalk rw, int expectedNumCommits) - throws MissingObjectException, IOException { - BitmapBuilder reuseBitmap = commitBitmapIndex.newBitmapBuilder(); - List reuse = new ArrayList(); + private boolean isRecentCommit(RevCommit revCommit) { + return revCommit.getCommitTime() > inactiveBranchTimestamp; + } + + /** + * A RevFilter that excludes the commits named in a bitmap from the walk. + *

+ * If a commit is in {@code bitmap} then that commit is not emitted by the + * walk and its parents are marked as SEEN so the walk can skip them. The + * bitmaps passed in have the property that the parents of any commit in + * {@code bitmap} are also in {@code bitmap}, so marking the parents as + * SEEN speeds up the RevWalk by saving it from walking down blind alleys + * and does not change the commits emitted. + */ + private static class NotInBitmapFilter extends RevFilter { + private final BitmapBuilder bitmap; + + NotInBitmapFilter(BitmapBuilder bitmap) { + this.bitmap = bitmap; + } + + @Override + public final boolean include(RevWalk rw, RevCommit c) { + if (!bitmap.contains(c)) { + return true; + } + for (RevCommit p : c.getParents()) { + p.add(SEEN); + } + return false; + } + + @Override + public final NotInBitmapFilter clone() { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean requiresCommitBody() { + return false; + } + } + + /** + * For each of the {@code want}s, which represent the tip commit of each + * branch, set up an initial {@link BitmapBuilder}. Reuse previously built + * bitmaps if possible. + * + * @param rw + * a {@link RevWalk} to find reachable objects in this repository + * @param expectedCommitCount + * expected count of commits. The actual count may be less due to + * unreachable garbage. + * @param excludeFromBitmapSelection + * commits that should be excluded from bitmap selection + * @return a {@link CommitSelectionHelper} containing bitmaps for the tip + * commits + * @throws IncorrectObjectTypeException + * if any of the processed objects is not a commit + * @throws IOException + * on errors reading pack or index files + * @throws MissingObjectException + * if an expected object is missing + */ + private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw, + int expectedCommitCount, + Set excludeFromBitmapSelection) + throws IncorrectObjectTypeException, IOException, + MissingObjectException { + BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder(); + List reuseCommits = new ArrayList<>(); for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) { - if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) + // More recent commits did not have the reuse flag set, so skip them + if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) { continue; - + } RevObject ro = rw.peel(rw.parseAny(entry)); - if (ro instanceof RevCommit) { - RevCommit rc = (RevCommit) ro; - reuse.add(new BitmapCommit(rc, false, entry.getFlags())); - rw.markUninteresting(rc); + if (!(ro instanceof RevCommit)) { + continue; + } + RevCommit rc = (RevCommit) ro; + reuseCommits.add(new BitmapCommit(rc, false, entry.getFlags())); + if (!reuse.contains(rc)) { EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType( bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT); - writeBitmaps.addBitmap(rc, bitmap, 0); - reuseBitmap.add(rc, Constants.OBJ_COMMIT); + reuse.or(new CompressedBitmap(bitmap, commitBitmapIndex)); } } - writeBitmaps.clearBitmaps(); // Remove temporary bitmaps - // Do a RevWalk by commit time descending. Keep track of all the paths - // from the wants. - List paths = new ArrayList(want.size()); - Set peeledWant = new HashSet(want.size()); + // Add branch tips that are not represented in old bitmap indices. Set + // up the RevWalk to walk the new commits not in the old packs. + List tipCommitBitmaps = new ArrayList<>( + want.size()); + Set peeledWant = new HashSet<>(want.size()); for (AnyObjectId objectId : want) { RevObject ro = rw.peel(rw.parseAny(objectId)); - if (ro instanceof RevCommit && !reuseBitmap.contains(ro)) { - RevCommit rc = (RevCommit) ro; - peeledWant.add(rc); - rw.markStart(rc); - - BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder(); - bitmap.or(reuseBitmap); - bitmap.add(rc, Constants.OBJ_COMMIT); - paths.add(bitmap); + if (!(ro instanceof RevCommit) || reuse.contains(ro) + || excludeFromBitmapSelection.contains(ro)) { + continue; } + + RevCommit rc = (RevCommit) ro; + peeledWant.add(rc); + rw.markStart(rc); + + BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder(); + bitmap.addObject(rc, Constants.OBJ_COMMIT); + tipCommitBitmaps.add(new BitmapBuilderEntry(rc, bitmap)); } - // Update the paths from the wants and create a list of commits in - // reverse iteration order. - RevCommit[] commits = new RevCommit[expectedNumCommits]; + // Create a list of commits in reverse order (older to newer). + // For each branch that contains the commit, mark its parents as being + // in the bitmap. + rw.setRevFilter(new NotInBitmapFilter(reuse)); + RevCommit[] commits = new RevCommit[expectedCommitCount]; int pos = commits.length; RevCommit rc; - while ((rc = rw.next()) != null) { + while ((rc = rw.next()) != null && pos > 0) { commits[--pos] = rc; - for (BitmapBuilder path : paths) { - if (path.contains(rc)) { - for (RevCommit c : rc.getParents()) - path.add(c, Constants.OBJ_COMMIT); + for (BitmapBuilderEntry entry : tipCommitBitmaps) { + BitmapBuilder bitmap = entry.getBuilder(); + if (!bitmap.contains(rc)) { + continue; + } + for (RevCommit c : rc.getParents()) { + if (reuse.contains(c)) { + continue; + } + bitmap.addObject(c, Constants.OBJ_COMMIT); } } - pm.update(1); } - // Remove the reused bitmaps from the paths - if (!reuse.isEmpty()) - for (BitmapBuilder bitmap : paths) - bitmap.andNot(reuseBitmap); - - // Sort the paths - List distinctPaths = new ArrayList(paths.size()); - while (!paths.isEmpty()) { - Collections.sort(paths, BUILDER_BY_CARDINALITY_DSC); - BitmapBuilder largest = paths.remove(0); - distinctPaths.add(largest); + // Sort the tip commit bitmaps. Find the one containing the most + // commits, remove those commits from the remaining bitmaps, resort and + // repeat. + List orderedTipCommitBitmaps = new ArrayList<>( + tipCommitBitmaps.size()); + while (!tipCommitBitmaps.isEmpty()) { + BitmapBuilderEntry largest = + Collections.max(tipCommitBitmaps, ORDER_BY_CARDINALITY); + tipCommitBitmaps.remove(largest); + orderedTipCommitBitmaps.add(largest); // Update the remaining paths, by removing the objects from // the path that was just added. - for (int i = paths.size() - 1; i >= 0; i--) - paths.get(i).andNot(largest); + for (int i = tipCommitBitmaps.size() - 1; i >= 0; i--) { + tipCommitBitmaps.get(i).getBuilder() + .andNot(largest.getBuilder()); + } } - return new WalkResult(peeledWant, commits, pos, distinctPaths, reuse); + return new CommitSelectionHelper(peeledWant, commits, pos, + orderedTipCommitBitmaps, reuse, reuseCommits); } - private int nextSelectionDistance(int idx, int cardinality) { - if (idx > cardinality) + /*- + * Returns the desired distance to the next bitmap based on the distance + * from the tip commit. Only differentiates recent from distant spans, + * selectCommits() handles the contiguous commits at the tip for active + * or inactive branches. + * + * A graph of this function looks like this, where + * the X axis is the distance from the tip commit and the Y axis is the + * bitmap selection distance. + * + * 5000 ____... + * / + * / + * / + * / + * 100 _____/ + * 0 20100 25000 + * + * Linear scaling between 20100 and 25000 prevents spans >100 for distances + * <20000 (otherwise, a span of 5000 would be returned for a distance of + * 21000, and the range 16000-20000 would have no selections). + */ + int nextSpan(int distanceFromTip) { + if (distanceFromTip < 0) { throw new IllegalArgumentException(); - int idxFromStart = cardinality - idx; - int mustRegionEnd = 100; - if (idxFromStart <= mustRegionEnd) - return 0; + } // Commits more toward the start will have more bitmaps. - int minRegionEnd = 20000; - if (idxFromStart <= minRegionEnd) - return Math.min(idxFromStart - mustRegionEnd, minCommits); - - // Commits more toward the end will have fewer. - int next = Math.min(idxFromStart - minRegionEnd, maxCommits); - return Math.max(next, minCommits); + if (distanceFromTip <= recentCommitCount) { + return recentCommitSpan; + } + + int next = Math.min(distanceFromTip - recentCommitCount, + distantCommitSpan); + return Math.max(next, recentCommitSpan); } - PackWriterBitmapWalker newBitmapWalker() { - return new PackWriterBitmapWalker( + BitmapWalker newBitmapWalker() { + return new BitmapWalker( new ObjectWalk(reader), bitmapIndex, null); } + /** + * A commit object for which a bitmap index should be built. + */ static final class BitmapCommit extends ObjectId { private final boolean reuseWalker; private final int flags; - private BitmapCommit( - AnyObjectId objectId, boolean reuseWalker, int flags) { + BitmapCommit(AnyObjectId objectId, boolean reuseWalker, int flags) { super(objectId); this.reuseWalker = reuseWalker; this.flags = flags; @@ -336,39 +536,85 @@ } } - private static final class WalkResult implements Iterable { - private final Set peeledWant; - private final RevCommit[] commitsByOldest; - private final int commitStartPos; - private final List paths; - private final Iterable reuse; + /** + * A POJO representing a Pair. + */ + private static final class BitmapBuilderEntry { + private final RevCommit commit; + private final BitmapBuilder builder; + + BitmapBuilderEntry(RevCommit commit, BitmapBuilder builder) { + this.commit = commit; + this.builder = builder; + } - private WalkResult(Set peeledWant, + RevCommit getCommit() { + return commit; + } + + BitmapBuilder getBuilder() { + return builder; + } + } + + /** + * Container for state used in the first phase of selecting commits, which + * walks all of the reachable commits via the branch tips ( + * {@code peeledWants}), stores them in {@code commitsByOldest}, and sets up + * bitmaps for each branch tip ({@code tipCommitBitmaps}). + * {@code commitsByOldest} is initialized with an expected size of all + * commits, but may be smaller if some commits are unreachable, in which + * case {@code commitStartPos} will contain a positive offset to the root + * commit. + */ + private static final class CommitSelectionHelper implements Iterable { + final Set peeledWants; + final List tipCommitBitmaps; + + final BitmapBuilder reusedCommitsBitmap; + final Iterable reusedCommits; + final RevCommit[] commitsByOldest; + final int commitStartPos; + + CommitSelectionHelper(Set peeledWant, RevCommit[] commitsByOldest, int commitStartPos, - List paths, Iterable reuse) { - this.peeledWant = peeledWant; + List bitmapEntries, + BitmapBuilder reusedCommitsBitmap, + Iterable reuse) { + this.peeledWants = peeledWant; this.commitsByOldest = commitsByOldest; this.commitStartPos = commitStartPos; - this.paths = paths; - this.reuse = reuse; + this.tipCommitBitmaps = bitmapEntries; + this.reusedCommitsBitmap = reusedCommitsBitmap; + this.reusedCommits = reuse; } + @Override public Iterator iterator() { + // Member variables referenced by this iterator will have synthetic + // accessors generated for them if they are made private. return new Iterator() { int pos = commitStartPos; + @Override public boolean hasNext() { return pos < commitsByOldest.length; } + @Override public RevCommit next() { return commitsByOldest[pos++]; } + @Override public void remove() { throw new UnsupportedOperationException(); } }; } + + int getCommitCount() { + return commitsByOldest.length - commitStartPos; + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2012, 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.internal.storage.pack; - -import java.io.IOException; -import java.util.Set; - -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.BitmapIndex; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.NullProgressMonitor; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.BitmapIndex.Bitmap; -import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.revwalk.ObjectWalk; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevFlag; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.revwalk.filter.RevFilter; - -/** Helper class for PackWriter to do ObjectWalks with pack index bitmaps. */ -final class PackWriterBitmapWalker { - - private final ObjectWalk walker; - - private final BitmapIndex bitmapIndex; - - private final ProgressMonitor pm; - - private long countOfBitmapIndexMisses; - - PackWriterBitmapWalker( - ObjectWalk walker, BitmapIndex bitmapIndex, ProgressMonitor pm) { - this.walker = walker; - this.bitmapIndex = bitmapIndex; - this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm; - } - - long getCountOfBitmapIndexMisses() { - return countOfBitmapIndexMisses; - } - - BitmapBuilder findObjects(Set start, BitmapBuilder seen, boolean ignoreMissingStart) - throws MissingObjectException, IncorrectObjectTypeException, - IOException { - final BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder(); - - for (ObjectId obj : start) { - Bitmap bitmap = bitmapIndex.getBitmap(obj); - if (bitmap != null) - bitmapResult.or(bitmap); - } - - boolean marked = false; - for (ObjectId obj : start) { - try { - if (!bitmapResult.contains(obj)) { - walker.markStart(walker.parseAny(obj)); - marked = true; - } - } catch (MissingObjectException e) { - if (ignoreMissingStart) - continue; - throw e; - } - } - - if (marked) { - BitmapRevFilter filter = newRevFilter(seen, bitmapResult); - walker.setRevFilter(filter); - - while (walker.next() != null) { - // Iterate through all of the commits. The BitmapRevFilter does - // the work. - pm.update(1); - } - - RevObject ro; - while ((ro = walker.nextObject()) != null) { - bitmapResult.add(ro, ro.getType()); - pm.update(1); - } - countOfBitmapIndexMisses += filter.getCountOfLoadedCommits(); - } - - return bitmapResult; - } - - void reset() { - walker.reset(); - } - - static BitmapRevFilter newRevFilter( - final BitmapBuilder seen, final BitmapBuilder bitmapResult) { - if (seen != null) { - return new BitmapRevFilter() { - protected boolean load(RevCommit cmit) { - if (seen.contains(cmit)) - return false; - return bitmapResult.add(cmit, Constants.OBJ_COMMIT); - } - }; - } - return new BitmapRevFilter() { - @Override - protected boolean load(RevCommit cmit) { - return bitmapResult.add(cmit, Constants.OBJ_COMMIT); - } - }; - } - - static abstract class BitmapRevFilter extends RevFilter { - private long countOfLoadedCommits; - - protected abstract boolean load(RevCommit cmit); - - @Override - public final boolean include(RevWalk walker, RevCommit cmit) { - if (load(cmit)) { - countOfLoadedCommits++; - return true; - } - for (RevCommit p : cmit.getParents()) - p.add(RevFlag.SEEN); - return false; - } - - @Override - public final RevFilter clone() { - return this; - } - - @Override - public final boolean requiresCommitBody() { - return false; - } - - long getCountOfLoadedCommits() { - return countOfLoadedCommits; - } - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -80,6 +80,8 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.LargeObjectException; @@ -99,12 +101,14 @@ import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ThreadSafeProgressMonitor; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; +import org.eclipse.jgit.revwalk.BitmapWalker; import org.eclipse.jgit.revwalk.DepthWalk; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; @@ -132,8 +136,8 @@ *

  • (usually) by providing sets of interesting and uninteresting objects in * repository - all interesting objects and their ancestors except uninteresting * objects and their ancestors will be included in pack, or
  • - *
  • by providing iterator of {@link RevObject} specifying exact list and - * order of objects in pack
  • + *
  • by providing iterator of {@link org.eclipse.jgit.revwalk.RevObject} + * specifying exact list and order of objects in pack
  • * *

    * Typical usage consists of creating an instance, configuring options, @@ -146,10 +150,11 @@ * followed by {@link #writeBitmapIndex(OutputStream)}. *

    *

    - * Class provide set of configurable options and {@link ProgressMonitor} - * support, as operations may take a long time for big repositories. Deltas - * searching algorithm is NOT IMPLEMENTED yet - this implementation - * relies only on deltas and objects reuse. + * Class provide set of configurable options and + * {@link org.eclipse.jgit.lib.ProgressMonitor} support, as operations may take + * a long time for big repositories. Deltas searching algorithm is NOT + * IMPLEMENTED yet - this implementation relies only on deltas and objects + * reuse. *

    *

    * This class is not thread safe. It is intended to be used in one thread as a @@ -161,28 +166,21 @@ public class PackWriter implements AutoCloseable { private static final int PACK_VERSION_GENERATED = 2; - /** A collection of object ids. */ - public interface ObjectIdSet { - /** - * Returns true if the objectId is contained within the collection. - * - * @param objectId - * the objectId to find - * @return whether the collection contains the objectId. - */ - boolean contains(AnyObjectId objectId); - } + /** Empty set of objects for {@code preparePack()}. */ + public static final Set NONE = Collections.emptySet(); private static final Map, Boolean> instances = - new ConcurrentHashMap, Boolean>(); + new ConcurrentHashMap<>(); private static final Iterable instancesIterable = new Iterable() { + @Override public Iterator iterator() { return new Iterator() { private final Iterator> it = instances.keySet().iterator(); private PackWriter next; + @Override public boolean hasNext() { if (next != null) return true; @@ -196,6 +194,7 @@ return false; } + @Override public PackWriter next() { if (hasNext()) { PackWriter result = next; @@ -205,6 +204,7 @@ throw new NoSuchElementException(); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -212,31 +212,37 @@ } }; - /** @return all allocated, non-released PackWriters instances. */ + /** + * Get all allocated, non-released PackWriters instances. + * + * @return all allocated, non-released PackWriters instances. + */ public static Iterable getInstances() { return instancesIterable; } @SuppressWarnings("unchecked") - private BlockList objectsLists[] = new BlockList[OBJ_TAG + 1]; + BlockList objectsLists[] = new BlockList[OBJ_TAG + 1]; { - objectsLists[OBJ_COMMIT] = new BlockList(); - objectsLists[OBJ_TREE] = new BlockList(); - objectsLists[OBJ_BLOB] = new BlockList(); - objectsLists[OBJ_TAG] = new BlockList(); + objectsLists[OBJ_COMMIT] = new BlockList<>(); + objectsLists[OBJ_TREE] = new BlockList<>(); + objectsLists[OBJ_BLOB] = new BlockList<>(); + objectsLists[OBJ_TAG] = new BlockList<>(); } - private ObjectIdOwnerMap objectsMap = new ObjectIdOwnerMap(); + private ObjectIdOwnerMap objectsMap = new ObjectIdOwnerMap<>(); // edge objects for thin packs - private List edgeObjects = new BlockList(); + private List edgeObjects = new BlockList<>(); // Objects the client is known to have already. private BitmapBuilder haveObjects; - private List cachedPacks = new ArrayList(2); + private List cachedPacks = new ArrayList<>(2); + + private Set tagTargets = NONE; - private Set tagTargets = Collections.emptySet(); + private Set excludeFromBitmapSelection = NONE; private ObjectIdSet[] excludeInPacks; @@ -249,7 +255,7 @@ /** {@link #reader} recast to the reuse interface, if it supports it. */ private final ObjectReuseAsIs reuseSupport; - private final PackConfig config; + final PackConfig config; private final PackStatistics.Accumulator stats; @@ -350,6 +356,24 @@ * reader to read from the repository with. */ public PackWriter(final PackConfig config, final ObjectReader reader) { + this(config, reader, null); + } + + /** + * Create writer with a specified configuration. + *

    + * Objects for packing are specified in {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Set, Set)}. + * + * @param config + * configuration for the pack writer. + * @param reader + * reader to read from the repository with. + * @param statsAccumulator + * accumulator for statics + */ + public PackWriter(PackConfig config, final ObjectReader reader, + @Nullable PackStatistics.Accumulator statsAccumulator) { this.config = config; this.reader = reader; if (reader instanceof ObjectReuseAsIs) @@ -360,9 +384,10 @@ deltaBaseAsOffset = config.isDeltaBaseAsOffset(); reuseDeltas = config.isReuseDeltas(); reuseValidate = true; // be paranoid by default - stats = new PackStatistics.Accumulator(); + stats = statsAccumulator != null ? statsAccumulator + : new PackStatistics.Accumulator(); state = new MutableState(); - selfRef = new WeakReference(this); + selfRef = new WeakReference<>(this); instances.put(selfRef, Boolean.TRUE); } @@ -374,9 +399,7 @@ * * @param callback * the callback to set - * * @return this object for chaining. - * @since 4.1 */ public PackWriter setObjectCountCallback(ObjectCountCallback callback) { this.callback = callback; @@ -388,11 +411,10 @@ * * @param clientShallowCommits * the shallow commits in the client - * @since 4.1 */ public void setClientShallowCommits(Set clientShallowCommits) { stats.clientShallowCommits = Collections - .unmodifiableSet(new HashSet(clientShallowCommits)); + .unmodifiableSet(new HashSet<>(clientShallowCommits)); } /** @@ -468,12 +490,19 @@ reuseValidate = validate; } - /** @return true if this writer is producing a thin pack. */ + /** + * Whether this writer is producing a thin pack. + * + * @return true if this writer is producing a thin pack. + */ public boolean isThin() { return thin; } /** + * Whether writer may pack objects with delta base object not within set of + * objects to pack + * * @param packthin * a boolean indicating whether writer may pack objects with * delta base object not within set of objects to pack, but @@ -485,16 +514,23 @@ thin = packthin; } - /** @return true to reuse cached packs. If true index creation isn't available. */ + /** + * Whether to reuse cached packs. + * + * @return {@code true} to reuse cached packs. If true index creation isn't + * available. + */ public boolean isUseCachedPacks() { return useCachedPacks; } /** + * Whether to use cached packs + * * @param useCached - * if set to true and a cached pack is present, it will be - * appended onto the end of a thin-pack, reducing the amount of - * working set space and CPU used by PackWriter. Enabling this + * if set to {@code true} and a cached pack is present, it will + * be appended onto the end of a thin-pack, reducing the amount + * of working set space and CPU used by PackWriter. Enabling this * feature prevents PackWriter from creating an index for the * newly created pack, so its only suitable for writing to a * network client, where the client will make the index. @@ -503,12 +539,18 @@ useCachedPacks = useCached; } - /** @return true to use bitmaps for ObjectWalks, if available. */ + /** + * Whether to use bitmaps + * + * @return {@code true} to use bitmaps for ObjectWalks, if available. + */ public boolean isUseBitmaps() { return useBitmaps; } /** + * Whether to use bitmaps + * * @param useBitmaps * if set to true, bitmaps will be used when preparing a pack. */ @@ -516,23 +558,33 @@ this.useBitmaps = useBitmaps; } - /** @return true if the index file cannot be created by this PackWriter. */ + /** + * Whether the index file cannot be created by this PackWriter. + * + * @return {@code true} if the index file cannot be created by this + * PackWriter. + */ public boolean isIndexDisabled() { return indexDisabled || !cachedPacks.isEmpty(); } /** + * Whether to disable creation of the index file. + * * @param noIndex - * true to disable creation of the index file. + * {@code true} to disable creation of the index file. */ public void setIndexDisabled(boolean noIndex) { this.indexDisabled = noIndex; } /** - * @return true to ignore objects that are uninteresting and also not found - * on local disk; false to throw a {@link MissingObjectException} - * out of {@link #preparePack(ProgressMonitor, Set, Set)} if an + * Whether to ignore missing uninteresting objects + * + * @return {@code true} to ignore objects that are uninteresting and also + * not found on local disk; false to throw a + * {@link org.eclipse.jgit.errors.MissingObjectException} out of + * {@link #preparePack(ProgressMonitor, Set, Set)} if an * uninteresting object is not in the source repository. By default, * true, permitting gracefully ignoring of uninteresting objects. */ @@ -541,11 +593,13 @@ } /** + * Whether writer should ignore non existing uninteresting objects + * * @param ignore - * true if writer should ignore non existing uninteresting - * objects during construction set of objects to pack; false - * otherwise - non existing uninteresting objects may cause - * {@link MissingObjectException} + * {@code true} if writer should ignore non existing + * uninteresting objects during construction set of objects to + * pack; false otherwise - non existing uninteresting objects may + * cause {@link org.eclipse.jgit.errors.MissingObjectException} */ public void setIgnoreMissingUninteresting(final boolean ignore) { ignoreMissingUninteresting = ignore; @@ -571,7 +625,8 @@ * Configure this pack for a shallow clone. * * @param depth - * maximum depth to traverse the commit graph + * maximum depth of history to return. 1 means return only the + * "wants". * @param unshallow * objects which used to be shallow on the client, but are being * extended as part of this fetch @@ -587,7 +642,7 @@ * Returns objects number in a pack file that was created by this writer. * * @return number of objects in pack. - * @throws IOException + * @throws java.io.IOException * a cached pack cannot supply its object count. */ public long getObjectCount() throws IOException { @@ -614,7 +669,7 @@ * been invoked and completed successfully. * * @return set of objects in pack. - * @throws IOException + * @throws java.io.IOException * a cached pack cannot supply its object ids. */ public ObjectIdOwnerMap getObjectSet() @@ -673,15 +728,17 @@ * @param objectsSource * iterator of object to store in a pack; order of objects within * each type is important, ordering by type is not needed; - * allowed types for objects are {@link Constants#OBJ_COMMIT}, - * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and - * {@link Constants#OBJ_TAG}; objects returned by iterator may be - * later reused by caller as object id and type are internally - * copied in each iteration. - * @throws IOException + * allowed types for objects are + * {@link org.eclipse.jgit.lib.Constants#OBJ_COMMIT}, + * {@link org.eclipse.jgit.lib.Constants#OBJ_TREE}, + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB} and + * {@link org.eclipse.jgit.lib.Constants#OBJ_TAG}; objects + * returned by iterator may be later reused by caller as object + * id and type are internally copied in each iteration. + * @throws java.io.IOException * when some I/O problem occur during reading objects. */ - public void preparePack(final Iterator objectsSource) + public void preparePack(@NonNull Iterator objectsSource) throws IOException { while (objectsSource.hasNext()) { addObject(objectsSource.next()); @@ -694,32 +751,113 @@ * Basing on these 2 sets, another set of objects to put in a pack file is * created: this set consists of all objects reachable (ancestors) from * interesting objects, except uninteresting objects and their ancestors. - * This method uses class {@link ObjectWalk} extensively to find out that - * appropriate set of output objects and their optimal order in output pack. - * Order is consistent with general git in-pack rules: sort by object type, - * recency, path and delta-base first. + * This method uses class {@link org.eclipse.jgit.revwalk.ObjectWalk} + * extensively to find out that appropriate set of output objects and their + * optimal order in output pack. Order is consistent with general git + * in-pack rules: sort by object type, recency, path and delta-base first. *

    * * @param countingMonitor * progress during object enumeration. * @param want * collection of objects to be marked as interesting (start - * points of graph traversal). + * points of graph traversal). Must not be {@code null}. * @param have * collection of objects to be marked as uninteresting (end - * points of graph traversal). - * @throws IOException + * points of graph traversal). Pass {@link #NONE} if all objects + * reachable from {@code want} are desired, such as when serving + * a clone. + * @throws java.io.IOException * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, - Set want, - Set have) throws IOException { - ObjectWalk ow; - if (shallowPack) - ow = new DepthWalk.ObjectWalk(reader, depth); - else - ow = new ObjectWalk(reader); - preparePack(countingMonitor, ow, want, have); + @NonNull Set want, + @NonNull Set have) throws IOException { + preparePack(countingMonitor, want, have, NONE, NONE); + } + + /** + * Prepare the list of objects to be written to the pack stream. + *

    + * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows + * specifying commits that should not be walked past ("shallow" commits). + * The caller is responsible for filtering out commits that should not be + * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from + * the shallow set. + * + * @param countingMonitor + * progress during object enumeration. + * @param want + * objects of interest, ancestors of which will be included in + * the pack. Must not be {@code null}. + * @param have + * objects whose ancestors (up to and including {@code shallow} + * commits) do not need to be included in the pack because they + * are already available from elsewhere. Must not be + * {@code null}. + * @param shallow + * commits indicating the boundary of the history marked with + * {@code have}. Shallow commits have parents but those parents + * are considered not to be already available. Parents of + * {@code shallow} commits and earlier generations will be + * included in the pack if requested by {@code want}. Must not be + * {@code null}. + * @throws java.io.IOException + * an I/O problem occurred while reading objects. + */ + public void preparePack(ProgressMonitor countingMonitor, + @NonNull Set want, + @NonNull Set have, + @NonNull Set shallow) throws IOException { + preparePack(countingMonitor, want, have, shallow, NONE); + } + + /** + * Prepare the list of objects to be written to the pack stream. + *

    + * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows + * specifying commits that should not be walked past ("shallow" commits). + * The caller is responsible for filtering out commits that should not be + * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from + * the shallow set. + * + * @param countingMonitor + * progress during object enumeration. + * @param want + * objects of interest, ancestors of which will be included in + * the pack. Must not be {@code null}. + * @param have + * objects whose ancestors (up to and including {@code shallow} + * commits) do not need to be included in the pack because they + * are already available from elsewhere. Must not be + * {@code null}. + * @param shallow + * commits indicating the boundary of the history marked with + * {@code have}. Shallow commits have parents but those parents + * are considered not to be already available. Parents of + * {@code shallow} commits and earlier generations will be + * included in the pack if requested by {@code want}. Must not be + * {@code null}. + * @param noBitmaps + * collection of objects to be excluded from bitmap commit + * selection. + * @throws java.io.IOException + * an I/O problem occurred while reading objects. + */ + public void preparePack(ProgressMonitor countingMonitor, + @NonNull Set want, + @NonNull Set have, + @NonNull Set shallow, + @NonNull Set noBitmaps) throws IOException { + try (ObjectWalk ow = getObjectWalk()) { + ow.assumeShallow(shallow); + preparePack(countingMonitor, ow, want, have, noBitmaps); + } + } + + private ObjectWalk getObjectWalk() { + return shallowPack ? new DepthWalk.ObjectWalk(reader, depth - 1) + : new ObjectWalk(reader); } /** @@ -728,10 +866,10 @@ * Basing on these 2 sets, another set of objects to put in a pack file is * created: this set consists of all objects reachable (ancestors) from * interesting objects, except uninteresting objects and their ancestors. - * This method uses class {@link ObjectWalk} extensively to find out that - * appropriate set of output objects and their optimal order in output pack. - * Order is consistent with general git in-pack rules: sort by object type, - * recency, path and delta-base first. + * This method uses class {@link org.eclipse.jgit.revwalk.ObjectWalk} + * extensively to find out that appropriate set of output objects and their + * optimal order in output pack. Order is consistent with general git + * in-pack rules: sort by object type, recency, path and delta-base first. *

    * * @param countingMonitor @@ -740,24 +878,31 @@ * ObjectWalk to perform enumeration. * @param interestingObjects * collection of objects to be marked as interesting (start - * points of graph traversal). + * points of graph traversal). Must not be {@code null}. * @param uninterestingObjects * collection of objects to be marked as uninteresting (end - * points of graph traversal). - * @throws IOException + * points of graph traversal). Pass {@link #NONE} if all objects + * reachable from {@code want} are desired, such as when serving + * a clone. + * @param noBitmaps + * collection of objects to be excluded from bitmap commit + * selection. + * @throws java.io.IOException * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, - ObjectWalk walk, - final Set interestingObjects, - final Set uninterestingObjects) + @NonNull ObjectWalk walk, + @NonNull Set interestingObjects, + @NonNull Set uninterestingObjects, + @NonNull Set noBitmaps) throws IOException { if (countingMonitor == null) countingMonitor = NullProgressMonitor.INSTANCE; if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk)) - walk = new DepthWalk.ObjectWalk(reader, depth); + throw new IllegalArgumentException( + JGitText.get().shallowPacksRequireDepthWalk); findObjectsToPack(countingMonitor, walk, interestingObjects, - uninterestingObjects); + uninterestingObjects, noBitmaps); } /** @@ -766,7 +911,7 @@ * @param id * the object to test the existence of. * @return true if the object will appear in the output pack file. - * @throws IOException + * @throws java.io.IOException * a cached pack cannot be examined. */ public boolean willInclude(final AnyObjectId id) throws IOException { @@ -833,7 +978,7 @@ * @param indexStream * output for the index data. Caller is responsible for closing * this stream. - * @throws IOException + * @throws java.io.IOException * the index data could not be written to the supplied stream. */ public void writeIndex(final OutputStream indexStream) throws IOException { @@ -855,7 +1000,7 @@ * @param bitmapIndexStream * output for the bitmap index data. Caller is responsible for * closing this stream. - * @throws IOException + * @throws java.io.IOException * the index data could not be written to the supplied stream. */ public void writeBitmapIndex(final OutputStream bitmapIndexStream) @@ -877,7 +1022,7 @@ cnt += objectsLists[OBJ_BLOB].size(); cnt += objectsLists[OBJ_TAG].size(); - sortedByName = new BlockList(cnt); + sortedByName = new BlockList<>(cnt); sortedByName.addAll(objectsLists[OBJ_COMMIT]); sortedByName.addAll(objectsLists[OBJ_TREE]); sortedByName.addAll(objectsLists[OBJ_BLOB]); @@ -924,8 +1069,9 @@ /** * Write the prepared pack to the supplied stream. *

    - * Called after {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set)} - * or {@link #preparePack(ProgressMonitor, Set, Set)}. + * Called after + * {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set, Set)} or + * {@link #preparePack(ProgressMonitor, Set, Set)}. *

    * Performs delta search if enabled and writes the pack stream. *

    @@ -939,13 +1085,13 @@ * @param packStream * output stream of pack data. The stream should be buffered by * the caller. The caller is responsible for closing the stream. - * @throws IOException + * @throws java.io.IOException * 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} - * . + * the write operation is aborted by + * {@link org.eclipse.jgit.transport.ObjectCountCallback} . */ public void writePack(ProgressMonitor compressMonitor, ProgressMonitor writeMonitor, OutputStream packStream) @@ -1034,6 +1180,9 @@ } /** + * Get statistics of what this PackWriter did in order to create the final + * pack stream. + * * @return description of what this PackWriter did in order to create the * final pack stream. This should only be invoked after the calls to * create the pack/index/bitmap have completed. @@ -1042,15 +1191,19 @@ return new PackStatistics(stats); } - /** @return snapshot of the current state of this PackWriter. */ + /** + * Get snapshot of the current state of this PackWriter. + * + * @return snapshot of the current state of this PackWriter. + */ public State getState() { return state.snapshot(); } /** + * {@inheritDoc} + *

    * Release all resources used by this writer. - * - * @since 4.0 */ @Override public void close() { @@ -1073,7 +1226,7 @@ beginPhase(PackingPhase.FINDING_SOURCES, monitor, cnt); if (cnt <= 4096) { // For small object counts, do everything as one list. - BlockList tmp = new BlockList((int) cnt); + BlockList tmp = new BlockList<>((int) cnt); tmp.addAll(objectsLists[OBJ_TAG]); tmp.addAll(objectsLists[OBJ_COMMIT]); tmp.addAll(objectsLists[OBJ_TREE]); @@ -1219,6 +1372,7 @@ // bigger ones, because source files grow and hardly ever shrink. // Arrays.sort(list, 0, cnt, new Comparator() { + @Override public int compare(ObjectToPack a, ObjectToPack b) { int cmp = (a.isDoNotDelta() ? 1 : 0) - (b.isDoNotDelta() ? 1 : 0); @@ -1306,8 +1460,7 @@ long totalWeight = 0; for (int i = 0; i < cnt; i++) { ObjectToPack o = list[i]; - if (!o.isEdge() && !o.doNotAttemptDelta()) - totalWeight += o.getWeight(); + totalWeight += DeltaTask.getAdjustedWeight(o); } long bytesPerUnit = 1; @@ -1365,6 +1518,7 @@ // can schedule these for us. for (final DeltaTask task : taskBlock.tasks) { executor.execute(new Runnable() { + @Override public void run() { try { task.call(); @@ -1397,9 +1551,7 @@ if (err instanceof IOException) throw (IOException) err; - IOException fail = new IOException(err.getMessage()); - fail.initCause(err); - throw fail; + throw new IOException(err.getMessage(), err); } endPhase(monitor); } @@ -1407,7 +1559,7 @@ private static void runTasks(ExecutorService pool, ThreadSafeProgressMonitor pm, DeltaTask.Block tb, List errors) throws IOException { - List> futures = new ArrayList>(tb.tasks.size()); + List> futures = new ArrayList<>(tb.tasks.size()); for (DeltaTask task : tb.tasks) futures.add(pool.submit(task)); @@ -1552,18 +1704,21 @@ if (zbuf != null) { out.writeHeader(otp, otp.getCachedSize()); out.write(zbuf); + typeStats.cntDeltas++; + typeStats.deltaBytes += out.length() - otp.getOffset(); return; } } - TemporaryBuffer.Heap delta = delta(otp); - out.writeHeader(otp, delta.length()); + try (TemporaryBuffer.Heap delta = delta(otp)) { + out.writeHeader(otp, delta.length()); - Deflater deflater = deflater(); - deflater.reset(); - DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater); - delta.writeTo(dst, null); - dst.finish(); + Deflater deflater = deflater(); + deflater.reset(); + DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater); + delta.writeTo(dst, null); + dst.finish(); + } typeStats.cntDeltas++; typeStats.deltaBytes += out.length() - otp.getOffset(); } @@ -1607,19 +1762,16 @@ out.write(packcsum); } - private void findObjectsToPack(final ProgressMonitor countingMonitor, - final ObjectWalk walker, final Set want, - Set have) - throws MissingObjectException, IOException, - IncorrectObjectTypeException { + private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor, + @NonNull ObjectWalk walker, @NonNull Set want, + @NonNull Set have, + @NonNull Set noBitmaps) throws IOException { final long countingStart = System.currentTimeMillis(); beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN); - if (have == null) - have = Collections.emptySet(); - stats.interestingObjects = Collections.unmodifiableSet(new HashSet(want)); stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet(have)); + excludeFromBitmapSelection = noBitmaps; canBuildBitmaps = config.isBuildBitmaps() && !shallowPack @@ -1628,7 +1780,7 @@ if (!shallowPack && useBitmaps) { BitmapIndex bitmapIndex = reader.getBitmapIndex(); if (bitmapIndex != null) { - PackWriterBitmapWalker bitmapWalker = new PackWriterBitmapWalker( + BitmapWalker bitmapWalker = new BitmapWalker( walker, bitmapIndex, countingMonitor); findObjectsToPackUsingBitmaps(bitmapWalker, want, have); endPhase(countingMonitor); @@ -1638,7 +1790,7 @@ } } - List all = new ArrayList(want.size() + have.size()); + List all = new ArrayList<>(want.size() + have.size()); all.addAll(want); all.addAll(have); @@ -1656,10 +1808,12 @@ walker.sort(RevSort.BOUNDARY, true); } - List wantObjs = new ArrayList(want.size()); - List haveObjs = new ArrayList(haveEst); - List wantTags = new ArrayList(want.size()); + List wantObjs = new ArrayList<>(want.size()); + List haveObjs = new ArrayList<>(haveEst); + List wantTags = new ArrayList<>(want.size()); + // Retrieve the RevWalk's versions of "want" and "have" objects to + // maintain any state previously set in the RevWalk. AsyncRevObjectQueue q = walker.parseAny(all, true); try { for (;;) { @@ -1687,7 +1841,7 @@ } if (!wantTags.isEmpty()) { - all = new ArrayList(wantTags.size()); + all = new ArrayList<>(wantTags.size()); for (RevTag tag : wantTags) all.add(tag.getObject()); q = walker.parseAny(all, true); @@ -1702,11 +1856,25 @@ if (walker instanceof DepthWalk.ObjectWalk) { DepthWalk.ObjectWalk depthWalk = (DepthWalk.ObjectWalk) walker; - for (RevObject obj : wantObjs) + for (RevObject obj : wantObjs) { depthWalk.markRoot(obj); + } + // Mark the tree objects associated with "have" commits as + // uninteresting to avoid writing redundant blobs. A normal RevWalk + // lazily propagates the "uninteresting" state from a commit to its + // tree during the walk, but DepthWalks can terminate early so + // preemptively propagate that state here. + for (RevObject obj : haveObjs) { + if (obj instanceof RevCommit) { + RevTree t = ((RevCommit) obj).getTree(); + depthWalk.markUninteresting(t); + } + } + if (unshallowObjects != null) { - for (ObjectId id : unshallowObjects) + for (ObjectId id : unshallowObjects) { depthWalk.markUnshallow(walker.parseAny(id)); + } } } else { for (RevObject obj : wantObjs) @@ -1716,8 +1884,8 @@ walker.markUninteresting(obj); final int maxBases = config.getDeltaSearchWindowSize(); - Set baseTrees = new HashSet(); - BlockList commits = new BlockList(); + Set baseTrees = new HashSet<>(); + BlockList commits = new BlockList<>(); Set roots = new HashSet<>(); RevCommit c; while ((c = walker.next()) != null) { @@ -1815,12 +1983,11 @@ } private void findObjectsToPackUsingBitmaps( - PackWriterBitmapWalker bitmapWalker, Set want, + BitmapWalker bitmapWalker, Set want, Set have) throws MissingObjectException, IncorrectObjectTypeException, IOException { BitmapBuilder haveBitmap = bitmapWalker.findObjects(have, null, true); - bitmapWalker.reset(); BitmapBuilder wantBitmap = bitmapWalker.findObjects(want, haveBitmap, false); BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap); @@ -1870,7 +2037,7 @@ * * @param object * the object to add. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object is an unsupported type. */ public void addObject(final RevObject object) @@ -1912,9 +2079,9 @@ /** * Select an object representation for this writer. *

    - * An {@link ObjectReader} implementation should invoke this method once for - * each representation available for an object, to allow the writer to find - * the most suitable one for the output. + * An {@link org.eclipse.jgit.lib.ObjectReader} implementation should invoke + * this method once for each representation available for an object, to + * allow the writer to find the most suitable one for the output. * * @param otp * the object being packed. @@ -1995,7 +2162,7 @@ * @param pm * progress monitor to report bitmap building work. * @return whether a bitmap index may be written. - * @throws IOException + * @throws java.io.IOException * when some I/O problem occur during reading objects. */ public boolean prepareBitmapIndex(ProgressMonitor pm) throws IOException { @@ -2015,21 +2182,19 @@ byName = null; PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer( - reader, writeBitmaps, pm, stats.interestingObjects); + reader, writeBitmaps, pm, stats.interestingObjects, config); - Collection selectedCommits = - bitmapPreparer.doCommitSelection(numCommits); + Collection selectedCommits = bitmapPreparer + .selectCommits(numCommits, excludeFromBitmapSelection); beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size()); - PackWriterBitmapWalker walker = bitmapPreparer.newBitmapWalker(); + BitmapWalker walker = bitmapPreparer.newBitmapWalker(); AnyObjectId last = null; for (PackWriterBitmapPreparer.BitmapCommit cmit : selectedCommits) { - if (cmit.isReuseWalker()) - walker.reset(); - else + if (!cmit.isReuseWalker()) { walker = bitmapPreparer.newBitmapWalker(); - + } BitmapBuilder bitmap = walker.findObjects( Collections.singleton(cmit), null, false); @@ -2205,8 +2370,6 @@ * @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 statistics.getBitmapIndexMisses(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/StoredObjectRepresentation.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/StoredObjectRepresentation.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/StoredObjectRepresentation.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/StoredObjectRepresentation.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import org.eclipse.jgit.lib.ObjectId; /** - * An object representation {@link PackWriter} can consider for packing. + * An object representation + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} can consider for + * packing. */ public class StoredObjectRepresentation { /** Special unknown value for {@link #getWeight()}. */ @@ -62,6 +64,8 @@ public static final int FORMAT_OTHER = 2; /** + * Get relative size of this object's packed form. + * * @return relative size of this object's packed form. The special value * {@link #WEIGHT_UNKNOWN} can be returned to indicate the * implementation doesn't know, or cannot supply the weight up @@ -72,6 +76,8 @@ } /** + * Get the storage format type + * * @return the storage format type, which must be one of * {@link #PACK_DELTA}, {@link #PACK_WHOLE}, or * {@link #FORMAT_OTHER}. @@ -81,6 +87,9 @@ } /** + * Get identity of the object this delta applies to in order to recover the + * original object content. + * * @return identity of the object this delta applies to in order to recover * the original object content. This method should only be called if * {@link #getFormat()} returned {@link #PACK_DELTA}. @@ -90,6 +99,9 @@ } /** + * Whether the current representation of the object has had delta + * compression attempted on it. + * * @return whether the current representation of the object has had delta * compression attempted on it. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.compare; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.lib.CheckoutEntry; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** Reads a single block for {@link ReftableReader}. */ +class BlockReader { + private byte blockType; + private long endPosition; + private boolean truncated; + + private byte[] buf; + private int bufLen; + private int ptr; + + private int keysStart; + private int keysEnd; + + private int restartCnt; + private int restartTbl; + + private byte[] nameBuf = new byte[256]; + private int nameLen; + private int valueType; + + byte type() { + return blockType; + } + + boolean truncated() { + return truncated; + } + + long endPosition() { + return endPosition; + } + + boolean next() { + return ptr < keysEnd; + } + + void parseKey() { + int pfx = readVarint32(); + valueType = readVarint32(); + int sfx = valueType >>> 3; + if (pfx + sfx > nameBuf.length) { + int n = Math.max(pfx + sfx, nameBuf.length * 2); + nameBuf = Arrays.copyOf(nameBuf, n); + } + System.arraycopy(buf, ptr, nameBuf, pfx, sfx); + ptr += sfx; + nameLen = pfx + sfx; + } + + String name() { + int len = nameLen; + if (blockType == LOG_BLOCK_TYPE) { + len -= 9; + } + return RawParseUtils.decode(UTF_8, nameBuf, 0, len); + } + + boolean match(byte[] match, boolean matchIsPrefix) { + int len = nameLen; + if (blockType == LOG_BLOCK_TYPE) { + len -= 9; + } + if (matchIsPrefix) { + return len >= match.length + && compare( + match, 0, match.length, + nameBuf, 0, match.length) == 0; + } + return compare(match, 0, match.length, nameBuf, 0, len) == 0; + } + + long readPositionFromIndex() throws IOException { + if (blockType != INDEX_BLOCK_TYPE) { + throw invalidBlock(); + } + + readVarint32(); // skip prefix length + int n = readVarint32() >>> 3; + ptr += n; // skip name + return readVarint64(); + } + + long readUpdateIndexDelta() { + return readVarint64(); + } + + Ref readRef() throws IOException { + String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen); + switch (valueType & VALUE_TYPE_MASK) { + case VALUE_NONE: // delete + return newRef(name); + + case VALUE_1ID: + return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId()); + + case VALUE_2ID: { // annotated tag + ObjectId id1 = readValueId(); + ObjectId id2 = readValueId(); + return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2); + } + + case VALUE_SYMREF: { + String val = readValueString(); + return new SymbolicRef(name, newRef(val)); + } + + default: + throw invalidBlock(); + } + } + + @Nullable + LongList readBlockPositionList() { + int n = valueType & VALUE_TYPE_MASK; + if (n == 0) { + n = readVarint32(); + if (n == 0) { + return null; + } + } + + LongList b = new LongList(n); + b.add(readVarint64()); + for (int j = 1; j < n; j++) { + long prior = b.get(j - 1); + b.add(prior + readVarint64()); + } + return b; + } + + long readLogUpdateIndex() { + return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8)); + } + + @Nullable + ReflogEntry readLogEntry() { + if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) { + return null; + } + + ObjectId oldId = readValueId(); + ObjectId newId = readValueId(); + PersonIdent who = readPersonIdent(); + String msg = readValueString(); + + return new ReflogEntry() { + @Override + public ObjectId getOldId() { + return oldId; + } + + @Override + public ObjectId getNewId() { + return newId; + } + + @Override + public PersonIdent getWho() { + return who; + } + + @Override + public String getComment() { + return msg; + } + + @Override + public CheckoutEntry parseCheckout() { + return null; + } + }; + } + + private ObjectId readValueId() { + ObjectId id = ObjectId.fromRaw(buf, ptr); + ptr += OBJECT_ID_LENGTH; + return id; + } + + private String readValueString() { + int len = readVarint32(); + int end = ptr + len; + String s = RawParseUtils.decode(UTF_8, buf, ptr, end); + ptr = end; + return s; + } + + private PersonIdent readPersonIdent() { + String name = readValueString(); + String email = readValueString(); + long ms = readVarint64() * 1000; + int tz = readInt16(); + return new PersonIdent(name, email, ms, tz); + } + + void readBlock(BlockSource src, long pos, int fileBlockSize) + throws IOException { + readBlockIntoBuf(src, pos, fileBlockSize); + parseBlockStart(src, pos, fileBlockSize); + } + + private void readBlockIntoBuf(BlockSource src, long pos, int size) + throws IOException { + ByteBuffer b = src.read(pos, size); + bufLen = b.position(); + if (bufLen <= 0) { + throw invalidBlock(); + } + if (b.hasArray() && b.arrayOffset() == 0) { + buf = b.array(); + } else { + buf = new byte[bufLen]; + b.flip(); + b.get(buf); + } + endPosition = pos + bufLen; + } + + private void parseBlockStart(BlockSource src, long pos, int fileBlockSize) + throws IOException { + ptr = 0; + if (pos == 0) { + if (bufLen == FILE_HEADER_LEN) { + setupEmptyFileBlock(); + return; + } + ptr += FILE_HEADER_LEN; // first block begins with file header + } + + int typeAndSize = NB.decodeInt32(buf, ptr); + ptr += 4; + + blockType = (byte) (typeAndSize >>> 24); + int blockLen = decodeBlockLen(typeAndSize); + if (blockType == LOG_BLOCK_TYPE) { + // Log blocks must be inflated after the header. + long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize); + endPosition = pos + 4 + deflatedSize; + } + if (bufLen < blockLen) { + if (blockType != INDEX_BLOCK_TYPE) { + throw invalidBlock(); + } + // Its OK during sequential scan for an index block to have been + // partially read and be truncated in-memory. This happens when + // the index block is larger than the file's blockSize. Caller + // will break out of its scan loop once it sees the blockType. + truncated = true; + } else if (bufLen > blockLen) { + bufLen = blockLen; + } + + if (blockType != FILE_BLOCK_TYPE) { + restartCnt = NB.decodeUInt16(buf, bufLen - 2); + restartTbl = bufLen - (restartCnt * 3 + 2); + keysStart = ptr; + keysEnd = restartTbl; + } else { + keysStart = ptr; + keysEnd = ptr; + } + } + + static int decodeBlockLen(int typeAndSize) { + return typeAndSize & 0xffffff; + } + + private long inflateBuf(BlockSource src, long pos, int blockLen, + int fileBlockSize) throws IOException { + byte[] dst = new byte[blockLen]; + System.arraycopy(buf, 0, dst, 0, 4); + + long deflatedSize = 0; + Inflater inf = InflaterCache.get(); + try { + inf.setInput(buf, ptr, bufLen - ptr); + for (int o = 4;;) { + int n = inf.inflate(dst, o, dst.length - o); + o += n; + if (inf.finished()) { + deflatedSize = inf.getBytesRead(); + break; + } else if (n <= 0 && inf.needsInput()) { + long p = pos + 4 + inf.getBytesRead(); + readBlockIntoBuf(src, p, fileBlockSize); + inf.setInput(buf, 0, bufLen); + } else if (n <= 0) { + throw invalidBlock(); + } + } + } catch (DataFormatException e) { + throw invalidBlock(e); + } finally { + InflaterCache.release(inf); + } + + buf = dst; + bufLen = dst.length; + return deflatedSize; + } + + private void setupEmptyFileBlock() { + // An empty reftable has only the file header in first block. + blockType = FILE_BLOCK_TYPE; + ptr = FILE_HEADER_LEN; + restartCnt = 0; + restartTbl = bufLen; + keysStart = bufLen; + keysEnd = bufLen; + } + + void verifyIndex() throws IOException { + if (blockType != INDEX_BLOCK_TYPE || truncated) { + throw invalidBlock(); + } + } + + /** + * Finds a key in the block and positions the current pointer on its record. + *

    + * As a side-effect this method arranges for the current pointer to be near + * or exactly on {@code key}, allowing other methods to access data from + * that current record: + *

      + *
    • {@link #name()} + *
    • {@link #match(byte[], boolean)} + *
    • {@link #readRef()} + *
    • {@link #readLogUpdateIndex()} + *
    • {@link #readLogEntry()} + *
    • {@link #readBlockPositionList()} + *
    + * + * @param key + * key to find. + * @return {@code <0} if the key occurs before the start of this block; + * {@code 0} if the block is positioned on the key; {@code >0} if + * the key occurs after the last key of this block. + */ + int seekKey(byte[] key) { + int low = 0; + int end = restartCnt; + for (;;) { + int mid = (low + end) >>> 1; + int p = NB.decodeUInt24(buf, restartTbl + mid * 3); + ptr = p + 1; // skip 0 prefix length + int n = readVarint32() >>> 3; + int cmp = compare(key, 0, key.length, buf, ptr, n); + if (cmp < 0) { + end = mid; + } else if (cmp == 0) { + ptr = p; + return 0; + } else /* if (cmp > 0) */ { + low = mid + 1; + } + if (low >= end) { + return scanToKey(key, p, low, cmp); + } + } + } + + /** + * Performs the linear search step within a restart interval. + *

    + * Starts at a restart position whose key sorts before (or equal to) + * {@code key} and walks sequentially through the following prefix + * compressed records to find {@code key}. + * + * @param key + * key the caller wants to find. + * @param rPtr + * current record pointer from restart table binary search. + * @param rIdx + * current restart table index. + * @param rCmp + * result of compare from restart table binary search. + * @return {@code <0} if the key occurs before the start of this block; + * {@code 0} if the block is positioned on the key; {@code >0} if + * the key occurs after the last key of this block. + */ + private int scanToKey(byte[] key, int rPtr, int rIdx, int rCmp) { + if (rCmp < 0) { + if (rIdx == 0) { + ptr = keysStart; + return -1; + } + ptr = NB.decodeUInt24(buf, restartTbl + (rIdx - 1) * 3); + } else { + ptr = rPtr; + } + + int cmp; + do { + int savePtr = ptr; + parseKey(); + cmp = compare(key, 0, key.length, nameBuf, 0, nameLen); + if (cmp <= 0) { + // cmp < 0, name should be in this block, but is not. + // cmp = 0, block is positioned at name. + ptr = savePtr; + return cmp < 0 && savePtr == keysStart ? -1 : 0; + } + skipValue(); + } while (ptr < keysEnd); + return cmp; + } + + void skipValue() { + switch (blockType) { + case REF_BLOCK_TYPE: + readVarint64(); // update_index_delta + switch (valueType & VALUE_TYPE_MASK) { + case VALUE_NONE: + return; + case VALUE_1ID: + ptr += OBJECT_ID_LENGTH; + return; + case VALUE_2ID: + ptr += 2 * OBJECT_ID_LENGTH; + return; + case VALUE_SYMREF: + skipString(); + return; + } + break; + + case OBJ_BLOCK_TYPE: { + int n = valueType & VALUE_TYPE_MASK; + if (n == 0) { + n = readVarint32(); + } + while (n-- > 0) { + readVarint32(); + } + return; + } + + case INDEX_BLOCK_TYPE: + readVarint32(); + return; + + case LOG_BLOCK_TYPE: + if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) { + return; + } else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) { + ptr += 2 * OBJECT_ID_LENGTH; // oldId, newId + skipString(); // name + skipString(); // email + readVarint64(); // time + ptr += 2; // tz + skipString(); // msg + return; + } + } + + throw new IllegalStateException(); + } + + private void skipString() { + int n = readVarint32(); // string length + ptr += n; + } + + private short readInt16() { + return (short) NB.decodeUInt16(buf, ptr += 2); + } + + private int readVarint32() { + byte c = buf[ptr++]; + int val = c & 0x7f; + while ((c & 0x80) != 0) { + c = buf[ptr++]; + val++; + val <<= 7; + val |= (c & 0x7f); + } + return val; + } + + private long readVarint64() { + byte c = buf[ptr++]; + long val = c & 0x7f; + while ((c & 0x80) != 0) { + c = buf[ptr++]; + val++; + val <<= 7; + val |= (c & 0x7f); + } + return val; + } + + private static Ref newRef(String name) { + return new ObjectIdRef.Unpeeled(NEW, name, null); + } + + private static IOException invalidBlock() { + return invalidBlock(null); + } + + private static IOException invalidBlock(Throwable cause) { + return new IOException(JGitText.get().invalidReftableBlock, cause); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; + +/** + * Thrown if {@link org.eclipse.jgit.internal.storage.reftable.ReftableWriter} + * cannot fit a reference. + */ +public class BlockSizeTooSmallException extends IOException { + private static final long serialVersionUID = 1L; + + private final int minBlockSize; + + BlockSizeTooSmallException(int b) { + minBlockSize = b; + } + + /** + * Get minimum block size in bytes reftable requires to write a ref. + * + * @return minimum block size in bytes reftable requires to write a ref. + */ + public int getMinimumBlockSize() { + return minBlockSize; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex; +import static org.eclipse.jgit.internal.storage.reftable.ReftableOutputStream.computeVarintSize; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.util.IntList; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.NB; + +/** Formats and writes blocks for {@link ReftableWriter}. */ +class BlockWriter { + private final byte blockType; + private final byte keyType; + private final List entries; + private final int blockLimitBytes; + private final int restartInterval; + + private int entriesSumBytes; + private int restartCnt; + + BlockWriter(byte type, byte kt, int bs, int ri) { + blockType = type; + keyType = kt; + blockLimitBytes = bs; + restartInterval = ri; + entries = new ArrayList<>(estimateEntryCount(type, kt, bs)); + } + + private static int estimateEntryCount(byte blockType, byte keyType, + int blockLimitBytes) { + double avgBytesPerEntry; + switch (blockType) { + case REF_BLOCK_TYPE: + default: + avgBytesPerEntry = 35.31; + break; + + case OBJ_BLOCK_TYPE: + avgBytesPerEntry = 4.19; + break; + + case LOG_BLOCK_TYPE: + avgBytesPerEntry = 101.14; + break; + + case INDEX_BLOCK_TYPE: + switch (keyType) { + case REF_BLOCK_TYPE: + case LOG_BLOCK_TYPE: + default: + avgBytesPerEntry = 27.44; + break; + + case OBJ_BLOCK_TYPE: + avgBytesPerEntry = 11.57; + break; + } + } + + int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry)); + return Math.min(cnt, 4096); + } + + byte blockType() { + return blockType; + } + + boolean padBetweenBlocks() { + return padBetweenBlocks(blockType) + || (blockType == INDEX_BLOCK_TYPE && padBetweenBlocks(keyType)); + } + + static boolean padBetweenBlocks(byte type) { + return type == REF_BLOCK_TYPE || type == OBJ_BLOCK_TYPE; + } + + byte[] lastKey() { + return entries.get(entries.size() - 1).key; + } + + int currentSize() { + return computeBlockBytes(0, false); + } + + void mustAdd(Entry entry) throws BlockSizeTooSmallException { + if (!tryAdd(entry, true)) { + // Insanely long names need a larger block size. + throw blockSizeTooSmall(entry); + } + } + + boolean tryAdd(Entry entry) { + if (entry instanceof ObjEntry + && computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) { + // If the ObjEntry has so many ref block pointers that its + // encoding overflows any block, reconfigure it to tell readers to + // instead scan all refs for this ObjectId. That significantly + // shrinks the entry to a very small size, which may now fit into + // this block. + ((ObjEntry) entry).markScanRequired(); + } + + if (tryAdd(entry, true)) { + return true; + } else if (nextShouldBeRestart()) { + // It was time for another restart, but the entry doesn't fit + // with its complete key, as the block is nearly full. Try to + // force it to fit with prefix compression rather than waste + // the tail of the block with padding. + return tryAdd(entry, false); + } + return false; + } + + private boolean tryAdd(Entry entry, boolean tryRestart) { + byte[] key = entry.key; + int prefixLen = 0; + boolean restart = tryRestart && nextShouldBeRestart(); + if (!restart) { + Entry priorEntry = entries.get(entries.size() - 1); + byte[] prior = priorEntry.key; + prefixLen = commonPrefix(prior, prior.length, key); + if (prefixLen <= 5 /* "refs/" */ && keyType == REF_BLOCK_TYPE) { + // Force restart points at transitions between namespaces + // such as "refs/heads/" to "refs/tags/". + restart = true; + prefixLen = 0; + } else if (prefixLen == 0) { + restart = true; + } + } + + entry.restart = restart; + entry.prefixLen = prefixLen; + int entryBytes = entry.sizeBytes(); + if (computeBlockBytes(entryBytes, restart) > blockLimitBytes) { + return false; + } + + entriesSumBytes += entryBytes; + entries.add(entry); + if (restart) { + restartCnt++; + } + return true; + } + + private boolean nextShouldBeRestart() { + int cnt = entries.size(); + return (cnt == 0 || ((cnt + 1) % restartInterval) == 0) + && restartCnt < MAX_RESTARTS; + } + + private int computeBlockBytes(int entryBytes, boolean restart) { + return computeBlockBytes( + entriesSumBytes + entryBytes, + restartCnt + (restart ? 1 : 0)); + } + + private static int computeBlockBytes(int entryBytes, int restartCnt) { + return 4 // 4-byte block header + + entryBytes + + restartCnt * 3 // restart_offset + + 2; // 2-byte restart_count + } + + void writeTo(ReftableOutputStream os) throws IOException { + os.beginBlock(blockType); + IntList restarts = new IntList(restartCnt); + for (Entry entry : entries) { + if (entry.restart) { + restarts.add(os.bytesWrittenInBlock()); + } + entry.writeKey(os); + entry.writeValue(os); + } + if (restarts.size() == 0 || restarts.size() > MAX_RESTARTS) { + throw new IllegalStateException(); + } + for (int i = 0; i < restarts.size(); i++) { + os.writeInt24(restarts.get(i)); + } + os.writeInt16(restarts.size()); + os.flushBlock(); + } + + private BlockSizeTooSmallException blockSizeTooSmall(Entry entry) { + // Compute size required to fit this entry by itself. + int min = FILE_HEADER_LEN + computeBlockBytes(entry.sizeBytes(), 1); + return new BlockSizeTooSmallException(min); + } + + static int commonPrefix(byte[] a, int n, byte[] b) { + int len = Math.min(n, Math.min(a.length, b.length)); + for (int i = 0; i < len; i++) { + if (a[i] != b[i]) { + return i; + } + } + return len; + } + + static int encodeSuffixAndType(int sfx, int valueType) { + return (sfx << 3) | valueType; + } + + static int compare( + byte[] a, int ai, int aLen, + byte[] b, int bi, int bLen) { + int aEnd = ai + aLen; + int bEnd = bi + bLen; + while (ai < aEnd && bi < bEnd) { + int c = (a[ai++] & 0xff) - (b[bi++] & 0xff); + if (c != 0) { + return c; + } + } + return aLen - bLen; + } + + static abstract class Entry { + static int compare(Entry ea, Entry eb) { + byte[] a = ea.key; + byte[] b = eb.key; + return BlockWriter.compare(a, 0, a.length, b, 0, b.length); + } + + final byte[] key; + int prefixLen; + boolean restart; + + Entry(byte[] key) { + this.key = key; + } + + void writeKey(ReftableOutputStream os) { + int sfxLen = key.length - prefixLen; + os.writeVarint(prefixLen); + os.writeVarint(encodeSuffixAndType(sfxLen, valueType())); + os.write(key, prefixLen, sfxLen); + } + + int sizeBytes() { + int sfxLen = key.length - prefixLen; + int sfx = encodeSuffixAndType(sfxLen, valueType()); + return computeVarintSize(prefixLen) + + computeVarintSize(sfx) + + sfxLen + + valueSize(); + } + + abstract byte blockType(); + abstract int valueType(); + abstract int valueSize(); + abstract void writeValue(ReftableOutputStream os) throws IOException; + } + + static class IndexEntry extends Entry { + private final long blockPosition; + + IndexEntry(byte[] key, long blockPosition) { + super(key); + this.blockPosition = blockPosition; + } + + @Override + byte blockType() { + return INDEX_BLOCK_TYPE; + } + + @Override + int valueType() { + return 0; + } + + @Override + int valueSize() { + return computeVarintSize(blockPosition); + } + + @Override + void writeValue(ReftableOutputStream os) { + os.writeVarint(blockPosition); + } + } + + static class RefEntry extends Entry { + final Ref ref; + final long updateIndexDelta; + + RefEntry(Ref ref, long updateIndexDelta) { + super(nameUtf8(ref)); + this.ref = ref; + this.updateIndexDelta = updateIndexDelta; + } + + @Override + byte blockType() { + return REF_BLOCK_TYPE; + } + + @Override + int valueType() { + if (ref.isSymbolic()) { + return VALUE_SYMREF; + } else if (ref.getStorage() == NEW && ref.getObjectId() == null) { + return VALUE_NONE; + } else if (ref.getPeeledObjectId() != null) { + return VALUE_2ID; + } else { + return VALUE_1ID; + } + } + + @Override + int valueSize() { + int n = computeVarintSize(updateIndexDelta); + switch (valueType()) { + case VALUE_NONE: + return n; + case VALUE_1ID: + return n + OBJECT_ID_LENGTH; + case VALUE_2ID: + return n + 2 * OBJECT_ID_LENGTH; + case VALUE_SYMREF: + if (ref.isSymbolic()) { + int nameLen = nameUtf8(ref.getTarget()).length; + return n + computeVarintSize(nameLen) + nameLen; + } + } + throw new IllegalStateException(); + } + + @Override + void writeValue(ReftableOutputStream os) throws IOException { + os.writeVarint(updateIndexDelta); + switch (valueType()) { + case VALUE_NONE: + return; + + case VALUE_1ID: { + ObjectId id1 = ref.getObjectId(); + if (!ref.isPeeled()) { + throw new IOException(JGitText.get().peeledRefIsRequired); + } else if (id1 == null) { + throw new IOException(JGitText.get().invalidId0); + } + os.writeId(id1); + return; + } + + case VALUE_2ID: { + ObjectId id1 = ref.getObjectId(); + ObjectId id2 = ref.getPeeledObjectId(); + if (!ref.isPeeled()) { + throw new IOException(JGitText.get().peeledRefIsRequired); + } else if (id1 == null || id2 == null) { + throw new IOException(JGitText.get().invalidId0); + } + os.writeId(id1); + os.writeId(id2); + return; + } + + case VALUE_SYMREF: + if (ref.isSymbolic()) { + os.writeVarintString(ref.getTarget().getName()); + return; + } + } + throw new IllegalStateException(); + } + + private static byte[] nameUtf8(Ref ref) { + return ref.getName().getBytes(UTF_8); + } + } + + static class ObjEntry extends Entry { + final LongList blockPos; + + ObjEntry(int idLen, ObjectId id, LongList blockPos) { + super(key(idLen, id)); + this.blockPos = blockPos; + } + + private static byte[] key(int idLen, ObjectId id) { + byte[] key = new byte[OBJECT_ID_LENGTH]; + id.copyRawTo(key, 0); + if (idLen < OBJECT_ID_LENGTH) { + return Arrays.copyOf(key, idLen); + } + return key; + } + + void markScanRequired() { + blockPos.clear(); + } + + @Override + byte blockType() { + return OBJ_BLOCK_TYPE; + } + + @Override + int valueType() { + int cnt = blockPos.size(); + return cnt != 0 && cnt <= VALUE_TYPE_MASK ? cnt : 0; + } + + @Override + int valueSize() { + int cnt = blockPos.size(); + if (cnt == 0) { + return computeVarintSize(0); + } + + int n = 0; + if (cnt > VALUE_TYPE_MASK) { + n += computeVarintSize(cnt); + } + n += computeVarintSize(blockPos.get(0)); + for (int j = 1; j < cnt; j++) { + long prior = blockPos.get(j - 1); + long b = blockPos.get(j); + n += computeVarintSize(b - prior); + } + return n; + } + + @Override + void writeValue(ReftableOutputStream os) throws IOException { + int cnt = blockPos.size(); + if (cnt == 0) { + os.writeVarint(0); + return; + } + + if (cnt > VALUE_TYPE_MASK) { + os.writeVarint(cnt); + } + os.writeVarint(blockPos.get(0)); + for (int j = 1; j < cnt; j++) { + long prior = blockPos.get(j - 1); + long b = blockPos.get(j); + os.writeVarint(b - prior); + } + } + } + + static class DeleteLogEntry extends Entry { + DeleteLogEntry(String refName, long updateIndex) { + super(LogEntry.key(refName, updateIndex)); + } + + @Override + byte blockType() { + return LOG_BLOCK_TYPE; + } + + @Override + int valueType() { + return LOG_NONE; + } + + @Override + int valueSize() { + return 0; + } + + @Override + void writeValue(ReftableOutputStream os) { + // Nothing in a delete log record. + } + } + + static class LogEntry extends Entry { + final ObjectId oldId; + final ObjectId newId; + final long timeSecs; + final short tz; + final byte[] name; + final byte[] email; + final byte[] msg; + + LogEntry(String refName, long updateIndex, PersonIdent who, + ObjectId oldId, ObjectId newId, String message) { + super(key(refName, updateIndex)); + + this.oldId = oldId; + this.newId = newId; + this.timeSecs = who.getWhen().getTime() / 1000L; + this.tz = (short) who.getTimeZoneOffset(); + this.name = who.getName().getBytes(UTF_8); + this.email = who.getEmailAddress().getBytes(UTF_8); + this.msg = message.getBytes(UTF_8); + } + + static byte[] key(String ref, long index) { + byte[] name = ref.getBytes(UTF_8); + byte[] key = Arrays.copyOf(name, name.length + 1 + 8); + NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index)); + return key; + } + + @Override + byte blockType() { + return LOG_BLOCK_TYPE; + } + + @Override + int valueType() { + return LOG_DATA; + } + + @Override + int valueSize() { + return 2 * OBJECT_ID_LENGTH + + computeVarintSize(name.length) + name.length + + computeVarintSize(email.length) + email.length + + computeVarintSize(timeSecs) + + 2 // tz + + computeVarintSize(msg.length) + msg.length; + } + + @Override + void writeValue(ReftableOutputStream os) { + os.writeId(oldId); + os.writeId(newId); + os.writeVarintString(name); + os.writeVarintString(email); + os.writeVarint(timeSecs); + os.writeInt16(tz); + os.writeVarintString(msg); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ReflogEntry; + +/** Empty {@link LogCursor} with no results. */ +class EmptyLogCursor extends LogCursor { + /** {@inheritDoc} */ + @Override + public boolean next() throws IOException { + return false; + } + + /** {@inheritDoc} */ + @Override + public String getRefName() { + return null; + } + + /** {@inheritDoc} */ + @Override + public long getUpdateIndex() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public ReflogEntry getReflogEntry() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void close() { + // Do nothing. + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ReflogEntry; + +/** + * Iterator over logs inside a + * {@link org.eclipse.jgit.internal.storage.reftable.Reftable}. + */ +public abstract class LogCursor implements AutoCloseable { + /** + * Check if another log record is available. + * + * @return {@code true} if there is another result. + * @throws java.io.IOException + * logs cannot be read. + */ + public abstract boolean next() throws IOException; + + /** + * Get name of the current reference. + * + * @return name of the current reference. + */ + public abstract String getRefName(); + + /** + * Get identifier of the transaction that created the log record. + * + * @return identifier of the transaction that created the log record. + */ + public abstract long getUpdateIndex(); + + /** + * Get current log entry. + * + * @return current log entry. + */ + public abstract ReflogEntry getReflogEntry(); + + /** {@inheritDoc} */ + @Override + public abstract void close(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; +import java.util.List; +import java.util.PriorityQueue; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogEntry; + +/** + * Merges multiple reference tables together. + *

    + * A {@link org.eclipse.jgit.internal.storage.reftable.MergedReftable} + * merge-joins multiple + * {@link org.eclipse.jgit.internal.storage.reftable.ReftableReader} on the fly. + * Tables higher/later in the stack shadow lower/earlier tables, hiding + * references that been updated/replaced. + *

    + * By default deleted references are skipped and not returned to the caller. + * {@link #setIncludeDeletes(boolean)} can be used to modify this behavior if + * the caller needs to preserve deletions during partial compaction. + *

    + * A {@code MergedReftable} is not thread-safe. + */ +public class MergedReftable extends Reftable { + private final Reftable[] tables; + + /** + * Initialize a merged table reader. + *

    + * The tables in {@code tableStack} will be closed when this + * {@code MergedReftable} is closed. + * + * @param tableStack + * stack of tables to read from. The base of the stack is at + * index 0, the most recent should be at the top of the stack at + * {@code tableStack.size() - 1}. The top of the stack (higher + * index) shadows the base of the stack (lower index). + */ + public MergedReftable(List tableStack) { + tables = tableStack.toArray(new Reftable[0]); + + // Tables must expose deletes to this instance to correctly + // shadow references from lower tables. + for (Reftable t : tables) { + t.setIncludeDeletes(true); + } + } + + /** {@inheritDoc} */ + @Override + public RefCursor allRefs() throws IOException { + MergedRefCursor m = new MergedRefCursor(); + for (int i = 0; i < tables.length; i++) { + m.add(new RefQueueEntry(tables[i].allRefs(), i)); + } + return m; + } + + /** {@inheritDoc} */ + @Override + public RefCursor seekRef(String name) throws IOException { + MergedRefCursor m = new MergedRefCursor(); + for (int i = 0; i < tables.length; i++) { + m.add(new RefQueueEntry(tables[i].seekRef(name), i)); + } + return m; + } + + /** {@inheritDoc} */ + @Override + public RefCursor byObjectId(AnyObjectId name) throws IOException { + MergedRefCursor m = new MergedRefCursor(); + for (int i = 0; i < tables.length; i++) { + m.add(new RefQueueEntry(tables[i].byObjectId(name), i)); + } + return m; + } + + /** {@inheritDoc} */ + @Override + public LogCursor allLogs() throws IOException { + MergedLogCursor m = new MergedLogCursor(); + for (int i = 0; i < tables.length; i++) { + m.add(new LogQueueEntry(tables[i].allLogs(), i)); + } + return m; + } + + /** {@inheritDoc} */ + @Override + public LogCursor seekLog(String refName, long updateIdx) + throws IOException { + MergedLogCursor m = new MergedLogCursor(); + for (int i = 0; i < tables.length; i++) { + m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i)); + } + return m; + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + for (Reftable t : tables) { + t.close(); + } + } + + int queueSize() { + return Math.max(1, tables.length); + } + + private class MergedRefCursor extends RefCursor { + private final PriorityQueue queue; + private RefQueueEntry head; + private Ref ref; + private long updateIndex; + + MergedRefCursor() { + queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare); + } + + void add(RefQueueEntry t) throws IOException { + // Common case is many iterations over the same RefQueueEntry + // for the bottom of the stack (scanning all refs). Its almost + // always less than the top of the queue. Avoid the queue's + // O(log N) insertion and removal costs for this common case. + if (!t.rc.next()) { + t.rc.close(); + } else if (head == null) { + RefQueueEntry p = queue.peek(); + if (p == null || RefQueueEntry.compare(t, p) < 0) { + head = t; + } else { + head = queue.poll(); + queue.add(t); + } + } else if (RefQueueEntry.compare(t, head) > 0) { + queue.add(t); + } else { + queue.add(head); + head = t; + } + } + + @Override + public boolean next() throws IOException { + for (;;) { + RefQueueEntry t = poll(); + if (t == null) { + return false; + } + + ref = t.rc.getRef(); + updateIndex = t.rc.getUpdateIndex(); + boolean include = includeDeletes || !t.rc.wasDeleted(); + add(t); + skipShadowedRefs(ref.getName()); + if (include) { + return true; + } + } + } + + private RefQueueEntry poll() { + RefQueueEntry e = head; + if (e != null) { + head = null; + return e; + } + return queue.poll(); + } + + private void skipShadowedRefs(String name) throws IOException { + for (;;) { + RefQueueEntry t = head != null ? head : queue.peek(); + if (t != null && name.equals(t.name())) { + add(poll()); + } else { + break; + } + } + } + + @Override + public Ref getRef() { + return ref; + } + + @Override + public long getUpdateIndex() { + return updateIndex; + } + + @Override + public void close() { + if (head != null) { + head.rc.close(); + head = null; + } + while (!queue.isEmpty()) { + queue.remove().rc.close(); + } + } + } + + private static class RefQueueEntry { + static int compare(RefQueueEntry a, RefQueueEntry b) { + int cmp = a.name().compareTo(b.name()); + if (cmp == 0) { + // higher updateIndex shadows lower updateIndex. + cmp = Long.signum(b.updateIndex() - a.updateIndex()); + } + if (cmp == 0) { + // higher index shadows lower index, so higher index first. + cmp = b.stackIdx - a.stackIdx; + } + return cmp; + } + + final RefCursor rc; + final int stackIdx; + + RefQueueEntry(RefCursor rc, int stackIdx) { + this.rc = rc; + this.stackIdx = stackIdx; + } + + String name() { + return rc.getRef().getName(); + } + + long updateIndex() { + return rc.getUpdateIndex(); + } + } + + private class MergedLogCursor extends LogCursor { + private final PriorityQueue queue; + private String refName; + private long updateIndex; + private ReflogEntry entry; + + MergedLogCursor() { + queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare); + } + + void add(LogQueueEntry t) throws IOException { + if (t.lc.next()) { + queue.add(t); + } else { + t.lc.close(); + } + } + + @Override + public boolean next() throws IOException { + for (;;) { + LogQueueEntry t = queue.poll(); + if (t == null) { + return false; + } + + refName = t.lc.getRefName(); + updateIndex = t.lc.getUpdateIndex(); + entry = t.lc.getReflogEntry(); + boolean include = includeDeletes || entry != null; + skipShadowed(refName, updateIndex); + add(t); + if (include) { + return true; + } + } + } + + private void skipShadowed(String name, long index) throws IOException { + for (;;) { + LogQueueEntry t = queue.peek(); + if (t != null && name.equals(t.name()) && index == t.index()) { + add(queue.remove()); + } else { + break; + } + } + } + + @Override + public String getRefName() { + return refName; + } + + @Override + public long getUpdateIndex() { + return updateIndex; + } + + @Override + public ReflogEntry getReflogEntry() { + return entry; + } + + @Override + public void close() { + while (!queue.isEmpty()) { + queue.remove().lc.close(); + } + } + } + + private static class LogQueueEntry { + static int compare(LogQueueEntry a, LogQueueEntry b) { + int cmp = a.name().compareTo(b.name()); + if (cmp == 0) { + // higher update index sorts first. + cmp = Long.signum(b.index() - a.index()); + } + if (cmp == 0) { + // higher index comes first. + cmp = b.stackIdx - a.stackIdx; + } + return cmp; + } + + final LogCursor lc; + final int stackIdx; + + LogQueueEntry(LogCursor lc, int stackIdx) { + this.lc = lc; + this.stackIdx = stackIdx; + } + + String name() { + return lc.getRefName(); + } + + long index() { + return lc.getUpdateIndex(); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; + +import org.eclipse.jgit.lib.Ref; + +/** + * Iterator over references inside a + * {@link org.eclipse.jgit.internal.storage.reftable.Reftable}. + */ +public abstract class RefCursor implements AutoCloseable { + /** + * Check if another reference is available. + * + * @return {@code true} if there is another result. + * @throws java.io.IOException + * references cannot be read. + */ + public abstract boolean next() throws IOException; + + /** + * Get reference at the current position. + * + * @return reference at the current position. + */ + public abstract Ref getRef(); + + /** + * Get updateIndex that last modified the current reference. + * + * @return updateIndex that last modified the current reference. + */ + public abstract long getUpdateIndex(); + + /** + * Whether the current reference was deleted. + * + * @return {@code true} if the current reference was deleted. + */ + public boolean wasDeleted() { + Ref r = getRef(); + return r.getStorage() == Ref.Storage.NEW && r.getObjectId() == null; + } + + /** {@inheritDoc} */ + @Override + public abstract void close(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ReflogEntry; + +/** + * Merges reftables and compacts them into a single output. + *

    + * For a partial compaction callers should {@link #setIncludeDeletes(boolean)} + * to {@code true} to ensure the new reftable continues to use a delete marker + * to shadow any lower reftable that may have the reference present. + *

    + * By default all log entries within the range defined by + * {@link #setMinUpdateIndex(long)} and {@link #setMaxUpdateIndex(long)} are + * copied, even if no references in the output file match the log records. + * Callers may truncate the log to a more recent time horizon with + * {@link #setOldestReflogTimeMillis(long)}, or disable the log altogether with + * {@code setOldestReflogTimeMillis(Long.MAX_VALUE)}. + */ +public class ReftableCompactor { + private final ReftableWriter writer = new ReftableWriter(); + private final ArrayDeque tables = new ArrayDeque<>(); + + private long compactBytesLimit; + private long bytesToCompact; + private boolean includeDeletes; + private long minUpdateIndex = -1; + private long maxUpdateIndex; + private long oldestReflogTimeMillis; + private Stats stats; + + /** + * Set configuration for the reftable. + * + * @param cfg + * configuration for the reftable. + * @return {@code this} + */ + public ReftableCompactor setConfig(ReftableConfig cfg) { + writer.setConfig(cfg); + return this; + } + + /** + * Set limit on number of bytes from source tables to compact. + * + * @param bytes + * limit on number of bytes from source tables to compact. + * @return {@code this} + */ + public ReftableCompactor setCompactBytesLimit(long bytes) { + compactBytesLimit = bytes; + return this; + } + + /** + * Whether to include deletions in the output, which may be necessary for + * partial compaction. + * + * @param deletes + * {@code true} to include deletions in the output, which may be + * necessary for partial compaction. + * @return {@code this} + */ + public ReftableCompactor setIncludeDeletes(boolean deletes) { + includeDeletes = deletes; + return this; + } + + /** + * Set the minimum update index for log entries that appear in the compacted + * reftable. + * + * @param min + * the minimum update index for log entries that appear in the + * compacted reftable. This should be 1 higher than the prior + * reftable's {@code maxUpdateIndex} if this table will be used + * in a stack. + * @return {@code this} + */ + public ReftableCompactor setMinUpdateIndex(long min) { + minUpdateIndex = min; + return this; + } + + /** + * Set the maximum update index for log entries that appear in the compacted + * reftable. + * + * @param max + * the maximum update index for log entries that appear in the + * compacted reftable. This should be at least 1 higher than the + * prior reftable's {@code maxUpdateIndex} if this table will be + * used in a stack. + * @return {@code this} + */ + public ReftableCompactor setMaxUpdateIndex(long max) { + maxUpdateIndex = max; + return this; + } + + /** + * Set oldest reflog time to preserve. + * + * @param timeMillis + * oldest log time to preserve. Entries whose timestamps are + * {@code >= timeMillis} will be copied into the output file. Log + * entries that predate {@code timeMillis} will be discarded. + * Specified in Java standard milliseconds since the epoch. + * @return {@code this} + */ + public ReftableCompactor setOldestReflogTimeMillis(long timeMillis) { + oldestReflogTimeMillis = timeMillis; + return this; + } + + /** + * Add all of the tables, in the specified order. + *

    + * Unconditionally adds all tables, ignoring the + * {@link #setCompactBytesLimit(long)}. + * + * @param readers + * tables to compact. Tables should be ordered oldest first/most + * recent last so that the more recent tables can shadow the + * older results. Caller is responsible for closing the readers. + * @throws java.io.IOException + * update indexes of a reader cannot be accessed. + */ + public void addAll(List readers) throws IOException { + tables.addAll(readers); + for (Reftable r : readers) { + if (r instanceof ReftableReader) { + adjustUpdateIndexes((ReftableReader) r); + } + } + } + + /** + * Try to add this reader at the bottom of the stack. + *

    + * A reader may be rejected by returning {@code false} if the compactor is + * already rewriting its {@link #setCompactBytesLimit(long)}. When this + * happens the caller should stop trying to add tables, and execute the + * compaction. + * + * @param reader + * the reader to insert at the bottom of the stack. Caller is + * responsible for closing the reader. + * @return {@code true} if the compactor accepted this table; {@code false} + * if the compactor has reached its limit. + * @throws java.io.IOException + * if size of {@code reader}, or its update indexes cannot be read. + */ + public boolean tryAddFirst(ReftableReader reader) throws IOException { + long sz = reader.size(); + if (compactBytesLimit > 0 && bytesToCompact + sz > compactBytesLimit) { + return false; + } + bytesToCompact += sz; + adjustUpdateIndexes(reader); + tables.addFirst(reader); + return true; + } + + private void adjustUpdateIndexes(ReftableReader reader) throws IOException { + if (minUpdateIndex == -1) { + minUpdateIndex = reader.minUpdateIndex(); + } else { + minUpdateIndex = Math.min(minUpdateIndex, reader.minUpdateIndex()); + } + maxUpdateIndex = Math.max(maxUpdateIndex, reader.maxUpdateIndex()); + } + + /** + * Write a compaction to {@code out}. + * + * @param out + * stream to write the compacted tables to. Caller is responsible + * for closing {@code out}. + * @throws java.io.IOException + * if tables cannot be read, or cannot be written. + */ + public void compact(OutputStream out) throws IOException { + MergedReftable mr = new MergedReftable(new ArrayList<>(tables)); + mr.setIncludeDeletes(includeDeletes); + + writer.setMinUpdateIndex(Math.max(minUpdateIndex, 0)); + writer.setMaxUpdateIndex(maxUpdateIndex); + writer.begin(out); + mergeRefs(mr); + mergeLogs(mr); + writer.finish(); + stats = writer.getStats(); + } + + /** + * Get statistics of the last written reftable. + * + * @return statistics of the last written reftable. + */ + public Stats getStats() { + return stats; + } + + private void mergeRefs(MergedReftable mr) throws IOException { + try (RefCursor rc = mr.allRefs()) { + while (rc.next()) { + writer.writeRef(rc.getRef(), rc.getUpdateIndex()); + } + } + } + + private void mergeLogs(MergedReftable mr) throws IOException { + if (oldestReflogTimeMillis == Long.MAX_VALUE) { + return; + } + + try (LogCursor lc = mr.allLogs()) { + while (lc.next()) { + long updateIndex = lc.getUpdateIndex(); + if (updateIndex < minUpdateIndex + || updateIndex > maxUpdateIndex) { + // Cannot merge log records outside the header's range. + continue; + } + + String refName = lc.getRefName(); + ReflogEntry log = lc.getReflogEntry(); + if (log == null) { + if (includeDeletes) { + writer.deleteLog(refName, updateIndex); + } + continue; + } + + PersonIdent who = log.getWho(); + if (who.getWhen().getTime() >= oldestReflogTimeMillis) { + writer.writeLog( + refName, + updateIndex, + who, + log.getOldId(), + log.getNewId(), + log.getComment()); + } + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE; + +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Repository; + +/** + * Configuration used by a reftable writer when constructing the stream. + */ +public class ReftableConfig { + private int refBlockSize = 4 << 10; + private int logBlockSize; + private int restartInterval; + private int maxIndexLevels; + private boolean alignBlocks = true; + private boolean indexObjects = true; + + /** + * Create a default configuration. + */ + public ReftableConfig() { + } + + /** + * Create a configuration honoring the repository's settings. + * + * @param db + * the repository to read settings from. The repository is not + * retained by the new configuration, instead its settings are + * copied during the constructor. + */ + public ReftableConfig(Repository db) { + fromConfig(db.getConfig()); + } + + /** + * Create a configuration honoring settings in a + * {@link org.eclipse.jgit.lib.Config}. + * + * @param cfg + * the source to read settings from. The source is not retained + * by the new configuration, instead its settings are copied + * during the constructor. + */ + public ReftableConfig(Config cfg) { + fromConfig(cfg); + } + + /** + * Copy an existing configuration to a new instance. + * + * @param cfg + * the source configuration to copy from. + */ + public ReftableConfig(ReftableConfig cfg) { + this.refBlockSize = cfg.refBlockSize; + this.logBlockSize = cfg.logBlockSize; + this.restartInterval = cfg.restartInterval; + this.maxIndexLevels = cfg.maxIndexLevels; + this.alignBlocks = cfg.alignBlocks; + this.indexObjects = cfg.indexObjects; + } + + /** + * Get desired output block size for references, in bytes. + * + * @return desired output block size for references, in bytes. + */ + public int getRefBlockSize() { + return refBlockSize; + } + + /** + * Set desired output block size for references, in bytes. + * + * @param szBytes + * desired output block size for references, in bytes. + */ + public void setRefBlockSize(int szBytes) { + if (szBytes > MAX_BLOCK_SIZE) { + throw new IllegalArgumentException(); + } + refBlockSize = Math.max(0, szBytes); + } + + /** + * Get desired output block size for log entries, in bytes. + * + * @return desired output block size for log entries, in bytes. If 0 the + * writer will default to {@code 2 * getRefBlockSize()}. + */ + public int getLogBlockSize() { + return logBlockSize; + } + + /** + * Set desired output block size for log entries, in bytes. + * + * @param szBytes + * desired output block size for log entries, in bytes. If 0 will + * default to {@code 2 * getRefBlockSize()}. + */ + public void setLogBlockSize(int szBytes) { + if (szBytes > MAX_BLOCK_SIZE) { + throw new IllegalArgumentException(); + } + logBlockSize = Math.max(0, szBytes); + } + + /** + * Get number of references between binary search markers. + * + * @return number of references between binary search markers. + */ + public int getRestartInterval() { + return restartInterval; + } + + /** + *

    Setter for the field restartInterval.

    + * + * @param interval + * number of references between binary search markers. If + * {@code interval} is 0 (default), the writer will select a + * default value based on the block size. + */ + public void setRestartInterval(int interval) { + restartInterval = Math.max(0, interval); + } + + /** + * Get maximum depth of the index; 0 for unlimited. + * + * @return maximum depth of the index; 0 for unlimited. + */ + public int getMaxIndexLevels() { + return maxIndexLevels; + } + + /** + * Set maximum number of levels to use in indexes. + * + * @param levels + * maximum number of levels to use in indexes. Lower levels of + * the index respect {@link #getRefBlockSize()}, and the highest + * level may exceed that if the number of levels is limited. + */ + public void setMaxIndexLevels(int levels) { + maxIndexLevels = Math.max(0, levels); + } + + /** + * Whether the writer should align blocks. + * + * @return {@code true} if the writer should align blocks. + */ + public boolean isAlignBlocks() { + return alignBlocks; + } + + /** + * Whether blocks are written aligned to multiples of + * {@link #getRefBlockSize()}. + * + * @param align + * if {@code true} blocks are written aligned to multiples of + * {@link #getRefBlockSize()}. May increase file size due to NUL + * padding bytes added between blocks. Default is {@code true}. + */ + public void setAlignBlocks(boolean align) { + alignBlocks = align; + } + + /** + * Whether the writer should index object to ref. + * + * @return {@code true} if the writer should index object to ref. + */ + public boolean isIndexObjects() { + return indexObjects; + } + + /** + * Whether the reftable may include additional storage to efficiently map + * from {@code ObjectId} to reference names. + * + * @param index + * if {@code true} the reftable may include additional storage to + * efficiently map from {@code ObjectId} to reference names. By + * default, {@code true}. + */ + public void setIndexObjects(boolean index) { + indexObjects = index; + } + + /** + * Update properties by setting fields from the configuration. + * + * If a property's corresponding variable is not defined in the supplied + * configuration, then it is left unmodified. + * + * @param rc + * configuration to read properties from. + */ + public void fromConfig(Config rc) { + refBlockSize = rc.getInt("reftable", "blockSize", refBlockSize); //$NON-NLS-1$ //$NON-NLS-2$ + logBlockSize = rc.getInt("reftable", "logBlockSize", logBlockSize); //$NON-NLS-1$ //$NON-NLS-2$ + restartInterval = rc.getInt("reftable", "restartInterval", restartInterval); //$NON-NLS-1$ //$NON-NLS-2$ + maxIndexLevels = rc.getInt("reftable", "indexLevels", maxIndexLevels); //$NON-NLS-1$ //$NON-NLS-2$ + alignBlocks = rc.getBoolean("reftable", "alignBlocks", alignBlocks); //$NON-NLS-1$ //$NON-NLS-2$ + indexObjects = rc.getBoolean("reftable", "indexObjects", indexObjects); //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +class ReftableConstants { + static final byte[] FILE_HEADER_MAGIC = { 'R', 'E', 'F', 'T' }; + static final byte VERSION_1 = (byte) 1; + + static final int FILE_HEADER_LEN = 24; + static final int FILE_FOOTER_LEN = 68; + + static final byte FILE_BLOCK_TYPE = 'R'; + static final byte REF_BLOCK_TYPE = 'r'; + static final byte OBJ_BLOCK_TYPE = 'o'; + static final byte LOG_BLOCK_TYPE = 'g'; + static final byte INDEX_BLOCK_TYPE = 'i'; + + static final int VALUE_NONE = 0x0; + static final int VALUE_1ID = 0x1; + static final int VALUE_2ID = 0x2; + static final int VALUE_SYMREF = 0x3; + static final int VALUE_TYPE_MASK = 0x7; + + static final int LOG_NONE = 0x0; + static final int LOG_DATA = 0x1; + + static final int MAX_BLOCK_SIZE = (1 << 24) - 1; + static final int MAX_RESTARTS = 65535; + + static boolean isFileHeaderMagic(byte[] buf, int o, int n) { + return (n - o) >= FILE_HEADER_MAGIC.length + && buf[o + 0] == FILE_HEADER_MAGIC[0] + && buf[o + 1] == FILE_HEADER_MAGIC[1] + && buf[o + 2] == FILE_HEADER_MAGIC[2] + && buf[o + 3] == FILE_HEADER_MAGIC[3]; + } + + static long reverseUpdateIndex(long time) { + return 0xffffffffffffffffL - time; + } + + private ReftableConstants() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; + +/** + * Abstract table of references. + */ +public abstract class Reftable implements AutoCloseable { + /** + * References to convert into a reftable + * + * @param refs + * references to convert into a reftable; may be empty. + * @return a reader for the supplied references. + */ + public static Reftable from(Collection refs) { + try { + ReftableConfig cfg = new ReftableConfig(); + cfg.setIndexObjects(false); + cfg.setAlignBlocks(false); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + new ReftableWriter() + .setConfig(cfg) + .begin(buf) + .sortAndWriteRefs(refs) + .finish(); + return new ReftableReader(BlockSource.from(buf.toByteArray())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** {@code true} if deletions should be included in results. */ + protected boolean includeDeletes; + + /** + * Whether deleted references will be returned. + * + * @param deletes + * if {@code true} deleted references will be returned. If + * {@code false} (default behavior), deleted references will be + * skipped, and not returned. + */ + public void setIncludeDeletes(boolean deletes) { + includeDeletes = deletes; + } + + /** + * Seek to the first reference, to iterate in order. + * + * @return cursor to iterate. + * @throws java.io.IOException + * if references cannot be read. + */ + public abstract RefCursor allRefs() throws IOException; + + /** + * Seek either to a reference, or a reference subtree. + *

    + * If {@code refName} ends with {@code "/"} the method will seek to the + * subtree of all references starting with {@code refName} as a prefix. If + * no references start with this prefix, an empty cursor is returned. + *

    + * Otherwise exactly {@code refName} will be looked for. If present, the + * returned cursor will iterate exactly one entry. If not found, an empty + * cursor is returned. + * + * @param refName + * reference name or subtree to find. + * @return cursor to iterate; empty cursor if no references match. + * @throws java.io.IOException + * if references cannot be read. + */ + public abstract RefCursor seekRef(String refName) throws IOException; + + /** + * Match references pointing to a specific object. + * + * @param id + * object to find. + * @return cursor to iterate; empty cursor if no references match. + * @throws java.io.IOException + * if references cannot be read. + */ + public abstract RefCursor byObjectId(AnyObjectId id) throws IOException; + + /** + * Seek reader to read log records. + * + * @return cursor to iterate; empty cursor if no logs are present. + * @throws java.io.IOException + * if logs cannot be read. + */ + public abstract LogCursor allLogs() throws IOException; + + /** + * Read a single reference's log. + * + * @param refName + * exact name of the reference whose log to read. + * @return cursor to iterate; empty cursor if no logs match. + * @throws java.io.IOException + * if logs cannot be read. + */ + public LogCursor seekLog(String refName) throws IOException { + return seekLog(refName, Long.MAX_VALUE); + } + + /** + * Seek to an update index in a reference's log. + * + * @param refName + * exact name of the reference whose log to read. + * @param updateIndex + * most recent index to return first in the log cursor. Log + * records at or before {@code updateIndex} will be returned. + * @return cursor to iterate; empty cursor if no logs match. + * @throws java.io.IOException + * if logs cannot be read. + */ + public abstract LogCursor seekLog(String refName, long updateIndex) + throws IOException; + + /** + * Lookup a reference, or null if not found. + * + * @param refName + * reference name to find. + * @return the reference, or {@code null} if not found. + * @throws java.io.IOException + * if references cannot be read. + */ + @Nullable + public Ref exactRef(String refName) throws IOException { + try (RefCursor rc = seekRef(refName)) { + return rc.next() ? rc.getRef() : null; + } + } + + /** + * Test if a reference or reference subtree exists. + *

    + * If {@code refName} ends with {@code "/"}, the method tests if any + * reference starts with {@code refName} as a prefix. + *

    + * Otherwise, the method checks if {@code refName} exists. + * + * @param refName + * reference name or subtree to find. + * @return {@code true} if the reference exists, or at least one reference + * exists in the subtree. + * @throws java.io.IOException + * if references cannot be read. + */ + public boolean hasRef(String refName) throws IOException { + try (RefCursor rc = seekRef(refName)) { + return rc.next(); + } + } + + /** + * Test if any reference directly refers to the object. + * + * @param id + * ObjectId to find. + * @return {@code true} if any reference exists directly referencing + * {@code id}, or a annotated tag that peels to {@code id}. + * @throws java.io.IOException + * if references cannot be read. + */ + public boolean hasId(AnyObjectId id) throws IOException { + try (RefCursor rc = byObjectId(id)) { + return rc.next(); + } + } + + /** + * Resolve a symbolic reference to populate its value. + * + * @param symref + * reference to resolve. + * @return resolved {@code symref}, or {@code null}. + * @throws java.io.IOException + * if references cannot be read. + */ + @Nullable + public Ref resolve(Ref symref) throws IOException { + return resolve(symref, 0); + } + + private Ref resolve(Ref ref, int depth) throws IOException { + if (!ref.isSymbolic()) { + return ref; + } + + Ref dst = ref.getTarget(); + if (MAX_SYMBOLIC_REF_DEPTH <= depth) { + return null; // claim it doesn't exist + } + + dst = exactRef(dst.getName()); + if (dst == null) { + return ref; + } + + dst = resolve(dst, depth + 1); + if (dst == null) { + return null; // claim it doesn't exist + } + return new SymbolicRef(ref.getName(), dst); + } + + /** {@inheritDoc} */ + @Override + public abstract void close() throws IOException; +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.io.CountingOutputStream; + +/** + * Wrapper to assist formatting a reftable to an {@link OutputStream}. + *

    + * Internally buffers at block size boundaries, flushing only complete blocks to + * the {@code OutputStream}. + */ +class ReftableOutputStream extends OutputStream { + private final byte[] tmp = new byte[10]; + private final CountingOutputStream out; + private final boolean alignBlocks; + + private Deflater deflater; + private DeflaterOutputStream compressor; + + private int blockType; + private int blockSize; + private int blockStart; + private byte[] blockBuf; + private int cur; + private long paddingUsed; + + ReftableOutputStream(OutputStream os, int bs, boolean align) { + blockSize = bs; + blockBuf = new byte[bs]; + alignBlocks = align; + out = new CountingOutputStream(os); + } + + void setBlockSize(int bs) { + blockSize = bs; + } + + /** {@inheritDoc} */ + @Override + public void write(int b) { + ensureBytesAvailableInBlockBuf(1); + blockBuf[cur++] = (byte) b; + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] b, int off, int cnt) { + ensureBytesAvailableInBlockBuf(cnt); + System.arraycopy(b, off, blockBuf, cur, cnt); + cur += cnt; + } + + int bytesWrittenInBlock() { + return cur; + } + + int bytesAvailableInBlock() { + return blockSize - cur; + } + + long paddingUsed() { + return paddingUsed; + } + + /** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */ + long size() { + return out.getCount(); + } + + static int computeVarintSize(long val) { + int n = 1; + for (; (val >>>= 7) != 0; n++) { + val--; + } + return n; + } + + void writeVarint(long val) { + int n = tmp.length; + tmp[--n] = (byte) (val & 0x7f); + while ((val >>>= 7) != 0) { + tmp[--n] = (byte) (0x80 | (--val & 0x7F)); + } + write(tmp, n, tmp.length - n); + } + + void writeInt16(int val) { + ensureBytesAvailableInBlockBuf(2); + NB.encodeInt16(blockBuf, cur, val); + cur += 2; + } + + void writeInt24(int val) { + ensureBytesAvailableInBlockBuf(3); + NB.encodeInt24(blockBuf, cur, val); + cur += 3; + } + + void writeId(ObjectId id) { + ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH); + id.copyRawTo(blockBuf, cur); + cur += OBJECT_ID_LENGTH; + } + + void writeVarintString(String s) { + writeVarintString(s.getBytes(UTF_8)); + } + + void writeVarintString(byte[] msg) { + writeVarint(msg.length); + write(msg, 0, msg.length); + } + + private void ensureBytesAvailableInBlockBuf(int cnt) { + if (cur + cnt > blockBuf.length) { + int n = Math.max(cur + cnt, blockBuf.length * 2); + blockBuf = Arrays.copyOf(blockBuf, n); + } + } + + void flushFileHeader() throws IOException { + if (cur == FILE_HEADER_LEN && out.getCount() == 0) { + out.write(blockBuf, 0, cur); + cur = 0; + } + } + + void beginBlock(byte type) { + blockType = type; + blockStart = cur; + cur += 4; // reserve space for 4-byte block header. + } + + void flushBlock() throws IOException { + if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) { + throw new IOException(JGitText.get().overflowedReftableBlock); + } + NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur); + + if (blockType == LOG_BLOCK_TYPE) { + // Log blocks are deflated after the block header. + out.write(blockBuf, 0, 4); + if (deflater != null) { + deflater.reset(); + } else { + deflater = new Deflater(Deflater.BEST_COMPRESSION); + compressor = new DeflaterOutputStream(out, deflater); + } + compressor.write(blockBuf, 4, cur - 4); + compressor.finish(); + } else { + // Other blocks are uncompressed. + out.write(blockBuf, 0, cur); + } + + cur = 0; + blockType = 0; + blockStart = 0; + } + + void padBetweenBlocksToNextBlock() throws IOException { + if (alignBlocks) { + long m = size() % blockSize; + if (m > 0) { + int pad = blockSize - (int) m; + ensureBytesAvailableInBlockBuf(pad); + Arrays.fill(blockBuf, 0, pad, (byte) 0); + out.write(blockBuf, 0, pad); + paddingUsed += pad; + } + } + } + + int estimatePadBetweenBlocks(int currentBlockSize) { + if (alignBlocks) { + long m = (size() + currentBlockSize) % blockSize; + return m > 0 ? blockSize - (int) m : 0; + } + return 0; + } + + void finishFile() throws IOException { + // File footer doesn't need patching for the block start. + // Just flush what has been buffered. + out.write(blockBuf, 0, cur); + cur = 0; + + if (deflater != null) { + deflater.end(); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.internal.storage.reftable.BlockReader.decodeBlockLen; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.isFileHeaderMagic; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.zip.CRC32; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.LongMap; +import org.eclipse.jgit.util.NB; + +/** + * Reads a reftable formatted file. + *

    + * {@code ReftableReader} is not thread-safe. Concurrent readers need their own + * instance to read from the same file. + */ +public class ReftableReader extends Reftable { + private final BlockSource src; + + private int blockSize = -1; + private long minUpdateIndex; + private long maxUpdateIndex; + + private long refEnd; + private long objPosition; + private long objEnd; + private long logPosition; + private long logEnd; + private int objIdLen; + + private long refIndexPosition = -1; + private long objIndexPosition = -1; + private long logIndexPosition = -1; + + private BlockReader refIndex; + private BlockReader objIndex; + private BlockReader logIndex; + private LongMap indexCache; + + /** + * Initialize a new reftable reader. + * + * @param src + * the file content to read. + */ + public ReftableReader(BlockSource src) { + this.src = src; + } + + /** + * Get the block size in bytes chosen for this file by the writer. + * + * @return the block size in bytes chosen for this file by the writer. Most + * reads from the + * {@link org.eclipse.jgit.internal.storage.io.BlockSource} will be + * aligned to the block size. + * @throws java.io.IOException + * file cannot be read. + */ + public int blockSize() throws IOException { + if (blockSize == -1) { + readFileHeader(); + } + return blockSize; + } + + /** + * Get the minimum update index for log entries that appear in this + * reftable. + * + * @return the minimum update index for log entries that appear in this + * reftable. This should be 1 higher than the prior reftable's + * {@code maxUpdateIndex} if this table is used in a stack. + * @throws java.io.IOException + * file cannot be read. + */ + public long minUpdateIndex() throws IOException { + if (blockSize == -1) { + readFileHeader(); + } + return minUpdateIndex; + } + + /** + * Get the maximum update index for log entries that appear in this + * reftable. + * + * @return the maximum update index for log entries that appear in this + * reftable. This should be 1 higher than the prior reftable's + * {@code maxUpdateIndex} if this table is used in a stack. + * @throws java.io.IOException + * file cannot be read. + */ + public long maxUpdateIndex() throws IOException { + if (blockSize == -1) { + readFileHeader(); + } + return maxUpdateIndex; + } + + /** {@inheritDoc} */ + @Override + public RefCursor allRefs() throws IOException { + if (blockSize == -1) { + readFileHeader(); + } + + long end = refEnd > 0 ? refEnd : (src.size() - FILE_FOOTER_LEN); + src.adviseSequentialRead(0, end); + + RefCursorImpl i = new RefCursorImpl(end, null, false); + i.block = readBlock(0, end); + return i; + } + + /** {@inheritDoc} */ + @Override + public RefCursor seekRef(String refName) throws IOException { + initRefIndex(); + + byte[] key = refName.getBytes(UTF_8); + boolean prefix = key[key.length - 1] == '/'; + + RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix); + i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd); + return i; + } + + /** {@inheritDoc} */ + @Override + public RefCursor byObjectId(AnyObjectId id) throws IOException { + initObjIndex(); + ObjCursorImpl i = new ObjCursorImpl(refEnd, id); + if (objIndex != null) { + i.initSeek(); + } else { + i.initScan(); + } + return i; + } + + /** {@inheritDoc} */ + @Override + public LogCursor allLogs() throws IOException { + initLogIndex(); + if (logPosition > 0) { + src.adviseSequentialRead(logPosition, logEnd); + LogCursorImpl i = new LogCursorImpl(logEnd, null); + i.block = readBlock(logPosition, logEnd); + return i; + } + return new EmptyLogCursor(); + } + + /** {@inheritDoc} */ + @Override + public LogCursor seekLog(String refName, long updateIndex) + throws IOException { + initLogIndex(); + if (logPosition > 0) { + byte[] key = LogEntry.key(refName, updateIndex); + byte[] match = refName.getBytes(UTF_8); + LogCursorImpl i = new LogCursorImpl(logEnd, match); + i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd); + return i; + } + return new EmptyLogCursor(); + } + + private BlockReader seek(byte blockType, byte[] key, BlockReader idx, + long startPos, long endPos) throws IOException { + if (idx != null) { + // Walk through a possibly multi-level index to a leaf block. + BlockReader block = idx; + do { + if (block.seekKey(key) > 0) { + return null; + } + long pos = block.readPositionFromIndex(); + block = readBlock(pos, endPos); + } while (block.type() == INDEX_BLOCK_TYPE); + block.seekKey(key); + return block; + } + return binarySearch(blockType, key, startPos, endPos); + } + + private BlockReader binarySearch(byte blockType, byte[] key, + long startPos, long endPos) throws IOException { + if (blockSize == 0) { + BlockReader b = readBlock(startPos, endPos); + if (blockType != b.type()) { + return null; + } + b.seekKey(key); + return b; + } + + int low = (int) (startPos / blockSize); + int end = blocksIn(startPos, endPos); + BlockReader block = null; + do { + int mid = (low + end) >>> 1; + block = readBlock(((long) mid) * blockSize, endPos); + if (blockType != block.type()) { + return null; + } + int cmp = block.seekKey(key); + if (cmp < 0) { + end = mid; + } else if (cmp == 0) { + break; + } else /* if (cmp > 0) */ { + low = mid + 1; + } + } while (low < end); + return block; + } + + private void readFileHeader() throws IOException { + readHeaderOrFooter(0, FILE_HEADER_LEN); + } + + private void readFileFooter() throws IOException { + int ftrLen = FILE_FOOTER_LEN; + byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen); + + CRC32 crc = new CRC32(); + crc.update(ftr, 0, ftrLen - 4); + if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) { + throw new IOException(JGitText.get().invalidReftableCRC); + } + + refIndexPosition = NB.decodeInt64(ftr, 24); + long p = NB.decodeInt64(ftr, 32); + objPosition = p >>> 5; + objIdLen = (int) (p & 0x1f); + objIndexPosition = NB.decodeInt64(ftr, 40); + logPosition = NB.decodeInt64(ftr, 48); + logIndexPosition = NB.decodeInt64(ftr, 56); + + if (refIndexPosition > 0) { + refEnd = refIndexPosition; + } else if (objPosition > 0) { + refEnd = objPosition; + } else if (logPosition > 0) { + refEnd = logPosition; + } else { + refEnd = src.size() - ftrLen; + } + + if (objPosition > 0) { + if (objIndexPosition > 0) { + objEnd = objIndexPosition; + } else if (logPosition > 0) { + objEnd = logPosition; + } else { + objEnd = src.size() - ftrLen; + } + } + + if (logPosition > 0) { + if (logIndexPosition > 0) { + logEnd = logIndexPosition; + } else { + logEnd = src.size() - ftrLen; + } + } + } + + private byte[] readHeaderOrFooter(long pos, int len) throws IOException { + ByteBuffer buf = src.read(pos, len); + if (buf.position() != len) { + throw new IOException(JGitText.get().shortReadOfBlock); + } + + byte[] tmp = new byte[len]; + buf.flip(); + buf.get(tmp); + if (!isFileHeaderMagic(tmp, 0, len)) { + throw new IOException(JGitText.get().invalidReftableFile); + } + + int v = NB.decodeInt32(tmp, 4); + int version = v >>> 24; + if (VERSION_1 != version) { + throw new IOException(MessageFormat.format( + JGitText.get().unsupportedReftableVersion, + Integer.valueOf(version))); + } + if (blockSize == -1) { + blockSize = v & 0xffffff; + } + minUpdateIndex = NB.decodeInt64(tmp, 8); + maxUpdateIndex = NB.decodeInt64(tmp, 16); + return tmp; + } + + private void initRefIndex() throws IOException { + if (refIndexPosition < 0) { + readFileFooter(); + } + if (refIndex == null && refIndexPosition > 0) { + refIndex = readIndex(refIndexPosition); + } + } + + private void initObjIndex() throws IOException { + if (objIndexPosition < 0) { + readFileFooter(); + } + if (objIndex == null && objIndexPosition > 0) { + objIndex = readIndex(objIndexPosition); + } + } + + private void initLogIndex() throws IOException { + if (logIndexPosition < 0) { + readFileFooter(); + } + if (logIndex == null && logIndexPosition > 0) { + logIndex = readIndex(logIndexPosition); + } + } + + private BlockReader readIndex(long pos) throws IOException { + int sz = readBlockLen(pos); + BlockReader i = new BlockReader(); + i.readBlock(src, pos, sz); + i.verifyIndex(); + return i; + } + + private int readBlockLen(long pos) throws IOException { + int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4; + ByteBuffer tmp = src.read(pos, sz); + if (tmp.position() < sz) { + throw new IOException(JGitText.get().invalidReftableFile); + } + byte[] buf; + if (tmp.hasArray() && tmp.arrayOffset() == 0) { + buf = tmp.array(); + } else { + buf = new byte[sz]; + tmp.flip(); + tmp.get(buf); + } + if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) { + return FILE_HEADER_LEN; + } + int p = pos == 0 ? FILE_HEADER_LEN : 0; + return decodeBlockLen(NB.decodeInt32(buf, p)); + } + + private BlockReader readBlock(long pos, long end) throws IOException { + if (indexCache != null) { + BlockReader b = indexCache.get(pos); + if (b != null) { + return b; + } + } + + int sz = blockSize; + if (sz == 0) { + sz = readBlockLen(pos); + } else if (pos + sz > end) { + sz = (int) (end - pos); // last block may omit padding. + } + + BlockReader b = new BlockReader(); + b.readBlock(src, pos, sz); + if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) { + if (indexCache == null) { + indexCache = new LongMap<>(); + } + indexCache.put(pos, b); + } + return b; + } + + private int blocksIn(long pos, long end) { + int blocks = (int) ((end - pos) / blockSize); + return end % blockSize == 0 ? blocks : (blocks + 1); + } + + /** + * Get size of the reftable, in bytes. + * + * @return size of the reftable, in bytes. + * @throws java.io.IOException + * size cannot be obtained. + */ + public long size() throws IOException { + return src.size(); + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + src.close(); + } + + private class RefCursorImpl extends RefCursor { + private final long scanEnd; + private final byte[] match; + private final boolean prefix; + + private Ref ref; + private long updateIndex; + BlockReader block; + + RefCursorImpl(long scanEnd, byte[] match, boolean prefix) { + this.scanEnd = scanEnd; + this.match = match; + this.prefix = prefix; + } + + @Override + public boolean next() throws IOException { + for (;;) { + if (block == null || block.type() != REF_BLOCK_TYPE) { + return false; + } else if (!block.next()) { + long pos = block.endPosition(); + if (pos >= scanEnd) { + return false; + } + block = readBlock(pos, scanEnd); + continue; + } + + block.parseKey(); + if (match != null && !block.match(match, prefix)) { + block.skipValue(); + return false; + } + + updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); + ref = block.readRef(); + if (!includeDeletes && wasDeleted()) { + continue; + } + return true; + } + } + + @Override + public Ref getRef() { + return ref; + } + + @Override + public long getUpdateIndex() { + return updateIndex; + } + + @Override + public void close() { + // Do nothing. + } + } + + private class LogCursorImpl extends LogCursor { + private final long scanEnd; + private final byte[] match; + + private String refName; + private long updateIndex; + private ReflogEntry entry; + BlockReader block; + + LogCursorImpl(long scanEnd, byte[] match) { + this.scanEnd = scanEnd; + this.match = match; + } + + @Override + public boolean next() throws IOException { + for (;;) { + if (block == null || block.type() != LOG_BLOCK_TYPE) { + return false; + } else if (!block.next()) { + long pos = block.endPosition(); + if (pos >= scanEnd) { + return false; + } + block = readBlock(pos, scanEnd); + continue; + } + + block.parseKey(); + if (match != null && !block.match(match, false)) { + block.skipValue(); + return false; + } + + refName = block.name(); + updateIndex = block.readLogUpdateIndex(); + entry = block.readLogEntry(); + if (entry == null && !includeDeletes) { + continue; + } + return true; + } + } + + @Override + public String getRefName() { + return refName; + } + + @Override + public long getUpdateIndex() { + return updateIndex; + } + + @Override + public ReflogEntry getReflogEntry() { + return entry; + } + + @Override + public void close() { + // Do nothing. + } + } + + static final LongList EMPTY_LONG_LIST = new LongList(0); + + private class ObjCursorImpl extends RefCursor { + private final long scanEnd; + private final ObjectId match; + + private Ref ref; + private long updateIndex; + private int listIdx; + + private LongList blockPos; + private BlockReader block; + + ObjCursorImpl(long scanEnd, AnyObjectId id) { + this.scanEnd = scanEnd; + this.match = id.copy(); + } + + void initSeek() throws IOException { + byte[] rawId = new byte[OBJECT_ID_LENGTH]; + match.copyRawTo(rawId, 0); + byte[] key = Arrays.copyOf(rawId, objIdLen); + + BlockReader b = objIndex; + do { + if (b.seekKey(key) > 0) { + blockPos = EMPTY_LONG_LIST; + return; + } + long pos = b.readPositionFromIndex(); + b = readBlock(pos, objEnd); + } while (b.type() == INDEX_BLOCK_TYPE); + b.seekKey(key); + while (b.next()) { + b.parseKey(); + if (b.match(key, false)) { + blockPos = b.readBlockPositionList(); + if (blockPos == null) { + initScan(); + return; + } + break; + } + b.skipValue(); + } + if (blockPos == null) { + blockPos = EMPTY_LONG_LIST; + } + if (blockPos.size() > 0) { + long pos = blockPos.get(listIdx++); + block = readBlock(pos, scanEnd); + } + } + + void initScan() throws IOException { + block = readBlock(0, scanEnd); + } + + @Override + public boolean next() throws IOException { + for (;;) { + if (block == null || block.type() != REF_BLOCK_TYPE) { + return false; + } else if (!block.next()) { + long pos; + if (blockPos != null) { + if (listIdx >= blockPos.size()) { + return false; + } + pos = blockPos.get(listIdx++); + } else { + pos = block.endPosition(); + } + if (pos >= scanEnd) { + return false; + } + block = readBlock(pos, scanEnd); + continue; + } + + block.parseKey(); + updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); + ref = block.readRef(); + ObjectId id = ref.getObjectId(); + if (id != null && match.equals(id) + && (includeDeletes || !wasDeleted())) { + return true; + } + } + } + + @Override + public Ref getRef() { + return ref; + } + + @Override + public long getUpdateIndex() { + return updateIndex; + } + + @Override + public void close() { + // Do nothing. + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static java.lang.Math.log; +import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; +import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.zip.CRC32; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry; +import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSubclassMap; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.NB; + +/** + * Writes a reftable formatted file. + *

    + * A reftable can be written in a streaming fashion, provided the caller sorts + * all references. A + * {@link org.eclipse.jgit.internal.storage.reftable.ReftableWriter} is + * single-use, and not thread-safe. + */ +public class ReftableWriter { + private ReftableConfig config; + private int refBlockSize; + private int logBlockSize; + private int restartInterval; + private int maxIndexLevels; + private boolean alignBlocks; + private boolean indexObjects; + + private long minUpdateIndex; + private long maxUpdateIndex; + + private ReftableOutputStream out; + private ObjectIdSubclassMap obj2ref; + + private BlockWriter cur; + private Section refs; + private Section objs; + private Section logs; + private int objIdLen; + private Stats stats; + + /** + * Initialize a writer with a default configuration. + */ + public ReftableWriter() { + this(new ReftableConfig()); + } + + /** + * Initialize a writer with a specific configuration. + * + * @param cfg + * configuration for the writer. + */ + public ReftableWriter(ReftableConfig cfg) { + config = cfg; + } + + /** + * Set configuration for the writer. + * + * @param cfg + * configuration for the writer. + * @return {@code this} + */ + public ReftableWriter setConfig(ReftableConfig cfg) { + this.config = cfg != null ? cfg : new ReftableConfig(); + return this; + } + + /** + * Set the minimum update index for log entries that appear in this + * reftable. + * + * @param min + * the minimum update index for log entries that appear in this + * reftable. This should be 1 higher than the prior reftable's + * {@code maxUpdateIndex} if this table will be used in a stack. + * @return {@code this} + */ + public ReftableWriter setMinUpdateIndex(long min) { + minUpdateIndex = min; + return this; + } + + /** + * Set the maximum update index for log entries that appear in this + * reftable. + * + * @param max + * the maximum update index for log entries that appear in this + * reftable. This should be at least 1 higher than the prior + * reftable's {@code maxUpdateIndex} if this table will be used + * in a stack. + * @return {@code this} + */ + public ReftableWriter setMaxUpdateIndex(long max) { + maxUpdateIndex = max; + return this; + } + + /** + * Begin writing the reftable. + * + * @param os + * stream to write the table to. Caller is responsible for + * closing the stream after invoking {@link #finish()}. + * @return {@code this} + * @throws java.io.IOException + * if reftable header cannot be written. + */ + public ReftableWriter begin(OutputStream os) throws IOException { + refBlockSize = config.getRefBlockSize(); + logBlockSize = config.getLogBlockSize(); + restartInterval = config.getRestartInterval(); + maxIndexLevels = config.getMaxIndexLevels(); + alignBlocks = config.isAlignBlocks(); + indexObjects = config.isIndexObjects(); + + if (refBlockSize <= 0) { + refBlockSize = 4 << 10; + } else if (refBlockSize > MAX_BLOCK_SIZE) { + throw new IllegalArgumentException(); + } + if (logBlockSize <= 0) { + logBlockSize = 2 * refBlockSize; + } + if (restartInterval <= 0) { + restartInterval = refBlockSize < (60 << 10) ? 16 : 64; + } + + out = new ReftableOutputStream(os, refBlockSize, alignBlocks); + refs = new Section(REF_BLOCK_TYPE); + if (indexObjects) { + obj2ref = new ObjectIdSubclassMap<>(); + } + writeFileHeader(); + return this; + } + + /** + * Sort a collection of references and write them to the reftable. + * + * @param refsToPack + * references to sort and write. + * @return {@code this} + * @throws java.io.IOException + * if reftable cannot be written. + */ + public ReftableWriter sortAndWriteRefs(Collection refsToPack) + throws IOException { + Iterator itr = refsToPack.stream() + .map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex)) + .sorted(Entry::compare) + .iterator(); + while (itr.hasNext()) { + RefEntry entry = itr.next(); + long blockPos = refs.write(entry); + indexRef(entry.ref, blockPos); + } + return this; + } + + /** + * Write one reference to the reftable. + *

    + * References must be passed in sorted order. + * + * @param ref + * the reference to store. + * @throws java.io.IOException + * if reftable cannot be written. + */ + public void writeRef(Ref ref) throws IOException { + writeRef(ref, maxUpdateIndex); + } + + /** + * Write one reference to the reftable. + *

    + * References must be passed in sorted order. + * + * @param ref + * the reference to store. + * @param updateIndex + * the updateIndex that modified this reference. Must be + * {@code >= minUpdateIndex} for this file. + * @throws java.io.IOException + * if reftable cannot be written. + */ + public void writeRef(Ref ref, long updateIndex) throws IOException { + if (updateIndex < minUpdateIndex) { + throw new IllegalArgumentException(); + } + long d = updateIndex - minUpdateIndex; + long blockPos = refs.write(new RefEntry(ref, d)); + indexRef(ref, blockPos); + } + + private void indexRef(Ref ref, long blockPos) { + if (indexObjects && !ref.isSymbolic()) { + indexId(ref.getObjectId(), blockPos); + indexId(ref.getPeeledObjectId(), blockPos); + } + } + + private void indexId(ObjectId id, long blockPos) { + if (id != null) { + RefList l = obj2ref.get(id); + if (l == null) { + l = new RefList(id); + obj2ref.add(l); + } + l.addBlock(blockPos); + } + } + + /** + * Write one reflog entry to the reftable. + *

    + * Reflog entries must be written in reference name and descending + * {@code updateIndex} (highest first) order. + * + * @param ref + * name of the reference. + * @param updateIndex + * identifier of the transaction that created the log record. The + * {@code updateIndex} must be unique within the scope of + * {@code ref}, and must be within the bounds defined by + * {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}. + * @param who + * committer of the reflog entry. + * @param oldId + * prior id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()} + * for creations. + * @param newId + * new id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()} + * for deletions. + * @param message + * optional message (may be null). + * @throws java.io.IOException + * if reftable cannot be written. + */ + public void writeLog(String ref, long updateIndex, PersonIdent who, + ObjectId oldId, ObjectId newId, @Nullable String message) + throws IOException { + String msg = message != null ? message : ""; //$NON-NLS-1$ + beginLog(); + logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg)); + } + + /** + * Record deletion of one reflog entry in this reftable. + * + *

    + * The deletion can shadow an entry stored in a lower table in the stack. + * This is useful for {@code refs/stash} and dropping an entry from its + * reflog. + *

    + * Deletion must be properly interleaved in sorted updateIndex order with + * any other logs written by + * {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}. + * + * @param ref + * the ref to delete (hide) a reflog entry from. + * @param updateIndex + * the update index that must be hidden. + * @throws java.io.IOException + * if reftable cannot be written. + */ + public void deleteLog(String ref, long updateIndex) throws IOException { + beginLog(); + logs.write(new DeleteLogEntry(ref, updateIndex)); + } + + private void beginLog() throws IOException { + if (logs == null) { + finishRefAndObjSections(); // close prior ref blocks and their index, if present. + out.flushFileHeader(); + out.setBlockSize(logBlockSize); + logs = new Section(LOG_BLOCK_TYPE); + } + } + + /** + * Get an estimate of the current size in bytes of the reftable + * + * @return an estimate of the current size in bytes of the reftable, if it + * was finished right now. Estimate is only accurate if + * {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setIndexObjects(boolean)} + * is {@code false} and + * {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setMaxIndexLevels(int)} + * is {@code 1}. + */ + public long estimateTotalBytes() { + long bytes = out.size(); + if (bytes == 0) { + bytes += FILE_HEADER_LEN; + } + if (cur != null) { + long curBlockPos = out.size(); + int sz = cur.currentSize(); + bytes += sz; + + IndexBuilder idx = null; + if (cur.blockType() == REF_BLOCK_TYPE) { + idx = refs.idx; + } else if (cur.blockType() == LOG_BLOCK_TYPE) { + idx = logs.idx; + } + if (idx != null && shouldHaveIndex(idx)) { + if (idx == refs.idx) { + bytes += out.estimatePadBetweenBlocks(sz); + } + bytes += idx.estimateBytes(curBlockPos); + } + } + bytes += FILE_FOOTER_LEN; + return bytes; + } + + /** + * Finish writing the reftable by writing its trailer. + * + * @return {@code this} + * @throws java.io.IOException + * if reftable cannot be written. + */ + public ReftableWriter finish() throws IOException { + finishRefAndObjSections(); + finishLogSection(); + writeFileFooter(); + out.finishFile(); + + stats = new Stats(this, out); + out = null; + obj2ref = null; + cur = null; + refs = null; + objs = null; + logs = null; + return this; + } + + private void finishRefAndObjSections() throws IOException { + if (cur != null && cur.blockType() == REF_BLOCK_TYPE) { + refs.finishSectionMaybeWriteIndex(); + if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) { + writeObjBlocks(); + } + obj2ref = null; + } + } + + private void writeObjBlocks() throws IOException { + List sorted = sortById(obj2ref); + obj2ref = null; + objIdLen = shortestUniqueAbbreviation(sorted); + + out.padBetweenBlocksToNextBlock(); + objs = new Section(OBJ_BLOCK_TYPE); + objs.entryCnt = sorted.size(); + for (RefList l : sorted) { + objs.write(new ObjEntry(objIdLen, l, l.blockPos)); + } + objs.finishSectionMaybeWriteIndex(); + } + + private void finishLogSection() throws IOException { + if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) { + logs.finishSectionMaybeWriteIndex(); + } + } + + private boolean shouldHaveIndex(IndexBuilder idx) { + int threshold; + if (idx == refs.idx && alignBlocks) { + threshold = 4; + } else { + threshold = 1; + } + return idx.entries.size() + (cur != null ? 1 : 0) > threshold; + } + + private void writeFileHeader() { + byte[] hdr = new byte[FILE_HEADER_LEN]; + encodeHeader(hdr); + out.write(hdr, 0, FILE_HEADER_LEN); + } + + private void encodeHeader(byte[] hdr) { + System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4); + int bs = alignBlocks ? refBlockSize : 0; + NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs); + NB.encodeInt64(hdr, 8, minUpdateIndex); + NB.encodeInt64(hdr, 16, maxUpdateIndex); + } + + private void writeFileFooter() { + int ftrLen = FILE_FOOTER_LEN; + byte[] ftr = new byte[ftrLen]; + encodeHeader(ftr); + + NB.encodeInt64(ftr, 24, indexPosition(refs)); + NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen); + NB.encodeInt64(ftr, 40, indexPosition(objs)); + NB.encodeInt64(ftr, 48, firstBlockPosition(logs)); + NB.encodeInt64(ftr, 56, indexPosition(logs)); + + CRC32 crc = new CRC32(); + crc.update(ftr, 0, ftrLen - 4); + NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue()); + + out.write(ftr, 0, ftrLen); + } + + private static long firstBlockPosition(@Nullable Section s) { + return s != null ? s.firstBlockPosition : 0; + } + + private static long indexPosition(@Nullable Section s) { + return s != null && s.idx != null ? s.idx.rootPosition : 0; + } + + /** + * Get statistics of the last written reftable. + * + * @return statistics of the last written reftable. + */ + public Stats getStats() { + return stats; + } + + /** Statistics about a written reftable. */ + public static class Stats { + private final int refBlockSize; + private final int logBlockSize; + private final int restartInterval; + + private final long minUpdateIndex; + private final long maxUpdateIndex; + + private final long refCnt; + private final long objCnt; + private final int objIdLen; + private final long logCnt; + private final long refBytes; + private final long objBytes; + private final long logBytes; + private final long paddingUsed; + private final long totalBytes; + + private final int refIndexSize; + private final int refIndexLevels; + private final int objIndexSize; + private final int objIndexLevels; + + Stats(ReftableWriter w, ReftableOutputStream o) { + refBlockSize = w.refBlockSize; + logBlockSize = w.logBlockSize; + restartInterval = w.restartInterval; + + minUpdateIndex = w.minUpdateIndex; + maxUpdateIndex = w.maxUpdateIndex; + paddingUsed = o.paddingUsed(); + totalBytes = o.size(); + + refCnt = w.refs.entryCnt; + refBytes = w.refs.bytes; + + objCnt = w.objs != null ? w.objs.entryCnt : 0; + objBytes = w.objs != null ? w.objs.bytes : 0; + objIdLen = w.objIdLen; + + logCnt = w.logs != null ? w.logs.entryCnt : 0; + logBytes = w.logs != null ? w.logs.bytes : 0; + + IndexBuilder refIdx = w.refs.idx; + refIndexSize = refIdx.bytes; + refIndexLevels = refIdx.levels; + + IndexBuilder objIdx = w.objs != null ? w.objs.idx : null; + objIndexSize = objIdx != null ? objIdx.bytes : 0; + objIndexLevels = objIdx != null ? objIdx.levels : 0; + } + + /** @return number of bytes in a ref block. */ + public int refBlockSize() { + return refBlockSize; + } + + /** @return number of bytes to compress into a log block. */ + public int logBlockSize() { + return logBlockSize; + } + + /** @return number of references between binary search markers. */ + public int restartInterval() { + return restartInterval; + } + + /** @return smallest update index contained in this reftable. */ + public long minUpdateIndex() { + return minUpdateIndex; + } + + /** @return largest update index contained in this reftable. */ + public long maxUpdateIndex() { + return maxUpdateIndex; + } + + /** @return total number of references in the reftable. */ + public long refCount() { + return refCnt; + } + + /** @return number of unique objects in the reftable. */ + public long objCount() { + return objCnt; + } + + /** @return total number of log records in the reftable. */ + public long logCount() { + return logCnt; + } + + /** @return number of bytes for references, including ref index. */ + public long refBytes() { + return refBytes; + } + + /** @return number of bytes for objects, including object index. */ + public long objBytes() { + return objBytes; + } + + /** @return number of bytes for log, including log index. */ + public long logBytes() { + return logBytes; + } + + /** @return total number of bytes in the reftable. */ + public long totalBytes() { + return totalBytes; + } + + /** @return bytes of padding used to maintain block alignment. */ + public long paddingBytes() { + return paddingUsed; + } + + /** @return number of bytes in the ref index; 0 if no index was used. */ + public int refIndexSize() { + return refIndexSize; + } + + /** @return number of levels in the ref index. */ + public int refIndexLevels() { + return refIndexLevels; + } + + /** @return number of bytes in the object index; 0 if no index. */ + public int objIndexSize() { + return objIndexSize; + } + + /** @return number of levels in the object index. */ + public int objIndexLevels() { + return objIndexLevels; + } + + /** + * @return number of bytes required to uniquely identify all objects in + * the reftable. Unique abbreviations in hex would be + * {@code 2 * objIdLength()}. + */ + public int objIdLength() { + return objIdLen; + } + } + + private static List sortById(ObjectIdSubclassMap m) { + List s = new ArrayList<>(m.size()); + for (RefList l : m) { + s.add(l); + } + Collections.sort(s); + return s; + } + + private static int shortestUniqueAbbreviation(List in) { + // Estimate minimum number of bytes necessary for unique abbreviations. + int bytes = Math.max(2, (int) (log(in.size()) / log(8))); + Set tmp = new HashSet<>((int) (in.size() * 0.75f)); + retry: for (;;) { + int hexLen = bytes * 2; + for (ObjectId id : in) { + AbbreviatedObjectId a = id.abbreviate(hexLen); + if (!tmp.add(a)) { + if (++bytes >= OBJECT_ID_LENGTH) { + return OBJECT_ID_LENGTH; + } + tmp.clear(); + continue retry; + } + } + return bytes; + } + } + + private static class RefList extends ObjectIdOwnerMap.Entry { + final LongList blockPos = new LongList(2); + + RefList(AnyObjectId id) { + super(id); + } + + void addBlock(long pos) { + if (!blockPos.contains(pos)) { + blockPos.add(pos); + } + } + } + + private class Section { + final IndexBuilder idx; + final long firstBlockPosition; + + long entryCnt; + long bytes; + + Section(byte keyType) { + idx = new IndexBuilder(keyType); + firstBlockPosition = out.size(); + } + + long write(BlockWriter.Entry entry) throws IOException { + if (cur == null) { + beginBlock(entry); + } else if (!cur.tryAdd(entry)) { + flushCurBlock(); + if (cur.padBetweenBlocks()) { + out.padBetweenBlocksToNextBlock(); + } + beginBlock(entry); + } + entryCnt++; + return out.size(); + } + + private void beginBlock(BlockWriter.Entry entry) + throws BlockSizeTooSmallException { + byte blockType = entry.blockType(); + int bs = out.bytesAvailableInBlock(); + cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval); + cur.mustAdd(entry); + } + + void flushCurBlock() throws IOException { + idx.entries.add(new IndexEntry(cur.lastKey(), out.size())); + cur.writeTo(out); + } + + void finishSectionMaybeWriteIndex() throws IOException { + flushCurBlock(); + cur = null; + if (shouldHaveIndex(idx)) { + idx.writeIndex(); + } + bytes = out.size() - firstBlockPosition; + } + } + + private class IndexBuilder { + final byte keyType; + List entries = new ArrayList<>(); + long rootPosition; + int bytes; + int levels; + + IndexBuilder(byte kt) { + keyType = kt; + } + + int estimateBytes(long curBlockPos) { + BlockWriter b = new BlockWriter( + INDEX_BLOCK_TYPE, keyType, + MAX_BLOCK_SIZE, + Math.max(restartInterval, entries.size() / MAX_RESTARTS)); + try { + for (Entry e : entries) { + b.mustAdd(e); + } + if (cur != null) { + b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos)); + } + } catch (BlockSizeTooSmallException e) { + return b.currentSize(); + } + return b.currentSize(); + } + + void writeIndex() throws IOException { + if (padBetweenBlocks(keyType)) { + out.padBetweenBlocksToNextBlock(); + } + long startPos = out.size(); + writeMultiLevelIndex(entries); + bytes = (int) (out.size() - startPos); + entries = null; + } + + private void writeMultiLevelIndex(List keys) + throws IOException { + levels = 1; + while (maxIndexLevels == 0 || levels < maxIndexLevels) { + keys = writeOneLevel(keys); + if (keys == null) { + return; + } + levels++; + } + + // When maxIndexLevels has restricted the writer, write one + // index block with the entire remaining set of keys. + BlockWriter b = new BlockWriter( + INDEX_BLOCK_TYPE, keyType, + MAX_BLOCK_SIZE, + Math.max(restartInterval, keys.size() / MAX_RESTARTS)); + for (Entry e : keys) { + b.mustAdd(e); + } + rootPosition = out.size(); + b.writeTo(out); + } + + private List writeOneLevel(List keys) + throws IOException { + Section thisLevel = new Section(keyType); + for (Entry e : keys) { + thisLevel.write(e); + } + if (!thisLevel.idx.entries.isEmpty()) { + thisLevel.flushCurBlock(); + if (cur.padBetweenBlocks()) { + out.padBetweenBlocksToNextBlock(); + } + cur = null; + return thisLevel.idx.entries; + } + + // The current block fit entire level; make it the root. + rootPosition = out.size(); + cur.writeTo(out); + cur = null; + return null; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; + +/** Update that always rejects with {@code LOCK_FAILURE}. */ +class AlwaysFailUpdate extends RefUpdate { + private final RefTreeDatabase refdb; + + AlwaysFailUpdate(RefTreeDatabase refdb, String name) { + super(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null)); + this.refdb = refdb; + setCheckConflicting(false); + } + + /** {@inheritDoc} */ + @Override + protected RefDatabase getRefDatabase() { + return refdb; + } + + /** {@inheritDoc} */ + @Override + protected Repository getRepository() { + return refdb.getRepository(); + } + + /** {@inheritDoc} */ + @Override + protected boolean tryLock(boolean deref) throws IOException { + return false; + } + + /** {@inheritDoc} */ + @Override + protected void unlock() { + // No locks are held here. + } + + /** {@inheritDoc} */ + @Override + protected Result doUpdate(Result desiredResult) { + return Result.LOCK_FAILURE; + } + + /** {@inheritDoc} */ + @Override + protected Result doDelete(Result desiredResult) { + return Result.LOCK_FAILURE; + } + + /** {@inheritDoc} */ + @Override + protected Result doLink(String target) { + return Result.LOCK_FAILURE; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.Ref.Storage.NETWORK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.ReceiveCommand.Result; + +/** + * Command to create, update or delete an entry inside a + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}. + *

    + * Unlike {@link org.eclipse.jgit.transport.ReceiveCommand} (which can only + * update a reference to an {@link org.eclipse.jgit.lib.ObjectId}), a RefTree + * Command can also create, modify or delete symbolic references to a target + * reference. + *

    + * RefTree Commands may wrap a {@code ReceiveCommand} to allow callers to + * process an existing ReceiveCommand against a RefTree. + *

    + * Commands should be passed into + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree#apply(java.util.Collection)} + * for processing. + */ +public class Command { + /** + * Set unprocessed commands as failed due to transaction aborted. + *

    + * If a command is still + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} it + * will be set to + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}. + * If {@code why} is non-null its contents will be used as the message for + * the first command status. + * + * @param commands + * commands to mark as failed. + * @param why + * optional message to set on the first aborted command. + */ + public static void abort(Iterable commands, @Nullable String why) { + if (why == null || why.isEmpty()) { + why = JGitText.get().transactionAborted; + } + for (Command c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, why); + why = JGitText.get().transactionAborted; + } + } + } + + private final Ref oldRef; + private final Ref newRef; + private final ReceiveCommand cmd; + private Result result; + + /** + * Create a command to create, update or delete a reference. + *

    + * At least one of {@code oldRef} or {@code newRef} must be supplied. + * + * @param oldRef + * expected value. Null if the ref should not exist. + * @param newRef + * desired value, must be peeled if not null and not symbolic. + * Null to delete the ref. + */ + public Command(@Nullable Ref oldRef, @Nullable Ref newRef) { + this.oldRef = oldRef; + this.newRef = newRef; + this.cmd = null; + this.result = NOT_ATTEMPTED; + + if (oldRef == null && newRef == null) { + throw new IllegalArgumentException(); + } + if (newRef != null && !newRef.isPeeled() && !newRef.isSymbolic()) { + throw new IllegalArgumentException(); + } + if (oldRef != null && newRef != null + && !oldRef.getName().equals(newRef.getName())) { + throw new IllegalArgumentException(); + } + } + + /** + * Construct a RefTree command wrapped around a ReceiveCommand. + * + * @param rw + * walk instance to peel the {@code newId}. + * @param cmd + * command received from a push client. + * @throws org.eclipse.jgit.errors.MissingObjectException + * {@code oldId} or {@code newId} is missing. + * @throws java.io.IOException + * {@code oldId} or {@code newId} cannot be peeled. + */ + public Command(RevWalk rw, ReceiveCommand cmd) + throws MissingObjectException, IOException { + this.oldRef = toRef(rw, cmd.getOldId(), cmd.getOldSymref(), + cmd.getRefName(), false); + this.newRef = toRef(rw, cmd.getNewId(), cmd.getNewSymref(), + cmd.getRefName(), true); + this.cmd = cmd; + } + + static Ref toRef(RevWalk rw, ObjectId id, @Nullable String target, + String name, boolean mustExist) + throws MissingObjectException, IOException { + if (target != null) { + return new SymbolicRef(name, + new ObjectIdRef.Unpeeled(NETWORK, target, id)); + } else if (ObjectId.zeroId().equals(id)) { + return null; + } + + try { + RevObject o = rw.parseAny(id); + if (o instanceof RevTag) { + RevObject p = rw.peel(o); + return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy()); + } + return new ObjectIdRef.PeeledNonTag(NETWORK, name, id); + } catch (MissingObjectException e) { + if (mustExist) { + throw e; + } + return new ObjectIdRef.Unpeeled(NETWORK, name, id); + } + } + + /** + * Get name of the reference affected by this command. + * + * @return name of the reference affected by this command. + */ + public String getRefName() { + if (cmd != null) { + return cmd.getRefName(); + } else if (newRef != null) { + return newRef.getName(); + } + return oldRef.getName(); + } + + /** + * Set the result of this command. + * + * @param result + * the command result. + */ + public void setResult(Result result) { + setResult(result, null); + } + + /** + * Set the result of this command. + * + * @param result + * the command result. + * @param why + * optional message explaining the result status. + */ + public void setResult(Result result, @Nullable String why) { + if (cmd != null) { + cmd.setResult(result, why); + } else { + this.result = result; + } + } + + /** + * Get result of executing this command. + * + * @return result of executing this command. + */ + public Result getResult() { + return cmd != null ? cmd.getResult() : result; + } + + /** + * Get optional message explaining command failure. + * + * @return optional message explaining command failure. + */ + @Nullable + public String getMessage() { + return cmd != null ? cmd.getMessage() : null; + } + + /** + * Old peeled reference. + * + * @return the old reference; null if the command is creating the reference. + */ + @Nullable + public Ref getOldRef() { + return oldRef; + } + + /** + * New peeled reference. + * + * @return the new reference; null if the command is deleting the reference. + */ + @Nullable + public Ref getNewRef() { + return newRef; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + append(s, oldRef, "CREATE"); //$NON-NLS-1$ + s.append(' '); + append(s, newRef, "DELETE"); //$NON-NLS-1$ + s.append(' ').append(getRefName()); + s.append(' ').append(getResult()); + if (getMessage() != null) { + s.append(' ').append(getMessage()); + } + return s.toString(); + } + + private static void append(StringBuilder s, Ref r, String nullName) { + if (r == null) { + s.append(nullName); + } else if (r.isSymbolic()) { + s.append(r.getTarget().getName()); + } else { + ObjectId id = r.getObjectId(); + if (id != null) { + s.append(id.name()); + } + } + } + + /** + * Check the entry is consistent with either the old or the new ref. + * + * @param entry + * current entry; null if the entry does not exist. + * @return true if entry matches {@link #getOldRef()} or + * {@link #getNewRef()}; otherwise false. + */ + boolean checkRef(@Nullable DirCacheEntry entry) { + if (entry != null && entry.getRawMode() == 0) { + entry = null; + } + return check(entry, oldRef) || check(entry, newRef); + } + + private static boolean check(@Nullable DirCacheEntry cur, + @Nullable Ref exp) { + if (cur == null) { + // Does not exist, ok if oldRef does not exist. + return exp == null; + } else if (exp == null) { + // Expected to not exist, but currently exists, fail. + return false; + } + + if (exp.isSymbolic()) { + String dst = exp.getTarget().getName(); + return cur.getRawMode() == TYPE_SYMLINK + && cur.getObjectId().equals(symref(dst)); + } + + return cur.getRawMode() == TYPE_GITLINK + && cur.getObjectId().equals(exp.getObjectId()); + } + + static ObjectId symref(String s) { + @SuppressWarnings("resource") + ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); + return fmt.idFor(OBJ_BLOB, encode(s)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** Batch update a {@link RefTreeDatabase}. */ +class RefTreeBatch extends BatchRefUpdate { + private final RefTreeDatabase refdb; + private Ref src; + private ObjectId parentCommitId; + private ObjectId parentTreeId; + private RefTree tree; + private PersonIdent author; + private ObjectId newCommitId; + + RefTreeBatch(RefTreeDatabase refdb) { + super(refdb); + this.refdb = refdb; + } + + /** {@inheritDoc} */ + @Override + public void execute(RevWalk rw, ProgressMonitor monitor) + throws IOException { + List todo = new ArrayList<>(getCommands().size()); + for (ReceiveCommand c : getCommands()) { + if (!isAllowNonFastForwards()) { + if (c.getType() == UPDATE) { + c.updateType(rw); + } + if (c.getType() == UPDATE_NONFASTFORWARD) { + c.setResult(REJECTED_NONFASTFORWARD); + if (isAtomic()) { + ReceiveCommand.abort(getCommands()); + return; + } else { + continue; + } + } + } + todo.add(new Command(rw, c)); + } + init(rw); + execute(rw, todo); + } + + void init(RevWalk rw) throws IOException { + src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted()); + if (src != null && src.getObjectId() != null) { + RevCommit c = rw.parseCommit(src.getObjectId()); + parentCommitId = c; + parentTreeId = c.getTree(); + tree = RefTree.read(rw.getObjectReader(), c.getTree()); + } else { + parentCommitId = ObjectId.zeroId(); + parentTreeId = new ObjectInserter.Formatter() + .idFor(OBJ_TREE, new byte[] {}); + tree = RefTree.newEmptyTree(); + } + } + + @Nullable + Ref exactRef(ObjectReader reader, String name) throws IOException { + return tree.exactRef(reader, name); + } + + /** + * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}. + * + * @param rw + * current RevWalk handling the update or rename. + * @param todo + * commands to execute. Must never be a bootstrap reference name. + * @throws IOException + * the storage system is unable to read or write data. + */ + void execute(RevWalk rw, List todo) throws IOException { + for (Command c : todo) { + if (c.getResult() != NOT_ATTEMPTED) { + Command.abort(todo, null); + return; + } + if (refdb.conflictsWithBootstrap(c.getRefName())) { + c.setResult(REJECTED_OTHER_REASON, MessageFormat + .format(JGitText.get().invalidRefName, c.getRefName())); + Command.abort(todo, null); + return; + } + } + + if (apply(todo) && newCommitId != null) { + commit(rw, todo); + } + } + + private boolean apply(List todo) throws IOException { + if (!tree.apply(todo)) { + // apply set rejection information on commands. + return false; + } + + Repository repo = refdb.getRepository(); + try (ObjectInserter ins = repo.newObjectInserter()) { + CommitBuilder b = new CommitBuilder(); + b.setTreeId(tree.writeTree(ins)); + if (parentTreeId.equals(b.getTreeId())) { + for (Command c : todo) { + c.setResult(OK); + } + return true; + } + if (!parentCommitId.equals(ObjectId.zeroId())) { + b.setParentId(parentCommitId); + } + + author = getRefLogIdent(); + if (author == null) { + author = new PersonIdent(repo); + } + b.setAuthor(author); + b.setCommitter(author); + b.setMessage(getRefLogMessage()); + newCommitId = ins.insert(b); + ins.flush(); + } + return true; + } + + private void commit(RevWalk rw, List todo) throws IOException { + ReceiveCommand commit = new ReceiveCommand( + parentCommitId, newCommitId, + refdb.getTxnCommitted()); + updateBootstrap(rw, commit); + + if (commit.getResult() == OK) { + for (Command c : todo) { + c.setResult(OK); + } + } else { + Command.abort(todo, commit.getResult().name()); + } + } + + private void updateBootstrap(RevWalk rw, ReceiveCommand commit) + throws IOException { + BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate(); + u.setAllowNonFastForwards(true); + u.setPushCertificate(getPushCertificate()); + if (isRefLogDisabled()) { + u.disableRefLog(); + } else { + u.setRefLogIdent(author); + u.setRefLogMessage(getRefLogMessage(), false); + } + u.addCommand(commit); + u.execute(rw, NullProgressMonitor.INSTANCE); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +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; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.util.RefMap; + +/** + * Reference database backed by a + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}. + *

    + * The storage for RefTreeDatabase has two parts. The main part is a native Git + * tree object stored under the {@code refs/txn} namespace. To avoid cycles, + * references to {@code refs/txn} are not stored in that tree object, but + * instead in a "bootstrap" layer, which is a separate + * {@link org.eclipse.jgit.lib.RefDatabase} such as + * {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local + * reference files inside of {@code $GIT_DIR/refs}. + */ +public class RefTreeDatabase extends RefDatabase { + private final Repository repo; + private final RefDatabase bootstrap; + private final String txnCommitted; + + @Nullable + private final String txnNamespace; + private volatile Scanner.Result refs; + + /** + * Create a RefTreeDb for a repository. + * + * @param repo + * the repository using references in this database. + * @param bootstrap + * bootstrap reference database storing the references that + * anchor the + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}. + */ + public RefTreeDatabase(Repository repo, RefDatabase bootstrap) { + Config cfg = repo.getConfig(); + String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$ + if (committed == null || committed.isEmpty()) { + committed = "refs/txn/committed"; //$NON-NLS-1$ + } + + this.repo = repo; + this.bootstrap = bootstrap; + this.txnNamespace = initNamespace(committed); + this.txnCommitted = committed; + } + + /** + * Create a RefTreeDb for a repository. + * + * @param repo + * the repository using references in this database. + * @param bootstrap + * bootstrap reference database storing the references that + * anchor the + * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}. + * @param txnCommitted + * name of the bootstrap reference holding the committed RefTree. + */ + public RefTreeDatabase(Repository repo, RefDatabase bootstrap, + String txnCommitted) { + this.repo = repo; + this.bootstrap = bootstrap; + this.txnNamespace = initNamespace(txnCommitted); + this.txnCommitted = txnCommitted; + } + + private static String initNamespace(String committed) { + int s = committed.lastIndexOf('/'); + if (s < 0) { + return null; + } + return committed.substring(0, s + 1); // Keep trailing '/'. + } + + Repository getRepository() { + return repo; + } + + /** + * Get the bootstrap reference database + * + * @return the bootstrap reference database, which must be used to access + * {@link #getTxnCommitted()}, {@link #getTxnNamespace()}. + */ + public RefDatabase getBootstrap() { + return bootstrap; + } + + /** + * Get name of bootstrap reference anchoring committed RefTree. + * + * @return name of bootstrap reference anchoring committed RefTree. + */ + public String getTxnCommitted() { + return txnCommitted; + } + + /** + * Get namespace used by bootstrap layer. + * + * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}. Always + * ends in {@code '/'}. + */ + @Nullable + public String getTxnNamespace() { + return txnNamespace; + } + + /** {@inheritDoc} */ + @Override + public void create() throws IOException { + bootstrap.create(); + } + + /** {@inheritDoc} */ + @Override + public boolean performsAtomicTransactions() { + return true; + } + + /** {@inheritDoc} */ + @Override + public void refresh() { + bootstrap.refresh(); + } + + /** {@inheritDoc} */ + @Override + public void close() { + refs = null; + bootstrap.close(); + } + + /** {@inheritDoc} */ + @Override + public Ref getRef(String name) throws IOException { + String[] needle = new String[SEARCH_PATH.length]; + for (int i = 0; i < SEARCH_PATH.length; i++) { + needle[i] = SEARCH_PATH[i] + name; + } + return firstExactRef(needle); + } + + /** {@inheritDoc} */ + @Override + public Ref exactRef(String name) throws IOException { + if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) { + // Pass through names like MERGE_HEAD, ORIG_HEAD, FETCH_HEAD. + return bootstrap.exactRef(name); + } else if (conflictsWithBootstrap(name)) { + return null; + } + + boolean partial = false; + Ref src = bootstrap.exactRef(txnCommitted); + Scanner.Result c = refs; + if (c == null || !c.refTreeId.equals(idOf(src))) { + c = Scanner.scanRefTree(repo, src, prefixOf(name), false); + partial = true; + } + + Ref r = c.all.get(name); + if (r != null && r.isSymbolic()) { + r = c.sym.get(name); + if (partial && r.getObjectId() == null) { + // Attempting exactRef("HEAD") with partial scan will leave + // an unresolved symref as its target e.g. refs/heads/master + // was not read by the partial scan. Scan everything instead. + return getRefs(ALL).get(name); + } + } + return r; + } + + private static String prefixOf(String name) { + int s = name.lastIndexOf('/'); + if (s >= 0) { + return name.substring(0, s); + } + return ""; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public Map getRefs(String prefix) throws IOException { + if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') { + return new HashMap<>(0); + } + + Ref src = bootstrap.exactRef(txnCommitted); + Scanner.Result c = refs; + if (c == null || !c.refTreeId.equals(idOf(src))) { + c = Scanner.scanRefTree(repo, src, prefix, true); + if (prefix.isEmpty()) { + refs = c; + } + } + return new RefMap(prefix, RefList. emptyList(), c.all, c.sym); + } + + private static ObjectId idOf(@Nullable Ref src) { + return src != null && src.getObjectId() != null + ? src.getObjectId() + : ObjectId.zeroId(); + } + + /** {@inheritDoc} */ + @Override + public List getAdditionalRefs() throws IOException { + Collection txnRefs; + if (txnNamespace != null) { + txnRefs = bootstrap.getRefs(txnNamespace).values(); + } else { + Ref r = bootstrap.exactRef(txnCommitted); + if (r != null && r.getObjectId() != null) { + txnRefs = Collections.singleton(r); + } else { + txnRefs = Collections.emptyList(); + } + } + + List otherRefs = bootstrap.getAdditionalRefs(); + List all = new ArrayList<>(txnRefs.size() + otherRefs.size()); + all.addAll(txnRefs); + all.addAll(otherRefs); + return all; + } + + /** {@inheritDoc} */ + @Override + public Ref peel(Ref ref) throws IOException { + Ref i = ref.getLeaf(); + ObjectId id = i.getObjectId(); + if (i.isPeeled() || id == null) { + return ref; + } + try (RevWalk rw = new RevWalk(repo)) { + RevObject obj = rw.parseAny(id); + if (obj instanceof RevTag) { + ObjectId p = rw.peel(obj).copy(); + i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p); + } else { + i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id); + } + } + return recreate(ref, i); + } + + private static Ref recreate(Ref old, Ref leaf) { + if (old.isSymbolic()) { + Ref dst = recreate(old.getTarget(), leaf); + return new SymbolicRef(old.getName(), dst); + } + return leaf; + } + + /** {@inheritDoc} */ + @Override + public boolean isNameConflicting(String name) throws IOException { + return conflictsWithBootstrap(name) + || !getConflictingNames(name).isEmpty(); + } + + /** {@inheritDoc} */ + @Override + public BatchRefUpdate newBatchUpdate() { + return new RefTreeBatch(this); + } + + /** {@inheritDoc} */ + @Override + public RefUpdate newUpdate(String name, boolean detach) throws IOException { + if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) { + return bootstrap.newUpdate(name, detach); + } + if (conflictsWithBootstrap(name)) { + return new AlwaysFailUpdate(this, name); + } + + Ref r = exactRef(name); + if (r == null) { + r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null); + } + + boolean detaching = detach && r.isSymbolic(); + if (detaching) { + r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId()); + } + + RefTreeUpdate u = new RefTreeUpdate(this, r); + if (detaching) { + u.setDetachingSymbolicRef(); + } + return u; + } + + /** {@inheritDoc} */ + @Override + public RefRename newRename(String fromName, String toName) + throws IOException { + RefUpdate from = newUpdate(fromName, true); + RefUpdate to = newUpdate(toName, true); + return new RefTreeRename(this, from, to); + } + + boolean conflictsWithBootstrap(String name) { + if (txnNamespace != null && name.startsWith(txnNamespace)) { + return true; + } else if (txnCommitted.equals(name)) { + return true; + } + + if (name.indexOf('/') < 0 && !HEAD.equals(name)) { + return true; + } + + if (name.length() > txnCommitted.length() + && name.charAt(txnCommitted.length()) == '/' + && name.startsWith(txnCommitted)) { + return true; + } + return false; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.GITLINK; +import static org.eclipse.jgit.lib.FileMode.SYMLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.DirCacheNameConflictException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Tree of references in the reference graph. + *

    + * The root corresponds to the {@code "refs/"} subdirectory, for example the + * default reference {@code "refs/heads/master"} is stored at path + * {@code "heads/master"} in a {@code RefTree}. + *

    + * Normal references are stored as {@link org.eclipse.jgit.lib.FileMode#GITLINK} + * tree entries. The ObjectId in the tree entry is the ObjectId the reference + * refers to. + *

    + * Symbolic references are stored as + * {@link org.eclipse.jgit.lib.FileMode#SYMLINK} entries, with the blob storing + * the name of the target reference. + *

    + * Annotated tags also store the peeled object using a {@code GITLINK} entry + * with the suffix " ^" (space carrot), for example + * {@code "tags/v1.0"} stores the annotated tag object, while + * "tags/v1.0 ^" stores the commit the tag annotates. + *

    + * {@code HEAD} is a special case and stored as {@code "..HEAD"}. + */ +public class RefTree { + /** Suffix applied to GITLINK to indicate its the peeled value of a tag. */ + public static final String PEELED_SUFFIX = " ^"; //$NON-NLS-1$ + static final String ROOT_DOTDOT = ".."; //$NON-NLS-1$ + + /** + * Create an empty reference tree. + * + * @return a new empty reference tree. + */ + public static RefTree newEmptyTree() { + return new RefTree(DirCache.newInCore()); + } + + /** + * Load a reference tree. + * + * @param reader + * reader to scan the reference tree with. + * @param tree + * the tree to read. + * @return the ref tree read from the commit. + * @throws java.io.IOException + * the repository cannot be accessed through the reader. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * a tree object is corrupt and cannot be read. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * a tree object wasn't actually a tree. + * @throws org.eclipse.jgit.errors.MissingObjectException + * a reference tree object doesn't exist. + */ + public static RefTree read(ObjectReader reader, RevTree tree) + throws MissingObjectException, IncorrectObjectTypeException, + CorruptObjectException, IOException { + return new RefTree(DirCache.read(reader, tree)); + } + + private DirCache contents; + private Map pendingBlobs; + + private RefTree(DirCache dc) { + this.contents = dc; + } + + /** + * Read one reference. + *

    + * References are always returned peeled + * ({@link org.eclipse.jgit.lib.Ref#isPeeled()} is true). If the reference + * points to an annotated tag, the returned reference will be peeled and + * contain {@link org.eclipse.jgit.lib.Ref#getPeeledObjectId()}. + *

    + * If the reference is a symbolic reference and the chain depth is less than + * {@link org.eclipse.jgit.lib.RefDatabase#MAX_SYMBOLIC_REF_DEPTH} the + * returned reference is resolved. If the chain depth is longer, the + * symbolic reference is returned without resolving. + * + * @param reader + * to access objects necessary to read the requested reference. + * @param name + * name of the reference to read. + * @return the reference; null if it does not exist. + * @throws java.io.IOException + * cannot read a symbolic reference target. + */ + @Nullable + public Ref exactRef(ObjectReader reader, String name) throws IOException { + Ref r = readRef(reader, name); + if (r == null) { + return null; + } else if (r.isSymbolic()) { + return resolve(reader, r, 0); + } + + DirCacheEntry p = contents.getEntry(peeledPath(name)); + if (p != null && p.getRawMode() == TYPE_GITLINK) { + return new ObjectIdRef.PeeledTag(PACKED, r.getName(), + r.getObjectId(), p.getObjectId()); + } + return r; + } + + private Ref readRef(ObjectReader reader, String name) throws IOException { + DirCacheEntry e = contents.getEntry(refPath(name)); + return e != null ? toRef(reader, e, name) : null; + } + + private Ref toRef(ObjectReader reader, DirCacheEntry e, String name) + throws IOException { + int mode = e.getRawMode(); + if (mode == TYPE_GITLINK) { + ObjectId id = e.getObjectId(); + return new ObjectIdRef.PeeledNonTag(PACKED, name, id); + } + + if (mode == TYPE_SYMLINK) { + ObjectId id = e.getObjectId(); + String n = pendingBlobs != null ? pendingBlobs.get(id) : null; + if (n == null) { + byte[] bin = reader.open(id, OBJ_BLOB).getCachedBytes(); + n = RawParseUtils.decode(bin); + } + Ref dst = new ObjectIdRef.Unpeeled(NEW, n, null); + return new SymbolicRef(name, dst); + } + + return null; // garbage file or something; not a reference. + } + + private Ref resolve(ObjectReader reader, Ref ref, int depth) + throws IOException { + if (ref.isSymbolic() && depth < MAX_SYMBOLIC_REF_DEPTH) { + Ref r = readRef(reader, ref.getTarget().getName()); + if (r == null) { + return ref; + } + Ref dst = resolve(reader, r, depth + 1); + return new SymbolicRef(ref.getName(), dst); + } + return ref; + } + + /** + * Attempt a batch of commands against this RefTree. + *

    + * The batch is applied atomically, either all commands apply at once, or + * they all reject and the RefTree is left unmodified. + *

    + * On success (when this method returns {@code true}) the command results + * are left as-is (probably {@code NOT_ATTEMPTED}). Result fields are set + * only when this method returns {@code false} to indicate failure. + * + * @param cmdList + * to apply. All commands should still have result NOT_ATTEMPTED. + * @return true if the commands applied; false if they were rejected. + */ + public boolean apply(Collection cmdList) { + try { + DirCacheEditor ed = contents.editor(); + for (Command cmd : cmdList) { + if (!isValidRef(cmd)) { + cmd.setResult(REJECTED_OTHER_REASON, + JGitText.get().funnyRefname); + Command.abort(cmdList, null); + return false; + } + apply(ed, cmd); + } + ed.finish(); + return true; + } catch (DirCacheNameConflictException e) { + String r1 = refName(e.getPath1()); + String r2 = refName(e.getPath2()); + for (Command cmd : cmdList) { + if (r1.equals(cmd.getRefName()) + || r2.equals(cmd.getRefName())) { + cmd.setResult(LOCK_FAILURE); + break; + } + } + Command.abort(cmdList, null); + return false; + } catch (LockFailureException e) { + Command.abort(cmdList, null); + return false; + } + } + + private static boolean isValidRef(Command cmd) { + String n = cmd.getRefName(); + return HEAD.equals(n) || Repository.isValidRefName(n); + } + + private void apply(DirCacheEditor ed, final Command cmd) { + String path = refPath(cmd.getRefName()); + Ref oldRef = cmd.getOldRef(); + final Ref newRef = cmd.getNewRef(); + + if (newRef == null) { + checkRef(contents.getEntry(path), cmd); + ed.add(new DeletePath(path)); + cleanupPeeledRef(ed, oldRef); + return; + } + + if (newRef.isSymbolic()) { + final String dst = newRef.getTarget().getName(); + ed.add(new PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + checkRef(ent, cmd); + ObjectId id = Command.symref(dst); + ent.setFileMode(SYMLINK); + ent.setObjectId(id); + if (pendingBlobs == null) { + pendingBlobs = new HashMap<>(4); + } + pendingBlobs.put(id, dst); + } + }.setReplace(false)); + cleanupPeeledRef(ed, oldRef); + return; + } + + ed.add(new PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + checkRef(ent, cmd); + ent.setFileMode(GITLINK); + ent.setObjectId(newRef.getObjectId()); + } + }.setReplace(false)); + + if (newRef.getPeeledObjectId() != null) { + ed.add(new PathEdit(peeledPath(newRef.getName())) { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(GITLINK); + ent.setObjectId(newRef.getPeeledObjectId()); + } + }.setReplace(false)); + } else { + cleanupPeeledRef(ed, oldRef); + } + } + + private static void checkRef(@Nullable DirCacheEntry ent, Command cmd) { + if (!cmd.checkRef(ent)) { + cmd.setResult(LOCK_FAILURE); + throw new LockFailureException(); + } + } + + private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) { + if (ref != null && !ref.isSymbolic() + && (!ref.isPeeled() || ref.getPeeledObjectId() != null)) { + ed.add(new DeletePath(peeledPath(ref.getName()))); + } + } + + /** + * Convert a path name in a RefTree to the reference name known by Git. + * + * @param path + * name read from the RefTree structure, for example + * {@code "heads/master"}. + * @return reference name for the path, {@code "refs/heads/master"}. + */ + public static String refName(String path) { + if (path.startsWith(ROOT_DOTDOT)) { + return path.substring(2); + } + return R_REFS + path; + } + + static String refPath(String name) { + if (name.startsWith(R_REFS)) { + return name.substring(R_REFS.length()); + } + return ROOT_DOTDOT + name; + } + + private static String peeledPath(String name) { + return refPath(name) + PEELED_SUFFIX; + } + + /** + * Write this reference tree. + * + * @param inserter + * inserter to use when writing trees to the object database. + * Caller is responsible for flushing the inserter before trying + * to read the objects, or exposing them through a reference. + * @return the top level tree. + * @throws java.io.IOException + * a tree could not be written. + */ + public ObjectId writeTree(ObjectInserter inserter) throws IOException { + if (pendingBlobs != null) { + for (String s : pendingBlobs.values()) { + inserter.insert(OBJ_BLOB, encode(s)); + } + pendingBlobs = null; + } + return contents.writeTree(inserter); + } + + /** + * Create a deep copy of this RefTree. + * + * @return a deep copy of this RefTree. + */ + public RefTree copy() { + RefTree r = new RefTree(DirCache.newInCore()); + DirCacheBuilder b = r.contents.builder(); + for (int i = 0; i < contents.getEntryCount(); i++) { + b.add(new DirCacheEntry(contents.getEntry(i))); + } + b.finish(); + if (pendingBlobs != null) { + r.pendingBlobs = new HashMap<>(pendingBlobs); + } + return r; + } + + private static class LockFailureException extends RuntimeException { + private static final long serialVersionUID = 1L; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import org.eclipse.jgit.lib.RefDatabase; + +/** + * Magic reference name logic for RefTrees. + */ +public class RefTreeNames { + /** + * Suffix used on a {@link RefTreeDatabase#getTxnNamespace()} for user data. + *

    + * A {@link RefTreeDatabase}'s namespace may include a subspace (e.g. + * {@code "refs/txn/stage/"}) containing commit objects from the usual user + * portion of the repository (e.g. {@code "refs/heads/"}). These should be + * packed by the garbage collector alongside other user content rather than + * with the RefTree. + */ + private static final String STAGE = "stage/"; //$NON-NLS-1$ + + /** + * Determine if the reference is likely to be a RefTree. + * + * @param refdb + * database instance. + * @param ref + * reference name. + * @return {@code true} if the reference is a RefTree. + */ + public static boolean isRefTree(RefDatabase refdb, String ref) { + if (refdb instanceof RefTreeDatabase) { + RefTreeDatabase b = (RefTreeDatabase) refdb; + if (ref.equals(b.getTxnCommitted())) { + return true; + } + + String namespace = b.getTxnNamespace(); + if (namespace != null + && ref.startsWith(namespace) + && !ref.startsWith(namespace + STAGE)) { + return true; + } + } + return false; + } + + private RefTreeNames() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.RefUpdate.Result.REJECTED; +import static org.eclipse.jgit.lib.RefUpdate.Result.RENAMED; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevWalk; + +/** Single reference rename to {@link RefTreeDatabase}. */ +class RefTreeRename extends RefRename { + private final RefTreeDatabase refdb; + + RefTreeRename(RefTreeDatabase refdb, RefUpdate src, RefUpdate dst) { + super(src, dst); + this.refdb = refdb; + } + + /** {@inheritDoc} */ + @Override + protected Result doRename() throws IOException { + try (RevWalk rw = new RevWalk(refdb.getRepository())) { + RefTreeBatch batch = new RefTreeBatch(refdb); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage(getRefLogMessage(), false); + batch.init(rw); + + Ref head = batch.exactRef(rw.getObjectReader(), HEAD); + Ref oldRef = batch.exactRef(rw.getObjectReader(), source.getName()); + if (oldRef == null) { + return REJECTED; + } + + Ref newRef = asNew(oldRef); + List mv = new ArrayList<>(3); + mv.add(new Command(oldRef, null)); + mv.add(new Command(null, newRef)); + if (head != null && head.isSymbolic() + && head.getTarget().getName().equals(oldRef.getName())) { + mv.add(new Command( + head, + new SymbolicRef(head.getName(), newRef))); + } + batch.execute(rw, mv); + return RefTreeUpdate.translate(mv.get(1).getResult(), RENAMED); + } + } + + private Ref asNew(Ref src) { + String name = destination.getName(); + if (src.isSymbolic()) { + return new SymbolicRef(name, src.getTarget()); + } + + ObjectId peeled = src.getPeeledObjectId(); + if (peeled != null) { + return new ObjectIdRef.PeeledTag( + src.getStorage(), + name, + src.getObjectId(), + peeled); + } + + return new ObjectIdRef.PeeledNonTag( + src.getStorage(), + name, + src.getObjectId()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; + +import java.io.IOException; +import java.util.Collections; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** Single reference update to {@link RefTreeDatabase}. */ +class RefTreeUpdate extends RefUpdate { + private final RefTreeDatabase refdb; + private RevWalk rw; + private RefTreeBatch batch; + private Ref oldRef; + + RefTreeUpdate(RefTreeDatabase refdb, Ref ref) { + super(ref); + this.refdb = refdb; + setCheckConflicting(false); // Done automatically by doUpdate. + } + + /** {@inheritDoc} */ + @Override + protected RefDatabase getRefDatabase() { + return refdb; + } + + /** {@inheritDoc} */ + @Override + protected Repository getRepository() { + return refdb.getRepository(); + } + + /** {@inheritDoc} */ + @Override + protected boolean tryLock(boolean deref) throws IOException { + rw = new RevWalk(getRepository()); + batch = new RefTreeBatch(refdb); + batch.init(rw); + oldRef = batch.exactRef(rw.getObjectReader(), getName()); + if (oldRef != null && oldRef.getObjectId() != null) { + setOldObjectId(oldRef.getObjectId()); + } else if (oldRef == null && getExpectedOldObjectId() != null) { + setOldObjectId(ObjectId.zeroId()); + } + return true; + } + + /** {@inheritDoc} */ + @Override + protected void unlock() { + batch = null; + if (rw != null) { + rw.close(); + rw = null; + } + } + + /** {@inheritDoc} */ + @Override + protected Result doUpdate(Result desiredResult) throws IOException { + return run(newRef(getName(), getNewObjectId()), desiredResult); + } + + private Ref newRef(String name, ObjectId id) + throws MissingObjectException, IOException { + RevObject o = rw.parseAny(id); + if (o instanceof RevTag) { + RevObject p = rw.peel(o); + return new ObjectIdRef.PeeledTag(LOOSE, name, id, p.copy()); + } + return new ObjectIdRef.PeeledNonTag(LOOSE, name, id); + } + + /** {@inheritDoc} */ + @Override + protected Result doDelete(Result desiredResult) throws IOException { + return run(null, desiredResult); + } + + /** {@inheritDoc} */ + @Override + protected Result doLink(String target) throws IOException { + Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); + SymbolicRef n = new SymbolicRef(getName(), dst); + Result desiredResult = getRef().getStorage() == NEW + ? Result.NEW + : Result.FORCED; + return run(n, desiredResult); + } + + private Result run(@Nullable Ref newRef, Result desiredResult) + throws IOException { + Command c = new Command(oldRef, newRef); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage(getRefLogMessage(), isRefLogIncludingResult()); + batch.execute(rw, Collections.singletonList(c)); + return translate(c.getResult(), desiredResult); + } + + static Result translate(ReceiveCommand.Result r, Result desiredResult) { + switch (r) { + case OK: + return desiredResult; + + case LOCK_FAILURE: + return Result.LOCK_FAILURE; + + case NOT_ATTEMPTED: + return Result.NOT_ATTEMPTED; + + case REJECTED_MISSING_OBJECT: + return Result.IO_FAILURE; + + case REJECTED_CURRENT_BRANCH: + return Result.REJECTED_CURRENT_BRANCH; + + case REJECTED_OTHER_REASON: + case REJECTED_NOCREATE: + case REJECTED_NODELETE: + case REJECTED_NONFASTFORWARD: + default: + return Result.REJECTED; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.Paths; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.RefList; + +/** A tree parser that extracts references from a {@link RefTree}. */ +class Scanner { + private static final int MAX_SYMLINK_BYTES = 10 << 10; + private static final byte[] BINARY_R_REFS = encode(R_REFS); + private static final byte[] REFS_DOT_DOT = encode("refs/.."); //$NON-NLS-1$ + + static class Result { + final ObjectId refTreeId; + final RefList all; + final RefList sym; + + Result(ObjectId id, RefList all, RefList sym) { + this.refTreeId = id; + this.all = all; + this.sym = sym; + } + } + + /** + * Scan a {@link RefTree} and parse entries into {@link Ref} instances. + * + * @param repo + * source repository containing the commit and tree objects that + * make up the RefTree. + * @param src + * bootstrap reference such as {@code refs/txn/committed} to read + * the reference tree tip from. The current ObjectId will be + * included in {@link Result#refTreeId}. + * @param prefix + * if non-empty a reference prefix to scan only a subdirectory. + * For example {@code prefix = "refs/heads/"} will limit the scan + * to only the {@code "heads"} directory of the RefTree, avoiding + * other directories like {@code "tags"}. Empty string reads all + * entries in the RefTree. + * @param recursive + * if true recurse into subdirectories of the reference tree; + * false to read only one level. Callers may use false during an + * implementation of {@code exactRef(String)} where only one + * reference is needed out of a specific subtree. + * @return sorted list of references after parsing. + * @throws IOException + * tree cannot be accessed from the repository. + */ + static Result scanRefTree(Repository repo, @Nullable Ref src, String prefix, + boolean recursive) throws IOException { + RefList.Builder all = new RefList.Builder<>(); + RefList.Builder sym = new RefList.Builder<>(); + + ObjectId srcId; + if (src != null && src.getObjectId() != null) { + try (ObjectReader reader = repo.newObjectReader()) { + srcId = src.getObjectId(); + scan(reader, srcId, prefix, recursive, all, sym); + } + } else { + srcId = ObjectId.zeroId(); + } + + RefList aList = all.toRefList(); + for (int idx = 0; idx < sym.size();) { + Ref s = sym.get(idx); + Ref r = resolve(s, 0, aList); + if (r != null) { + sym.set(idx++, r); + } else { + // Remove broken symbolic reference, they don't exist. + sym.remove(idx); + int rm = aList.find(s.getName()); + if (0 <= rm) { + aList = aList.remove(rm); + } + } + } + return new Result(srcId, aList, sym.toRefList()); + } + + private static void scan(ObjectReader reader, AnyObjectId srcId, + String prefix, boolean recursive, + RefList.Builder all, RefList.Builder sym) + throws IncorrectObjectTypeException, IOException { + CanonicalTreeParser p = createParserAtPath(reader, srcId, prefix); + if (p == null) { + return; + } + + while (!p.eof()) { + int mode = p.getEntryRawMode(); + if (mode == TYPE_TREE) { + if (recursive) { + p = p.createSubtreeIterator(reader); + } else { + p = p.next(); + } + continue; + } + + if (!curElementHasPeelSuffix(p)) { + Ref r = toRef(reader, mode, p); + if (r != null) { + all.add(r); + if (r.isSymbolic()) { + sym.add(r); + } + } + } else if (mode == TYPE_GITLINK) { + peel(all, p); + } + p = p.next(); + } + } + + private static CanonicalTreeParser createParserAtPath(ObjectReader reader, + AnyObjectId srcId, String prefix) throws IOException { + ObjectId root = toTree(reader, srcId); + if (prefix.isEmpty()) { + return new CanonicalTreeParser(BINARY_R_REFS, reader, root); + } + + String dir = RefTree.refPath(Paths.stripTrailingSeparator(prefix)); + TreeWalk tw = TreeWalk.forPath(reader, dir, root); + if (tw == null || !tw.isSubtree()) { + return null; + } + + ObjectId id = tw.getObjectId(0); + return new CanonicalTreeParser(encode(prefix), reader, id); + } + + private static Ref resolve(Ref ref, int depth, RefList refs) + throws IOException { + if (!ref.isSymbolic()) { + return ref; + } else if (MAX_SYMBOLIC_REF_DEPTH <= depth) { + return null; + } + + Ref r = refs.get(ref.getTarget().getName()); + if (r == null) { + return ref; + } + + Ref dst = resolve(r, depth + 1, refs); + if (dst == null) { + return null; + } + return new SymbolicRef(ref.getName(), dst); + } + + @SuppressWarnings("resource") + private static RevTree toTree(ObjectReader reader, AnyObjectId id) + throws IOException { + return new RevWalk(reader).parseTree(id); + } + + private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) { + int n = itr.getEntryPathLength(); + byte[] c = itr.getEntryPathBuffer(); + return n > 2 && c[n - 2] == ' ' && c[n - 1] == '^'; + } + + private static void peel(RefList.Builder all, CanonicalTreeParser p) { + String name = refName(p, true); + for (int idx = all.size() - 1; 0 <= idx; idx--) { + Ref r = all.get(idx); + int cmp = r.getName().compareTo(name); + if (cmp == 0) { + all.set(idx, new ObjectIdRef.PeeledTag(PACKED, r.getName(), + r.getObjectId(), p.getEntryObjectId())); + break; + } else if (cmp < 0) { + // Stray peeled name without matching base name; skip entry. + break; + } + } + } + + private static Ref toRef(ObjectReader reader, int mode, + CanonicalTreeParser p) throws IOException { + if (mode == TYPE_GITLINK) { + String name = refName(p, false); + ObjectId id = p.getEntryObjectId(); + return new ObjectIdRef.PeeledNonTag(PACKED, name, id); + + } else if (mode == TYPE_SYMLINK) { + ObjectId id = p.getEntryObjectId(); + byte[] bin = reader.open(id, OBJ_BLOB) + .getCachedBytes(MAX_SYMLINK_BYTES); + String dst = RawParseUtils.decode(bin); + Ref trg = new ObjectIdRef.Unpeeled(NEW, dst, null); + String name = refName(p, false); + return new SymbolicRef(name, trg); + } + return null; + } + + private static String refName(CanonicalTreeParser p, boolean peel) { + byte[] buf = p.getEntryPathBuffer(); + int len = p.getEntryPathLength(); + if (peel) { + len -= 2; + } + int ptr = 0; + if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) { + ptr = 7; + } + return RawParseUtils.decode(buf, ptr, len); + } + + private Scanner() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2018, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.submodule; + +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SUBMODULE_SECTION; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; + +/** + * Validations for the git submodule fields (name, path, uri). + * + * Invalid values in these fields can cause security problems as reported in + * CVE-2018-11235 and and CVE-2018-17456 + */ +public class SubmoduleValidator { + + /** + * Error validating a git submodule declaration + */ + public static class SubmoduleValidationException extends Exception { + + /** + * @param message + * Description of the problem + */ + public SubmoduleValidationException(String message) { + super(message); + } + + private static final long serialVersionUID = 1L; + } + + /** + * Validate name for a submodule + * + * @param name + * name of a submodule + * @throws SubmoduleValidationException + * name doesn't seem valid (detail in message) + */ + public static void assertValidSubmoduleName(String name) + throws SubmoduleValidationException { + if (name.contains("/../") || name.contains("\\..\\") //$NON-NLS-1$ //$NON-NLS-2$ + || name.startsWith("../") || name.startsWith("..\\") //$NON-NLS-1$ //$NON-NLS-2$ + || name.endsWith("/..") || name.endsWith("\\..")) { //$NON-NLS-1$ //$NON-NLS-2$ + // Submodule names are used to store the submodule repositories + // under $GIT_DIR/modules. Having ".." in submodule names makes a + // vulnerability (CVE-2018-11235 + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=535027#c0) + // Reject names containing ".." path segments. We don't + // automatically replace these characters or canonicalize by + // regarding the name as a file path. + // Since Path class is platform dependent, we manually check '/' and + // '\\' patterns here. + throw new SubmoduleValidationException(MessageFormat + .format(JGitText.get().invalidNameContainsDotDot, name)); + } + + if (name.startsWith("-")) { //$NON-NLS-1$ + throw new SubmoduleValidationException( + MessageFormat.format( + JGitText.get().submoduleNameInvalid, name)); + } + } + + /** + * Validate URI for a submodule + * + * @param uri + * uri of a submodule + * @throws SubmoduleValidationException + * uri doesn't seem valid + */ + public static void assertValidSubmoduleUri(String uri) + throws SubmoduleValidationException { + if (uri.startsWith("-")) { //$NON-NLS-1$ + throw new SubmoduleValidationException( + MessageFormat.format( + JGitText.get().submoduleUrlInvalid, uri)); + } + } + + /** + * Validate path for a submodule + * + * @param path + * path of a submodule + * @throws SubmoduleValidationException + * path doesn't look right + */ + public static void assertValidSubmodulePath(String path) + throws SubmoduleValidationException { + if (path.startsWith("-")) { //$NON-NLS-1$ + throw new SubmoduleValidationException( + MessageFormat.format( + JGitText.get().submodulePathInvalid, path)); + } + } + + /** + * @param gitModulesContents + * Contents of a .gitmodule file. They will be parsed internally. + * @throws IOException + * If the contents + */ + public static void assertValidGitModulesFile(String gitModulesContents) + throws IOException { + // Validate .gitmodules file + Config c = new Config(); + try { + c.fromText(gitModulesContents); + for (String subsection : + c.getSubsections(CONFIG_SUBMODULE_SECTION)) { + assertValidSubmoduleName(subsection); + + String url = c.getString( + CONFIG_SUBMODULE_SECTION, subsection, CONFIG_KEY_URL); + if (url != null) { + assertValidSubmoduleUri(url); + } + + String path = c.getString( + CONFIG_SUBMODULE_SECTION, subsection, CONFIG_KEY_PATH); + if (path != null) { + assertValidSubmodulePath(path); + } + } + } catch (ConfigInvalidException e) { + throw new IOException( + MessageFormat.format( + JGitText.get().invalidGitModules, + e)); + } catch (SubmoduleValidationException e) { + throw new IOException(e.getMessage(), e); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,7 @@ import org.eclipse.jgit.util.RawParseUtils; /** - * A prefix abbreviation of an {@link ObjectId}. + * A prefix abbreviation of an {@link org.eclipse.jgit.lib.ObjectId}. *

    * Sometimes Git produces abbreviated SHA-1 strings, using sufficient leading * digits from the ObjectId name to still be unique within the repository the @@ -109,13 +109,14 @@ } /** - * Convert an AbbreviatedObjectId from an {@link AnyObjectId}. + * Convert an AbbreviatedObjectId from an + * {@link org.eclipse.jgit.lib.AnyObjectId}. *

    * This method copies over all bits of the Id, and is therefore complete * (see {@link #isComplete()}). * * @param id - * the {@link ObjectId} to convert from. + * the {@link org.eclipse.jgit.lib.ObjectId} to convert from. * @return the converted object id. */ public static final AbbreviatedObjectId fromObjectId(AnyObjectId id) { @@ -205,17 +206,29 @@ w5 = new_5; } - /** @return number of hex digits appearing in this id */ + /** + * Get number of hex digits appearing in this id. + * + * @return number of hex digits appearing in this id. + */ public int length() { return nibbles; } - /** @return true if this ObjectId is actually a complete id. */ + /** + * Whether this ObjectId is actually a complete id. + * + * @return true if this ObjectId is actually a complete id. + */ public boolean isComplete() { return length() == Constants.OBJECT_ID_STRING_LENGTH; } - /** @return a complete ObjectId; null if {@link #isComplete()} is false */ + /** + * A complete ObjectId; null if {@link #isComplete()} is false + * + * @return a complete ObjectId; null if {@link #isComplete()} is false + */ public ObjectId toObjectId() { return isComplete() ? new ObjectId(w1, w2, w3, w4, w5) : null; } @@ -325,7 +338,11 @@ return NB.compareUInt32(w5, mask(5, bs[p + 4])); } - /** @return value for a fan-out style map, only valid of length >= 2. */ + /** + * Get value for a fan-out style map, only valid of length >= 2. + * + * @return value for a fan-out style map, only valid of length >= 2. + */ public final int getFirstByte() { return w1 >>> 24; } @@ -334,11 +351,13 @@ return mask(nibbles, word, v); } + /** {@inheritDoc} */ @Override public int hashCode() { - return w2; + return w1; } + /** {@inheritDoc} */ @Override public boolean equals(final Object o) { if (o instanceof AbbreviatedObjectId) { @@ -350,6 +369,8 @@ } /** + * Get string form of the abbreviation, in lower case hexadecimal. + * * @return string form of the abbreviation, in lower case hexadecimal. */ public final String name() { @@ -375,6 +396,7 @@ return new String(b, 0, nibbles); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,9 +53,9 @@ /** * A (possibly mutable) SHA-1 abstraction. *

    - * If this is an instance of {@link MutableObjectId} the concept of equality - * with this instance can alter at any time, if this instance is modified to - * represent a different object name. + * If this is an instance of {@link org.eclipse.jgit.lib.MutableObjectId} the + * concept of equality with this instance can alter at any time, if this + * instance is modified to represent a different object name. */ public abstract class AnyObjectId implements Comparable { @@ -117,14 +117,16 @@ * * @param index * index of the byte to obtain from the raw form of the ObjectId. - * Must be in range [0, {@link Constants#OBJECT_ID_LENGTH}). + * Must be in range [0, + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}). * @return the value of the requested byte at {@code index}. Returned values * are unsigned and thus are in the range [0,255] rather than the * signed byte range of [-128, 127]. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * {@code index} is less than 0, equal to - * {@link Constants#OBJECT_ID_LENGTH}, or greater than - * {@link Constants#OBJECT_ID_LENGTH}. + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}, or + * greater than + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}. */ public final int getByte(int index) { int w; @@ -152,13 +154,11 @@ } /** + * {@inheritDoc} + *

    * Compare this ObjectId to another and obtain a sort ordering. - * - * @param other - * the other id to compare to. Must not be null. - * @return < 0 if this id comes before other; 0 if this id is equal to - * other; > 0 if this id comes after other. */ + @Override public final int compareTo(final AnyObjectId other) { if (this == other) return 0; @@ -261,6 +261,8 @@ return abbr.prefixCompare(this) == 0; } + /** {@inheritDoc} */ + @Override public final int hashCode() { return w2; } @@ -276,6 +278,8 @@ return other != null ? equals(this, other) : false; } + /** {@inheritDoc} */ + @Override public final boolean equals(final Object o) { if (o instanceof AnyObjectId) return equals((AnyObjectId) o); @@ -334,7 +338,7 @@ * * @param w * the stream to write to. - * @throws IOException + * @throws java.io.IOException * the stream writing failed. */ public void copyRawTo(final OutputStream w) throws IOException { @@ -358,7 +362,7 @@ * * @param w * the stream to copy to. - * @throws IOException + * @throws java.io.IOException * the stream writing failed. */ public void copyTo(final OutputStream w) throws IOException { @@ -419,7 +423,7 @@ * * @param w * the stream to copy to. - * @throws IOException + * @throws java.io.IOException * the stream writing failed. */ public void copyTo(final Writer w) throws IOException { @@ -435,7 +439,7 @@ * of object id (40 characters or larger). * @param w * the stream to copy to. - * @throws IOException + * @throws java.io.IOException * the stream writing failed. */ public void copyTo(final char[] tmp, final Writer w) throws IOException { @@ -485,6 +489,7 @@ dst[o--] = '0'; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -492,6 +497,8 @@ } /** + *

    name.

    + * * @return string form of the SHA-1, in lower case hexadecimal. */ public final String name() { @@ -499,6 +506,8 @@ } /** + * Get string form of the SHA-1, in lower case hexadecimal. + * * @return string form of the SHA-1, in lower case hexadecimal. */ public final String getName() { @@ -508,9 +517,11 @@ /** * Return an abbreviation (prefix) of this object SHA-1. *

    - * This implementation does not guarantee uniqueness. Callers should - * instead use {@link ObjectReader#abbreviate(AnyObjectId, int)} to obtain a - * unique abbreviation within the scope of a particular object database. + * This implementation does not guarantee uniqueness. Callers should instead + * use + * {@link org.eclipse.jgit.lib.ObjectReader#abbreviate(AnyObjectId, int)} to + * obtain a unique abbreviation within the scope of a particular object + * database. * * @param len * length of the abbreviated string. @@ -529,8 +540,8 @@ * Obtain an immutable copy of this current object name value. *

    * Only returns this if this instance is an unsubclassed - * instance of {@link ObjectId}; otherwise a new instance is returned - * holding the same value. + * instance of {@link org.eclipse.jgit.lib.ObjectId}; otherwise a new + * instance is returned holding the same value. *

    * This method is useful to shed any additional memory that may be tied to * the subclass, yet retain the unique identity of the object id for future diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,30 +63,37 @@ * Position this queue onto the next available result. * * Even if this method returns true, {@link #open()} may still throw - * {@link MissingObjectException} if the underlying object database was - * concurrently modified and the current object is no longer available. + * {@link org.eclipse.jgit.errors.MissingObjectException} if the underlying + * object database was concurrently modified and the current object is no + * longer available. * * @return true if there is a result available; false if the queue has * finished its input iteration. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. If the implementation is retaining * the application's objects {@link #getCurrent()} will be the * current object that is missing. There may be more results * still available, so the caller should continue invoking next * to examine another result. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public boolean next() throws MissingObjectException, IOException; /** + * Get the current object, null if the implementation lost track. + * * @return the current object, null if the implementation lost track. * Implementations may for performance reasons discard the caller's * ObjectId and provider their own through {@link #getObjectId()}. */ public T getCurrent(); - /** @return the ObjectId of the current object. Never null. */ + /** + * Get the ObjectId of the current object. Never null. + * + * @return the ObjectId of the current object. Never null. + */ public ObjectId getObjectId(); /** @@ -105,7 +112,7 @@ * current object that is missing. There may be more results * still available, so the caller should continue invoking next * to examine another result. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public ObjectLoader open() throws IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,27 +64,37 @@ * * @return true if there is a result available; false if the queue has * finished its input iteration. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. If the implementation is retaining * the application's objects {@link #getCurrent()} will be the * current object that is missing. There may be more results * still available, so the caller should continue invoking next * to examine another result. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public boolean next() throws MissingObjectException, IOException; /** + *

    getCurrent.

    + * * @return the current object, null if the implementation lost track. * Implementations may for performance reasons discard the caller's * ObjectId and provider their own through {@link #getObjectId()}. */ public T getCurrent(); - /** @return the ObjectId of the current object. Never null. */ + /** + * Get the ObjectId of the current object. Never null. + * + * @return the ObjectId of the current object. Never null. + */ public ObjectId getObjectId(); - /** @return the size of the current object. */ + /** + * Get the size of the current object. + * + * @return the size of the current object. + */ public long getSize(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,6 +70,8 @@ */ public boolean cancel(boolean mayInterruptIfRunning); - /** Release resources used by the operation, including cancellation. */ + /** + * Release resources used by the operation, including cancellation. + */ public void release(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -109,7 +109,8 @@ int pathStart = 8; int lineEnd = RawParseUtils.nextLF(content, pathStart); - if (content[lineEnd - 1] == '\n') + while (content[lineEnd - 1] == '\n' || + (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows())) lineEnd--; if (lineEnd == pathStart) throw new IOException(MessageFormat.format( @@ -159,7 +160,11 @@ return self(); } - /** @return the file system abstraction, or null if not set. */ + /** + * Get the file system abstraction, or null if not set. + * + * @return the file system abstraction, or null if not set. + */ public FS getFS() { return fs; } @@ -181,7 +186,11 @@ return self(); } - /** @return the meta data directory; null if not set. */ + /** + * Get the meta data directory; null if not set. + * + * @return the meta data directory; null if not set. + */ public File getGitDir() { return gitDir; } @@ -199,7 +208,11 @@ return self(); } - /** @return the object directory; null if not set. */ + /** + * Get the object directory; null if not set. + * + * @return the object directory; null if not set. + */ public File getObjectDirectory() { return objectDirectory; } @@ -217,7 +230,7 @@ public B addAlternateObjectDirectory(File other) { if (other != null) { if (alternateObjectDirectories == null) - alternateObjectDirectories = new LinkedList(); + alternateObjectDirectories = new LinkedList<>(); alternateObjectDirectories.add(other); } return self(); @@ -261,7 +274,11 @@ return self(); } - /** @return ordered array of alternate directories; null if non were set. */ + /** + * Get ordered array of alternate directories; null if non were set. + * + * @return ordered array of alternate directories; null if non were set. + */ public File[] getAlternateObjectDirectories() { final List alts = alternateObjectDirectories; if (alts == null) @@ -284,7 +301,11 @@ return self(); } - /** @return true if this repository was forced bare by {@link #setBare()}. */ + /** + * Whether this repository was forced bare by {@link #setBare()}. + * + * @return true if this repository was forced bare by {@link #setBare()}. + */ public boolean isBare() { return bare; } @@ -302,7 +323,11 @@ return self(); } - /** @return true if the repository must exist before being opened. */ + /** + * Whether the repository must exist before being opened. + * + * @return true if the repository must exist before being opened. + */ public boolean isMustExist() { return mustExist; } @@ -319,7 +344,11 @@ return self(); } - /** @return the work tree directory, or null if not set. */ + /** + * Get the work tree directory, or null if not set. + * + * @return the work tree directory, or null if not set. + */ public File getWorkTree() { return workTree; } @@ -340,7 +369,11 @@ return self(); } - /** @return the index file location, or null if not set. */ + /** + * Get the index file location, or null if not set. + * + * @return the index file location, or null if not set. + */ public File getIndexFile() { return indexFile; } @@ -428,7 +461,7 @@ public B addCeilingDirectory(File root) { if (root != null) { if (ceilingDirectories == null) - ceilingDirectories = new LinkedList(); + ceilingDirectories = new LinkedList<>(); ceilingDirectories.add(root); } return self(); @@ -543,10 +576,10 @@ * exception is thrown to the caller. * * @return {@code this} - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * insufficient parameters were set, or some parameters are * incompatible with one another. - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed to configure the rest of * the builder's parameters. */ @@ -568,9 +601,9 @@ * @return a repository matching this configuration. The caller is * responsible to close the repository instance when it is no longer * needed. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * insufficient parameters were set. - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed to configure the rest of * the builder's parameters. */ @@ -582,7 +615,9 @@ return repo; } - /** Require either {@code gitDir} or {@code workTree} to be set. */ + /** + * Require either {@code gitDir} or {@code workTree} to be set. + */ protected void requireGitDirOrWorkTree() { if (getGitDir() == null && getWorkTree() == null) throw new IllegalArgumentException( @@ -592,7 +627,7 @@ /** * Perform standard gitDir initialization. * - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed */ protected void setupGitDir() throws IOException { @@ -614,7 +649,7 @@ * end after the repository has been identified and its configuration is * available for inspection. * - * @throws IOException + * @throws java.io.IOException * the repository configuration could not be read. */ protected void setupWorkTree() throws IOException { @@ -641,7 +676,7 @@ /** * Configure the internal implementation details of the repository. * - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed */ protected void setupInternals() throws IOException { @@ -653,7 +688,7 @@ * Get the cached repository configuration, loading if not yet available. * * @return the configuration of the repository. - * @throws IOException + * @throws java.io.IOException * the configuration is not available, or is badly formed. */ protected Config getConfig() throws IOException { @@ -669,7 +704,7 @@ * empty configuration if gitDir was not set. * * @return the repository's configuration. - * @throws IOException + * @throws java.io.IOException * the configuration is not available. */ protected Config loadConfig() throws IOException { @@ -727,14 +762,22 @@ return null; } - /** @return the configured FS, or {@link FS#DETECTED}. */ + /** + * Get the configured FS, or {@link FS#DETECTED}. + * + * @return the configured FS, or {@link FS#DETECTED}. + */ protected FS safeFS() { return getFS() != null ? getFS() : FS.DETECTED; } - /** @return {@code this} */ + /** + * Get this object + * + * @return {@code this} + */ @SuppressWarnings("unchecked") protected final B self() { return (B) this; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,55 +43,15 @@ package org.eclipse.jgit.lib; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -/** ProgressMonitor that batches update events. */ -public abstract class BatchingProgressMonitor implements ProgressMonitor { - private static final ScheduledThreadPoolExecutor alarmQueue; - - static final Object alarmQueueKiller; - - static { - // To support garbage collection, start our thread but - // swap out the thread factory. When our class is GC'd - // the alarmQueueKiller will finalize and ask the executor - // to shutdown, ending the worker. - // - int threads = 1; - alarmQueue = new ScheduledThreadPoolExecutor(threads, - new ThreadFactory() { - private final ThreadFactory baseFactory = Executors - .defaultThreadFactory(); - - public Thread newThread(Runnable taskBody) { - Thread thr = baseFactory.newThread(taskBody); - thr.setName("JGit-AlarmQueue"); //$NON-NLS-1$ - thr.setDaemon(true); - return thr; - } - }); - alarmQueue.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); - alarmQueue.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - alarmQueue.prestartAllCoreThreads(); - - // Now that the threads are running, its critical to swap out - // our own thread factory for one that isn't in the ClassLoader. - // This allows the class to GC. - // - alarmQueue.setThreadFactory(Executors.defaultThreadFactory()); - - alarmQueueKiller = new Object() { - @Override - protected void finalize() { - alarmQueue.shutdownNow(); - } - }; - } +import org.eclipse.jgit.lib.internal.WorkQueue; +/** + * ProgressMonitor that batches update events. + */ +public abstract class BatchingProgressMonitor implements ProgressMonitor { private long delayStartTime; private TimeUnit delayStartUnit = TimeUnit.MILLISECONDS; @@ -112,10 +72,14 @@ delayStartUnit = unit; } + /** {@inheritDoc} */ + @Override public void start(int totalTasks) { // Ignore the number of tasks. } + /** {@inheritDoc} */ + @Override public void beginTask(String title, int work) { endTask(); task = new Task(title, work); @@ -123,11 +87,15 @@ task.delay(delayStartTime, delayStartUnit); } + /** {@inheritDoc} */ + @Override public void update(int completed) { if (task != null) task.update(this, completed); } + /** {@inheritDoc} */ + @Override public void endTask() { if (task != null) { task.end(this); @@ -135,6 +103,8 @@ } } + /** {@inheritDoc} */ + @Override public boolean isCancelled() { return false; } @@ -219,9 +189,10 @@ void delay(long time, TimeUnit unit) { display = false; - timerFuture = alarmQueue.schedule(this, time, unit); + timerFuture = WorkQueue.getExecutor().schedule(this, time, unit); } + @Override public void run() { display = true; } @@ -254,7 +225,8 @@ private void restartTimer() { display = false; - timerFuture = alarmQueue.schedule(this, 1, TimeUnit.SECONDS); + timerFuture = WorkQueue.getExecutor().schedule(this, 1, + TimeUnit.SECONDS); } void end(BatchingProgressMonitor pm) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,18 +49,23 @@ import java.io.IOException; import java.text.MessageFormat; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.concurrent.TimeoutException; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.MissingObjectException; 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; +import org.eclipse.jgit.util.time.ProposedTimestamp; /** * Batch of reference updates to be applied to a repository. @@ -69,6 +74,19 @@ * server is making changes to more than one reference at a time. */ public class BatchRefUpdate { + /** + * Maximum delay the calling thread will tolerate while waiting for a + * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s. + *

    + * A default of 5 seconds was chosen by guessing. A common assumption is + * clock skew between machines on the same LAN using an NTP server also on + * the same LAN should be under 5 seconds. 5 seconds is also not that long + * for a large `git push` operation to complete. + * + * @since 4.9 + */ + protected static final Duration MAX_WAIT = Duration.ofSeconds(5); + private final RefDatabase refdb; /** Commands to apply during this batch. */ @@ -86,9 +104,24 @@ /** Should the result value be appended to {@link #refLogMessage}. */ private boolean refLogIncludeResult; + /** + * Should reflogs be written even if the configured default for this ref is + * not to write it. + */ + private boolean forceRefLog; + /** Push certificate associated with this update. */ private PushCertificate pushCert; + /** Whether updates should be atomic. */ + private boolean atomic; + + /** Push options associated with this update. */ + private List pushOptions; + + /** Associated timestamps that should be blocked on before update. */ + private List timestamps; + /** * Initialize a new batch update. * @@ -97,10 +130,14 @@ */ protected BatchRefUpdate(RefDatabase refdb) { this.refdb = refdb; - this.commands = new ArrayList(); + this.commands = new ArrayList<>(); + this.atomic = refdb.performsAtomicTransactions(); } /** + * Whether the batch update will permit a non-fast-forward update to an + * existing reference. + * * @return true if the batch update will permit a non-fast-forward update to * an existing reference. */ @@ -120,7 +157,11 @@ return this; } - /** @return identity of the user making the change in the reflog. */ + /** + * Get identity of the user making the change in the reflog. + * + * @return identity of the user making the change in the reflog. + */ public PersonIdent getRefLogIdent() { return refLogIdent; } @@ -149,21 +190,41 @@ * @return message the caller wants to include in the reflog; null if the * update should not be logged. */ + @Nullable public String getRefLogMessage() { return refLogMessage; } - /** @return {@code true} if the ref log message should show the result. */ + /** + * Check whether the reflog message should include the result of the update, + * such as fast-forward or force-update. + *

    + * Describes the default for commands in this batch that do not override it + * with + * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}. + * + * @return true if the message should include the result. + */ public boolean isRefLogIncludingResult() { return refLogIncludeResult; } /** * Set the message to include in the reflog. + *

    + * Repository implementations may limit which reflogs are written by + * default, based on the project configuration. If a repo is not configured + * to write logs for this ref by default, setting the message alone may have + * no effect. To indicate that the repo should write logs for this update in + * spite of configured defaults, use {@link #setForceRefLog(boolean)}. + *

    + * Describes the default for commands in this batch that do not override it + * with + * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}. * * @param msg - * the message to describe this change. It may be null if - * appendStatus is null in order not to append to the reflog + * the message to describe this change. If null and appendStatus + * is false, the reflog will not be updated. * @param appendStatus * true if the status of the ref change (fast-forward or * forced-update) should be appended to the user supplied @@ -185,6 +246,8 @@ /** * Don't record this update in the ref's associated reflog. + *

    + * Equivalent to {@code setRefLogMessage(null, false)}. * * @return {@code this}. */ @@ -194,12 +257,73 @@ return this; } - /** @return true if log has been disabled by {@link #disableRefLog()}. */ + /** + * Force writing a reflog for the updated ref. + * + * @param force whether to force. + * @return {@code this} + * @since 4.9 + */ + public BatchRefUpdate setForceRefLog(boolean force) { + forceRefLog = force; + return this; + } + + /** + * Check whether log has been disabled by {@link #disableRefLog()}. + * + * @return true if disabled. + */ public boolean isRefLogDisabled() { return refLogMessage == null; } /** + * Check whether the reflog should be written regardless of repo defaults. + * + * @return whether force writing is enabled. + * @since 4.9 + */ + protected boolean isForceRefLog() { + return forceRefLog; + } + + /** + * Request that all updates in this batch be performed atomically. + *

    + * When atomic updates are used, either all commands apply successfully, or + * none do. Commands that might have otherwise succeeded are rejected with + * {@code REJECTED_OTHER_REASON}. + *

    + * This method only works if the underlying ref database supports atomic + * transactions, i.e. + * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()} + * returns true. Calling this method with true if the underlying ref + * database does not support atomic transactions will cause all commands to + * fail with {@code + * REJECTED_OTHER_REASON}. + * + * @param atomic + * whether updates should be atomic. + * @return {@code this} + * @since 4.4 + */ + public BatchRefUpdate setAtomic(boolean atomic) { + this.atomic = atomic; + return this; + } + + /** + * Whether updates should be atomic. + * + * @return atomic whether updates should be atomic. + * @since 4.4 + */ + public boolean isAtomic() { + return atomic; + } + + /** * Set a push certificate associated with this update. *

    * This usually includes commands to update the refs in this batch, but is not @@ -226,7 +350,11 @@ return pushCert; } - /** @return commands this update will process. */ + /** + * Get commands this update will process. + * + * @return commands this update will process. + */ public List getCommands() { return Collections.unmodifiableList(commands); } @@ -267,36 +395,118 @@ } /** + * Gets the list of option strings associated with this update. + * + * @return push options that were passed to {@link #execute}; prior to calling + * {@link #execute}, always returns null. + * @since 4.5 + */ + @Nullable + public List getPushOptions() { + return pushOptions; + } + + /** + * Set push options associated with this update. + *

    + * Implementations must call this at the top of {@link #execute(RevWalk, + * ProgressMonitor, List)}. + * + * @param options options passed to {@code execute}. + * @since 4.9 + */ + protected void setPushOptions(List options) { + pushOptions = options; + } + + /** + * Get list of timestamps the batch must wait for. + * + * @return list of timestamps the batch must wait for. + * @since 4.6 + */ + public List getProposedTimestamps() { + if (timestamps != null) { + return Collections.unmodifiableList(timestamps); + } + return Collections.emptyList(); + } + + /** + * Request the batch to wait for the affected timestamps to resolve. + * + * @param ts + * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. + * @return {@code this}. + * @since 4.6 + */ + public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) { + if (timestamps == null) { + timestamps = new ArrayList<>(4); + } + timestamps.add(ts); + return this; + } + + /** * Execute this batch update. *

    * The default implementation of this method performs a sequential reference * update over each reference. + *

    + * Implementations must respect the atomicity requirements of the underlying + * database as described in {@link #setAtomic(boolean)} and + * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}. * * @param walk * a RevWalk to parse tags in case the storage system wants to * store them pre-peeled, a common performance optimization. * @param monitor * progress monitor to receive update status on. - * @throws IOException + * @param options + * a list of option strings; set null to execute without + * @throws java.io.IOException * the database is unable to accept the update. Individual * command status must be tested to determine if there is a * partial failure, or a total failure. + * @since 4.5 */ - public void execute(RevWalk walk, ProgressMonitor monitor) - throws IOException { + public void execute(RevWalk walk, ProgressMonitor monitor, + List options) throws IOException { + + if (atomic && !refdb.performsAtomicTransactions()) { + for (ReceiveCommand c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, + JGitText.get().atomicRefUpdatesNotSupported); + } + } + return; + } + if (!blockUntilTimestamps(MAX_WAIT)) { + return; + } + + if (options != null) { + setPushOptions(options); + } + monitor.beginTask(JGitText.get().updatingReferences, commands.size()); - List commands2 = new ArrayList( + List commands2 = new ArrayList<>( commands.size()); - List namesToCheck = new ArrayList(commands.size()); // First delete refs. This may free the name space for some of the // updates. for (ReceiveCommand cmd : commands) { try { if (cmd.getResult() == NOT_ATTEMPTED) { + if (isMissing(walk, cmd.getOldId()) + || isMissing(walk, cmd.getNewId())) { + cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT); + continue; + } cmd.updateType(walk); switch (cmd.getType()) { case CREATE: - namesToCheck.add(cmd.getRefName()); commands2.add(cmd); break; case UPDATE: @@ -318,7 +528,7 @@ } if (!commands2.isEmpty()) { // What part of the name space is already taken - Collection takenNames = new HashSet(refdb.getRefs( + Collection takenNames = new HashSet<>(refdb.getRefs( RefDatabase.ALL).keySet()); Collection takenPrefixes = getTakenPrefixes(takenNames); @@ -349,7 +559,7 @@ break SWITCH; } ru.setCheckConflicting(false); - addRefToPrefixes(takenPrefixes, cmd.getRefName()); + takenPrefixes.addAll(getPrefixes(cmd.getRefName())); takenNames.add(cmd.getRefName()); cmd.setResult(ru.update(walk)); } @@ -365,48 +575,123 @@ monitor.endTask(); } - private static Collection getTakenPrefixes( - final Collection names) { - Collection ref = new HashSet(); - for (String name : names) - ref.addAll(getPrefixes(name)); - return ref; + private static boolean isMissing(RevWalk walk, ObjectId id) + throws IOException { + if (id.equals(ObjectId.zeroId())) { + return false; // Explicit add or delete is not missing. + } + try { + walk.parseAny(id); + return false; + } catch (MissingObjectException e) { + return true; + } } - private static void addRefToPrefixes(Collection prefixes, - String name) { - for (String prefix : getPrefixes(name)) { - prefixes.add(prefix); + /** + * Wait for timestamps to be in the past, aborting commands on timeout. + * + * @param maxWait + * maximum amount of time to wait for timestamps to resolve. + * @return true if timestamps were successfully waited for; false if + * commands were aborted. + * @since 4.6 + */ + protected boolean blockUntilTimestamps(Duration maxWait) { + if (timestamps == null) { + return true; + } + try { + ProposedTimestamp.blockUntil(timestamps, maxWait); + return true; + } catch (TimeoutException | InterruptedException e) { + String msg = JGitText.get().timeIsUncertain; + for (ReceiveCommand c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, msg); + } + } + return false; } } - static Collection getPrefixes(String s) { - Collection ret = new HashSet(); - int p1 = s.indexOf('/'); - while (p1 > 0) { - ret.add(s.substring(0, p1)); - p1 = s.indexOf('/', p1 + 1); + /** + * Execute this batch update without option strings. + * + * @param walk + * a RevWalk to parse tags in case the storage system wants to + * store them pre-peeled, a common performance optimization. + * @param monitor + * progress monitor to receive update status on. + * @throws java.io.IOException + * the database is unable to accept the update. Individual + * command status must be tested to determine if there is a + * partial failure, or a total failure. + */ + public void execute(RevWalk walk, ProgressMonitor monitor) + throws IOException { + execute(walk, monitor, null); + } + + private static Collection getTakenPrefixes(Collection names) { + Collection ref = new HashSet<>(); + for (String name : names) { + addPrefixesTo(name, ref); } + return ref; + } + + /** + * Get all path prefixes of a ref name. + * + * @param name + * ref name. + * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns + * {@code refs} and {@code refs/heads}. + * @since 4.9 + */ + protected static Collection getPrefixes(String name) { + Collection ret = new HashSet<>(); + addPrefixesTo(name, ret); return ret; } /** + * Add prefixes of a ref name to an existing collection. + * + * @param name + * ref name. + * @param out + * path prefixes of the ref name. For {@code refs/heads/foo}, + * returns {@code refs} and {@code refs/heads}. + * @since 4.9 + */ + protected static void addPrefixesTo(String name, Collection out) { + int p1 = name.indexOf('/'); + while (p1 > 0) { + out.add(name.substring(0, p1)); + p1 = name.indexOf('/', p1 + 1); + } + } + + /** * Create a new RefUpdate copying the batch settings. * * @param cmd * specific command the update should be created to copy. * @return a single reference update command. - * @throws IOException + * @throws java.io.IOException * the reference database cannot make a new update object for * the given reference. */ protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException { RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false); - if (isRefLogDisabled()) + if (isRefLogDisabled(cmd)) { ru.disableRefLog(); - else { + } else { ru.setRefLogIdent(refLogIdent); - ru.setRefLogMessage(refLogMessage, refLogIncludeResult); + ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd)); + ru.setForceRefLog(isForceRefLog(cmd)); } ru.setPushCertificate(pushCert); switch (cmd.getType()) { @@ -427,6 +712,63 @@ } } + /** + * Check whether reflog is disabled for a command. + * + * @param cmd + * specific command. + * @return whether the reflog is disabled, taking into account the state from + * this instance as well as overrides in the given command. + * @since 4.9 + */ + protected boolean isRefLogDisabled(ReceiveCommand cmd) { + return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled(); + } + + /** + * Get reflog message for a command. + * + * @param cmd + * specific command. + * @return reflog message, taking into account the state from this instance as + * well as overrides in the given command. + * @since 4.9 + */ + protected String getRefLogMessage(ReceiveCommand cmd) { + return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage(); + } + + /** + * Check whether the reflog message for a command should include the result. + * + * @param cmd + * specific command. + * @return whether the reflog message should show the result, taking into + * account the state from this instance as well as overrides in the + * given command. + * @since 4.9 + */ + protected boolean isRefLogIncludingResult(ReceiveCommand cmd) { + return cmd.hasCustomRefLog() + ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult(); + } + + /** + * Check whether the reflog for a command should be written regardless of repo + * defaults. + * + * @param cmd + * specific command. + * @return whether force writing is enabled. + * @since 4.9 + */ + protected boolean isForceRefLog(ReceiveCommand cmd) { + Boolean isForceRefLog = cmd.isForceRefLog(); + return isForceRefLog != null ? isForceRefLog.booleanValue() + : isForceRefLog(); + } + + /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); @@ -438,7 +780,11 @@ for (ReceiveCommand cmd : commands) { r.append(" "); //$NON-NLS-1$ r.append(cmd); - r.append(" (").append(cmd.getResult()).append(")\n"); //$NON-NLS-1$ //$NON-NLS-2$ + r.append(" (").append(cmd.getResult()); //$NON-NLS-1$ + if (cmd.getMessage() != null) { + r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$ + } + r.append(")\n"); //$NON-NLS-1$ } return r.append(']').toString(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,7 +63,11 @@ */ Bitmap getBitmap(AnyObjectId objectId); - /** @return a new {@code BitmapBuilder} based on the values in the index. */ + /** + * Create a new {@code BitmapBuilder} based on the values in the index. + * + * @return a new {@code BitmapBuilder} based on the values in the index. + */ BitmapBuilder newBitmapBuilder(); /** @@ -108,6 +112,7 @@ * * @return an Iterator. */ + @Override Iterator iterator(); } @@ -125,7 +130,9 @@ * @param type * the Git object type. See {@link Constants}. * @return true if the value was not contained or able to be loaded. + * @deprecated use {@link #or} or {@link #addObject} instead. */ + @Deprecated boolean add(AnyObjectId objectId, int type); /** @@ -138,6 +145,18 @@ boolean contains(AnyObjectId objectId); /** + * Adds the id to the bitmap. + * + * @param objectId + * the object ID + * @param type + * the Git object type. See {@link Constants}. + * @return the current builder. + * @since 4.2 + */ + BitmapBuilder addObject(AnyObjectId objectId, int type); + + /** * Remove the id from the bitmap. * * @param objectId @@ -152,6 +171,7 @@ * the other bitmap * @return the current builder. */ + @Override BitmapBuilder or(Bitmap other); /** @@ -162,6 +182,7 @@ * the other bitmap * @return the current builder. */ + @Override BitmapBuilder andNot(Bitmap other); /** @@ -171,6 +192,7 @@ * the other bitmap * @return the current builder. */ + @Override BitmapBuilder xor(Bitmap other); /** @return the fully built immutable bitmap */ @@ -190,5 +212,14 @@ /** @return the number of elements in the bitmap. */ int cardinality(); + + /** + * Get the BitmapIndex for this BitmapBuilder. + * + * @return the BitmapIndex for this BitmapBuilder + * + * @since 4.2 + */ + BitmapIndex getBitmapIndex(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ */ public abstract class BitmapObject { /** - * Get Git object type. See {@link Constants}. + * Get Git object type. See {@link org.eclipse.jgit.lib.Constants}. * * @return object type */ @@ -62,4 +62,4 @@ * @return unique hash of this object. */ public abstract ObjectId getObjectId(); -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,13 +73,20 @@ * the base configuration file * @param blob * the byte array, should be UTF-8 encoded text. - * @throws ConfigInvalidException + * @throws org.eclipse.jgit.errors.ConfigInvalidException * the byte array is not a valid configuration format. */ public BlobBasedConfig(Config base, final byte[] blob) throws ConfigInvalidException { super(base); - fromText(RawParseUtils.decode(blob)); + final String decoded; + if (isUtf8(blob)) { + decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, + blob, 3, blob.length); + } else { + decoded = RawParseUtils.decode(blob); + } + fromText(decoded); } /** @@ -91,9 +98,9 @@ * the repository * @param objectId * the object identifier - * @throws IOException + * @throws java.io.IOException * the blob cannot be read from the repository. - * @throws ConfigInvalidException + * @throws org.eclipse.jgit.errors.ConfigInvalidException * the blob is not a valid configuration format. */ public BlobBasedConfig(Config base, Repository db, AnyObjectId objectId) @@ -127,11 +134,11 @@ * the tree (or commit) that contains the object * @param path * the path within the tree - * @throws FileNotFoundException + * @throws java.io.FileNotFoundException * the path does not exist in the commit's tree. - * @throws IOException + * @throws java.io.IOException * the tree and/or blob cannot be accessed. - * @throws ConfigInvalidException + * @throws org.eclipse.jgit.errors.ConfigInvalidException * the blob is not a valid configuration format. */ public BlobBasedConfig(Config base, Repository db, AnyObjectId treeish, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017, 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.lib; + +import org.eclipse.jgit.errors.CorruptObjectException; + +/** + * Verifies that a blob object is a valid object. + *

    + * Unlike trees, commits and tags, there's no validity of blobs. Implementers + * can optionally implement this blob checker to reject certain blobs. + * + * @since 4.9 + */ +public interface BlobObjectChecker { + /** No-op implementation of {@link BlobObjectChecker}. */ + public static final BlobObjectChecker NULL_CHECKER = + new BlobObjectChecker() { + @Override + public void update(byte[] in, int p, int len) { + // Empty implementation. + } + + @Override + public void endBlob(AnyObjectId id) { + // Empty implementation. + } + }; + + /** + * Check a new fragment of the blob. + * + * @param in + * input array of bytes. + * @param offset + * offset to start at from {@code in}. + * @param len + * length of the fragment to check. + */ + void update(byte[] in, int offset, int len); + + /** + * Finalize the blob checking. + * + * @param id + * identity of the object being checked. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * if any error was detected. + */ + void endBlob(AnyObjectId id) throws CorruptObjectException; +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,6 +55,39 @@ public class BranchConfig { /** + * Config values for branch.[name].rebase (and pull.rebase). + * + * @since 4.5 + */ + public enum BranchRebaseMode implements Config.ConfigEnum { + + /** Value for rebasing */ + REBASE("true"), //$NON-NLS-1$ + /** Value for rebasing preserving local merge commits */ + PRESERVE("preserve"), //$NON-NLS-1$ + /** Value for rebasing interactively */ + INTERACTIVE("interactive"), //$NON-NLS-1$ + /** Value for not rebasing at all but merging */ + NONE("false"); //$NON-NLS-1$ + + private final String configValue; + + private BranchRebaseMode(String configValue) { + this.configValue = configValue; + } + + @Override + public String toConfigValue() { + return configValue; + } + + @Override + public boolean matchConfigValue(String s) { + return configValue.equals(s); + } + } + + /** * The value that means "local repository" for {@link #getRemote()}: * {@value} * @@ -80,6 +113,8 @@ } /** + * Get the full tracking branch name + * * @return the full tracking branch name or null if it could * not be determined */ @@ -96,6 +131,8 @@ } /** + * Get the full remote-tracking branch name + * * @return the full remote-tracking branch name or {@code null} if it could * not be determined. If you also want local tracked branches use * {@link #getTrackingBranch()} instead. @@ -110,6 +147,9 @@ } /** + * Whether the "remote" setting points to the local repository (with + * {@value #LOCAL_REPOSITORY}) + * * @return {@code true} if the "remote" setting points to the local * repository (with {@value #LOCAL_REPOSITORY}), false otherwise * @since 3.5 @@ -119,6 +159,8 @@ } /** + * Get the remote this branch is configured to fetch from/push to + * * @return the remote this branch is configured to fetch from/push to, or * {@code null} if not defined * @since 3.5 @@ -129,6 +171,8 @@ } /** + * Get the name of the upstream branch as it is called on the remote + * * @return the name of the upstream branch as it is called on the remote, or * {@code null} if not defined * @since 3.5 @@ -139,12 +183,25 @@ } /** + * Whether the branch is configured to be rebased + * * @return {@code true} if the branch is configured to be rebased * @since 3.5 */ public boolean isRebase() { - return config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - branchName, ConfigConstants.CONFIG_KEY_REBASE, false); + return getRebaseMode() != BranchRebaseMode.NONE; + } + + /** + * Retrieves the config value of branch.[name].rebase. + * + * @return the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} + * @since 4.5 + */ + public BranchRebaseMode getRebaseMode() { + return config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, branchName, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); } /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,7 +65,7 @@ * @param branchName * the local branch * @return the tracking status, or null if it is not known - * @throws IOException + * @throws java.io.IOException */ public static BranchTrackingStatus of(Repository repository, String branchName) throws IOException { @@ -79,30 +79,33 @@ if (trackingBranch == null) return null; - Ref tracking = repository.getRef(trackingBranch); + Ref tracking = repository.exactRef(trackingBranch); if (tracking == null) return null; - Ref local = repository.getRef(fullBranchName); + Ref local = repository.exactRef(fullBranchName); if (local == null) return null; - RevWalk walk = new RevWalk(repository); + try (RevWalk walk = new RevWalk(repository)) { - RevCommit localCommit = walk.parseCommit(local.getObjectId()); - RevCommit trackingCommit = walk.parseCommit(tracking.getObjectId()); + RevCommit localCommit = walk.parseCommit(local.getObjectId()); + RevCommit trackingCommit = walk.parseCommit(tracking.getObjectId()); - walk.setRevFilter(RevFilter.MERGE_BASE); - walk.markStart(localCommit); - walk.markStart(trackingCommit); - RevCommit mergeBase = walk.next(); - - walk.reset(); - walk.setRevFilter(RevFilter.ALL); - int aheadCount = RevWalkUtils.count(walk, localCommit, mergeBase); - int behindCount = RevWalkUtils.count(walk, trackingCommit, mergeBase); - - return new BranchTrackingStatus(trackingBranch, aheadCount, behindCount); + walk.setRevFilter(RevFilter.MERGE_BASE); + walk.markStart(localCommit); + walk.markStart(trackingCommit); + RevCommit mergeBase = walk.next(); + + walk.reset(); + walk.setRevFilter(RevFilter.ALL); + int aheadCount = RevWalkUtils.count(walk, localCommit, mergeBase); + int behindCount = RevWalkUtils.count(walk, trackingCommit, + mergeBase); + + return new BranchTrackingStatus(trackingBranch, aheadCount, + behindCount); + } } private final String remoteTrackingBranch; @@ -119,6 +122,8 @@ } /** + * Get full remote-tracking branch name + * * @return full remote-tracking branch name */ public String getRemoteTrackingBranch() { @@ -126,6 +131,9 @@ } /** + * Get number of commits that the local branch is ahead of the + * remote-tracking branch + * * @return number of commits that the local branch is ahead of the * remote-tracking branch */ @@ -134,6 +142,9 @@ } /** + * Get number of commits that the local branch is behind of the + * remote-tracking branch + * * @return number of commits that the local branch is behind of the * remote-tracking branch */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -8,13 +8,17 @@ public interface CheckoutEntry { /** + * Get the name of the branch before checkout + * * @return the name of the branch before checkout */ public abstract String getFromBranch(); /** + * Get the name of the branch after checkout + * * @return the name of the branch after checkout */ public abstract String getToBranch(); -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -88,13 +88,19 @@ private Charset encoding; - /** Initialize an empty commit. */ + /** + * Initialize an empty commit. + */ public CommitBuilder() { parentIds = EMPTY_OBJECTID_LIST; encoding = Constants.CHARSET; } - /** @return id of the root tree listing this commit's snapshot. */ + /** + * Get id of the root tree listing this commit's snapshot. + * + * @return id of the root tree listing this commit's snapshot. + */ public ObjectId getTreeId() { return treeId; } @@ -109,7 +115,11 @@ treeId = id.copy(); } - /** @return the author of this commit (who wrote it). */ + /** + * Get the author of this commit (who wrote it). + * + * @return the author of this commit (who wrote it). + */ public PersonIdent getAuthor() { return author; } @@ -124,7 +134,11 @@ author = newAuthor; } - /** @return the committer and commit time for this object. */ + /** + * Get the committer and commit time for this object. + * + * @return the committer and commit time for this object. + */ public PersonIdent getCommitter() { return committer; } @@ -139,7 +153,11 @@ committer = newCommitter; } - /** @return the ancestors of this commit. Never null. */ + /** + * Get the ancestors of this commit. + * + * @return the ancestors of this commit. Never null. + */ public ObjectId[] getParentIds() { return parentIds; } @@ -210,7 +228,11 @@ } } - /** @return the complete commit message. */ + /** + * Get the complete commit message. + * + * @return the complete commit message. + */ public String getMessage() { return message; } @@ -229,7 +251,8 @@ * Set the encoding for the commit information * * @param encodingName - * the encoding name. See {@link Charset#forName(String)}. + * the encoding name. See + * {@link java.nio.charset.Charset#forName(String)}. */ public void setEncoding(String encodingName) { encoding = Charset.forName(encodingName); @@ -245,7 +268,11 @@ encoding = enc; } - /** @return the encoding that should be used for the commit message text. */ + /** + * Get the encoding that should be used for the commit message text. + * + * @return the encoding that should be used for the commit message text. + */ public Charset getEncoding() { return encoding; } @@ -255,7 +282,7 @@ * * @return this object in the canonical commit format, suitable for storage * in a repository. - * @throws UnsupportedEncodingException + * @throws java.io.UnsupportedEncodingException * the encoding specified by {@link #getEncoding()} is not * supported by this Java runtime. */ @@ -314,7 +341,7 @@ * * @return this object in the canonical commit format, suitable for storage * in a repository. - * @throws UnsupportedEncodingException + * @throws java.io.UnsupportedEncodingException * the encoding specified by {@link #getEncoding()} is not * supported by this Java runtime. */ @@ -322,6 +349,7 @@ return build(); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,6 +65,12 @@ /** The "dfs" section */ public static final String CONFIG_DFS_SECTION = "dfs"; + /** + * The "receive" section + * @since 4.6 + */ + public static final String CONFIG_RECEIVE_SECTION = "receive"; + /** The "user" section */ public static final String CONFIG_USER_SECTION = "user"; @@ -101,12 +107,49 @@ */ public static final String CONFIG_PULL_SECTION = "pull"; + /** + * The "merge" section + * @since 4.9 + */ + public static final String CONFIG_MERGE_SECTION = "merge"; + + /** + * The "filter" section + * @since 4.6 + */ + public static final String CONFIG_FILTER_SECTION = "filter"; + /** The "algorithm" key */ public static final String CONFIG_KEY_ALGORITHM = "algorithm"; /** The "autocrlf" key */ public static final String CONFIG_KEY_AUTOCRLF = "autocrlf"; + /** + * The "auto" key + * @since 4.6 + */ + public static final String CONFIG_KEY_AUTO = "auto"; + + /** + * The "autogc" key + * @since 4.6 + */ + public static final String CONFIG_KEY_AUTOGC = "autogc"; + + /** + * The "autopacklimit" key + * @since 4.6 + */ + public static final String CONFIG_KEY_AUTOPACKLIMIT = "autopacklimit"; + + /** + * The "eol" key + * + * @since 4.3 + */ + public static final String CONFIG_KEY_EOL = "eol"; + /** The "bare" key */ public static final String CONFIG_KEY_BARE = "bare"; @@ -138,6 +181,13 @@ /** The "blockSize" key */ public static final String CONFIG_KEY_BLOCK_SIZE = "blockSize"; + /** + * The "concurrencyLevel" key + * + * @since 4.6 + */ + public static final String CONFIG_KEY_CONCURRENCY_LEVEL = "concurrencyLevel"; + /** The "deltaBaseCacheLimit" key */ public static final String CONFIG_KEY_DELTA_BASE_CACHE_LIMIT = "deltaBaseCacheLimit"; @@ -228,6 +278,12 @@ */ public static final String CONFIG_KEY_HIDEDOTFILES = "hidedotfiles"; + /** + * The "dirnogitlinks" key + * @since 4.3 + */ + public static final String CONFIG_KEY_DIRNOGITLINKS = "dirNoGitLinks"; + /** The "precomposeunicode" key */ public static final String CONFIG_KEY_PRECOMPOSEUNICODE = "precomposeunicode"; @@ -235,6 +291,26 @@ public static final String CONFIG_KEY_PRUNEEXPIRE = "pruneexpire"; /** + * The "prunepackexpire" key + * @since 4.3 + */ + public static final String CONFIG_KEY_PRUNEPACKEXPIRE = "prunepackexpire"; + + /** + * The "logexpiry" key + * + * @since 4.7 + */ + public static final String CONFIG_KEY_LOGEXPIRY = "logExpiry"; + + /** + * The "autodetach" key + * + * @since 4.7 + */ + public static final String CONFIG_KEY_AUTODETACH = "autoDetach"; + + /** * The "aggressiveDepth" key * @since 3.6 */ @@ -271,6 +347,13 @@ public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat"; /** + * The "supportsAtomicFileCreation" key in the "core section" + * + * @since 4.5 + */ + public static final String CONFIG_KEY_SUPPORTSATOMICFILECREATION = "supportsatomicfilecreation"; + + /** * The "noprefix" key in the "diff section" * @since 3.0 */ @@ -295,6 +378,13 @@ public static final String CONFIG_KEY_RENAMES = "renames"; /** + * The "inCoreLimit" key in the "merge section". It's a size limit (bytes) used to + * control a file to be stored in {@code Heap} or {@code LocalFile} during the merge. + * @since 4.9 + */ + public static final String CONFIG_KEY_IN_CORE_LIMIT = "inCoreLimit"; + + /** * The "prune" key * @since 3.3 */ @@ -311,4 +401,35 @@ * @since 4.0 */ public static final String CONFIG_KEY_STREAM_RATIO = "streamRatio"; + + /** + * Flag in the filter section whether to use JGit's implementations of + * filters and hooks + * @since 4.6 + */ + public static final String CONFIG_KEY_USEJGITBUILTIN = "useJGitBuiltin"; + + /** + * The "fetchRecurseSubmodules" key + * @since 4.7 + */ + public static final String CONFIG_KEY_FETCH_RECURSE_SUBMODULES = "fetchRecurseSubmodules"; + + /** + * The "recurseSubmodules" key + * @since 4.7 + */ + public static final String CONFIG_KEY_RECURSE_SUBMODULES = "recurseSubmodules"; + + /** + * The "required" key + * @since 4.11 + */ + public static final String CONFIG_KEY_REQUIRED = "required"; + + /** + * The "lfs" section + * @since 4.11 + */ + public static final String CONFIG_SECTION_LFS = "lfs"; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,26 +55,36 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedListener; import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.events.ListenerList; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.StringUtils; - +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.util.RawParseUtils; /** * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file. */ public class Config { + private static final String[] EMPTY_STRING_ARRAY = {}; - private static final long KiB = 1024; - private static final long MiB = 1024 * KiB; - private static final long GiB = 1024 * MiB; + + static final long KiB = 1024; + static final long MiB = 1024 * KiB; + static final long GiB = 1024 * MiB; + private static final int MAX_DEPTH = 10; + + private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter(); + + private static TypedConfigGetter typedGetter = DEFAULT_GETTER; /** the change listeners */ private final ListenerList listeners = new ListenerList(); @@ -96,9 +106,11 @@ * must ensure it is a special copy of the empty string. It also must * be treated like the empty string. */ - private static final String MAGIC_EMPTY_VALUE = new String(); + static final String MAGIC_EMPTY_VALUE = new String(); - /** Create a configuration with no default fallback. */ + /** + * Create a configuration with no default fallback. + */ public Config() { this(null); } @@ -112,7 +124,19 @@ */ public Config(Config defaultConfig) { baseConfig = defaultConfig; - state = new AtomicReference(newState()); + state = new AtomicReference<>(newState()); + } + + /** + * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is + * subsequently used to read typed values from all git configs. + * + * @param getter + * to use; if {@code null} use the default getter. + * @since 4.9 + */ + public static void setTypedConfigGetter(TypedConfigGetter getter) { + typedGetter = getter == null ? DEFAULT_GETTER : getter; } /** @@ -122,54 +146,96 @@ * the value to escape * @return the escaped value */ - private static String escapeValue(final String x) { - boolean inquote = false; - int lineStart = 0; - final StringBuilder r = new StringBuilder(x.length()); + static String escapeValue(String x) { + if (x.isEmpty()) { + return ""; //$NON-NLS-1$ + } + + boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' '; + StringBuilder r = new StringBuilder(x.length()); for (int k = 0; k < x.length(); k++) { - final char c = x.charAt(k); + char c = x.charAt(k); + // git-config(1) lists the limited set of supported escape sequences, but + // the documentation is otherwise not especially normative. In particular, + // which ones of these produce and/or require escaping and/or quoting + // around them is not documented and was discovered by trial and error. + // In summary: + // + // * Quotes are only required if there is leading/trailing whitespace or a + // comment character. + // * Bytes that have a supported escape sequence are escaped, except for + // \b for some reason which isn't. + // * Needing an escape sequence is not sufficient reason to quote the + // value. switch (c) { + case '\0': + // Unix command line calling convention cannot pass a '\0' as an + // argument, so there is no equivalent way in C git to store a null byte + // in a config value. + throw new IllegalArgumentException( + JGitText.get().configValueContainsNullByte); + case '\n': - if (inquote) { - r.append('"'); - inquote = false; - } - r.append("\\n\\\n"); //$NON-NLS-1$ - lineStart = r.length(); + r.append('\\').append('n'); break; case '\t': - r.append("\\t"); //$NON-NLS-1$ + r.append('\\').append('t'); break; case '\b': - r.append("\\b"); //$NON-NLS-1$ + // Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the + // \x08, but since both escaped and unescaped forms are readable, we'll + // prefer internal consistency here. + r.append('\\').append('b'); break; case '\\': - r.append("\\\\"); //$NON-NLS-1$ + r.append('\\').append('\\'); break; case '"': - r.append("\\\""); //$NON-NLS-1$ + r.append('\\').append('"'); break; - case ';': case '#': - if (!inquote) { - r.insert(lineStart, '"'); - inquote = true; - } + case ';': + needQuote = true; r.append(c); break; - case ' ': - if (!inquote && r.length() > 0 - && r.charAt(r.length() - 1) == ' ') { - r.insert(lineStart, '"'); - inquote = true; - } - r.append(' '); + default: + r.append(c); + break; + } + } + + return needQuote ? '"' + r.toString() + '"' : r.toString(); + } + + static String escapeSubsection(String x) { + if (x.isEmpty()) { + return "\"\""; //$NON-NLS-1$ + } + + StringBuilder r = new StringBuilder(x.length() + 2).append('"'); + for (int k = 0; k < x.length(); k++) { + char c = x.charAt(k); + + // git-config(1) lists the limited set of supported escape sequences + // (which is even more limited for subsection names than for values). + switch (c) { + case '\0': + throw new IllegalArgumentException( + JGitText.get().configSubsectionContainsNullByte); + + case '\n': + throw new IllegalArgumentException( + JGitText.get().configSubsectionContainsNewline); + + case '\\': + case '"': + r.append('\\').append(c); break; default: @@ -177,10 +243,8 @@ break; } } - if (inquote) { - r.append('"'); - } - return r.toString(); + + return r.append('"').toString(); } /** @@ -196,7 +260,7 @@ */ public int getInt(final String section, final String name, final int defaultValue) { - return getInt(section, null, name, defaultValue); + return typedGetter.getInt(this, section, null, name, defaultValue); } /** @@ -214,11 +278,8 @@ */ public int getInt(final String section, String subsection, final String name, final int defaultValue) { - final long val = getLong(section, subsection, name, defaultValue); - if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) - return (int) val; - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().integerValueOutOfRange - , section, name)); + return typedGetter.getInt(this, section, subsection, name, + defaultValue); } /** @@ -233,7 +294,7 @@ * @return an integer value from the configuration, or defaultValue. */ public long getLong(String section, String name, long defaultValue) { - return getLong(section, null, name, defaultValue); + return typedGetter.getLong(this, section, null, name, defaultValue); } /** @@ -251,37 +312,8 @@ */ public long getLong(final String section, String subsection, final String name, final long defaultValue) { - final String str = getString(section, subsection, name); - if (str == null) - return defaultValue; - - String n = str.trim(); - if (n.length() == 0) - return defaultValue; - - long mul = 1; - switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) { - case 'g': - mul = GiB; - break; - case 'm': - mul = MiB; - break; - case 'k': - mul = KiB; - break; - } - if (mul > 1) - n = n.substring(0, n.length() - 1).trim(); - if (n.length() == 0) - return defaultValue; - - try { - return mul * Long.parseLong(n); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidIntegerValue - , section, name, str)); - } + return typedGetter.getLong(this, section, subsection, name, + defaultValue); } /** @@ -298,7 +330,7 @@ */ public boolean getBoolean(final String section, final String name, final boolean defaultValue) { - return getBoolean(section, null, name, defaultValue); + return typedGetter.getBoolean(this, section, null, name, defaultValue); } /** @@ -317,24 +349,13 @@ */ public boolean getBoolean(final String section, String subsection, final String name, final boolean defaultValue) { - String n = getRawString(section, subsection, name); - if (n == null) - return defaultValue; - if (MAGIC_EMPTY_VALUE == n) - return true; - try { - return StringUtils.toBoolean(n); - } catch (IllegalArgumentException err) { - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidBooleanValue - , section, name, n)); - } + return typedGetter.getBoolean(this, section, subsection, name, + defaultValue); } /** * Parse an enumeration from the configuration. * - * @param - * type of the enumeration object. * @param section * section the key is grouped within. * @param subsection @@ -348,7 +369,8 @@ public > T getEnum(final String section, final String subsection, final String name, final T defaultValue) { final T[] all = allValuesOf(defaultValue); - return getEnum(all, section, subsection, name, defaultValue); + return typedGetter.getEnum(this, all, section, subsection, name, + defaultValue); } @SuppressWarnings("unchecked") @@ -366,8 +388,6 @@ /** * Parse an enumeration from the configuration. * - * @param - * type of the enumeration object. * @param all * all possible values in the enumeration which should be * recognized. Typically {@code EnumType.values()}. @@ -383,55 +403,8 @@ */ public > T getEnum(final T[] all, final String section, final String subsection, final String name, final T defaultValue) { - String value = getString(section, subsection, name); - if (value == null) - return defaultValue; - - if (all[0] instanceof ConfigEnum) { - for (T t : all) { - if (((ConfigEnum) t).matchConfigValue(value)) - return t; - } - } - - String n = value.replace(' ', '_'); - - // Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in - // the real world before its breakage was fully understood, we must - // also accept '-' as though it were ' '. - n = n.replace('-', '_'); - - T trueState = null; - T falseState = null; - for (T e : all) { - if (StringUtils.equalsIgnoreCase(e.name(), n)) - return e; - else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) //$NON-NLS-1$ - trueState = e; - else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) //$NON-NLS-1$ - falseState = e; - } - - // This is an odd little fallback. C Git sometimes allows boolean - // values in a tri-state with other things. If we have both a true - // and a false value in our enumeration, assume its one of those. - // - if (trueState != null && falseState != null) { - try { - return StringUtils.toBoolean(n) ? trueState : falseState; - } catch (IllegalArgumentException err) { - // Fall through and use our custom error below. - } - } - - if (subsection != null) - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().enumValueNotSupported3, section, subsection, - name, value)); - else - throw new IllegalArgumentException( - MessageFormat.format(JGitText.get().enumValueNotSupported2, - section, name, value)); + return typedGetter.getEnum(this, all, section, subsection, name, + defaultValue); } /** @@ -485,6 +458,53 @@ } /** + * Parse a numerical time unit, such as "1 minute", from the configuration. + * + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @param defaultValue + * default value to return if no value was present. + * @param wantUnit + * the units of {@code defaultValue} and the return value, as + * well as the units to assume if the value does not contain an + * indication of the units. + * @return the value, or {@code defaultValue} if not set, expressed in + * {@code units}. + * @since 4.5 + */ + public long getTimeUnit(String section, String subsection, String name, + long defaultValue, TimeUnit wantUnit) { + return typedGetter.getTimeUnit(this, section, subsection, name, + defaultValue, wantUnit); + } + + /** + * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the + * configuration. + * + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @return a possibly empty list of + * {@link org.eclipse.jgit.transport.RefSpec}s + * @since 4.9 + */ + public List getRefSpecs(String section, String subsection, + String name) { + return typedGetter.getRefSpecs(this, section, subsection, name); + } + + /** + * Get set of all subsections of specified section within this configuration + * and its base configuration + * * @param section * section to search for. * @return set of all subsections of specified section within this @@ -498,16 +518,20 @@ } /** - * @return the sections defined in this {@link Config}. The set's iterator - * returns sections in the order they are declared by the - * configuration starting from this instance and progressing through - * the base. + * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}. + * + * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}. + * The set's iterator returns sections in the order they are + * declared by the configuration starting from this instance and + * progressing through the base. */ public Set getSections() { return getState().getSections(); } /** + * Get the list of names defined for this section + * * @param section * the section * @return the list of names defined for this section @@ -517,6 +541,8 @@ } /** + * Get the list of names defined for this subsection + * * @param section * the section * @param subsection @@ -528,6 +554,8 @@ } /** + * Get the list of names defined for this section + * * @param section * the section * @param recursive @@ -541,6 +569,8 @@ } /** + * Get the list of names defined for this section + * * @param section * the section * @param subsection @@ -597,7 +627,8 @@ * Adds a listener to be notified about changes. *

    * Clients are supposed to remove the listeners after they are done with - * them using the {@link ListenerHandle#remove()} method + * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()} + * method * * @param listener * the listener @@ -630,15 +661,16 @@ listeners.dispatch(new ConfigChangedEvent()); } - private String getRawString(final String section, final String subsection, + String getRawString(final String section, final String subsection, final String name) { String[] lst = getRawStringList(section, subsection, name); - if (lst != null) - return lst[0]; - else if (baseConfig != null) + if (lst != null) { + return lst[lst.length - 1]; + } else if (baseConfig != null) { return baseConfig.getRawString(section, subsection, name); - else + } else { return null; + } } private String[] getRawStringList(String section, String subsection, @@ -708,11 +740,11 @@ final String s; if (value >= GiB && (value % GiB) == 0) - s = String.valueOf(value / GiB) + " g"; //$NON-NLS-1$ + s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$ else if (value >= MiB && (value % MiB) == 0) - s = String.valueOf(value / MiB) + " m"; //$NON-NLS-1$ + s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$ else if (value >= KiB && (value % KiB) == 0) - s = String.valueOf(value / KiB) + " k"; //$NON-NLS-1$ + s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$ else s = String.valueOf(value); @@ -751,8 +783,6 @@ * name = value *

    * - * @param - * type of the enumeration object. * @param section * section name, e.g "branch" * @param subsection @@ -768,7 +798,7 @@ if (value instanceof ConfigEnum) n = ((ConfigEnum) value).toConfigValue(); else - n = value.name().toLowerCase().replace('_', ' '); + n = value.name().toLowerCase(Locale.ROOT).replace('_', ' '); setString(section, subsection, name, n); } @@ -832,7 +862,7 @@ final String section, final String subsection) { final int max = srcState.entryList.size(); - final ArrayList r = new ArrayList(max); + final ArrayList r = new ArrayList<>(max); boolean lastWasMatch = false; for (ConfigLine e : srcState.entryList) { @@ -855,7 +885,8 @@ * *
     	 * [section "subsection"]
    -	 *         name = value
    +	 *         name = value1
    +	 *         name = value2
     	 * 
    * * @param section @@ -946,7 +977,7 @@ // for a new section header. Assume that and allocate the space. // final int max = src.entryList.size() + values.size() + 1; - final ArrayList r = new ArrayList(max); + final ArrayList r = new ArrayList<>(max); r.addAll(src.entryList); return r; } @@ -971,6 +1002,8 @@ } /** + * Get this configuration, formatted as a Git style text file. + * * @return this configuration, formatted as a Git style text file. */ public String toText() { @@ -1020,12 +1053,21 @@ * * @param text * Git style text file listing configuration properties. - * @throws ConfigInvalidException + * @throws org.eclipse.jgit.errors.ConfigInvalidException * the text supplied is not formatted correctly. No changes were * made to {@code this}. */ public void fromText(final String text) throws ConfigInvalidException { - final List newEntries = new ArrayList(); + state.set(newState(fromTextRecurse(text, 1))); + } + + private List fromTextRecurse(final String text, int depth) + throws ConfigInvalidException { + if (depth > MAX_DEPTH) { + throw new ConfigInvalidException( + JGitText.get().tooManyIncludeRecursions); + } + final List newEntries = new ArrayList<>(); final StringReader in = new StringReader(text); ConfigLine last = null; ConfigLine e = new ConfigLine(); @@ -1064,7 +1106,7 @@ e.section = readSectionName(in); input = in.read(); if ('"' == input) { - e.subsection = readValue(in, true, '"'); + e.subsection = readSubsectionName(in); input = in.read(); } if (']' != input) @@ -1081,13 +1123,60 @@ e.name = e.name.substring(0, e.name.length() - 1); e.value = MAGIC_EMPTY_VALUE; } else - e.value = readValue(in, false, -1); + e.value = readValue(in); + if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$ + addIncludedConfig(newEntries, e, depth); + } } else throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile); } - state.set(newState(newEntries)); + return newEntries; + } + + /** + * Read the included config from the specified (possibly) relative path + * + * @param relPath + * possibly relative path to the included config, as specified in + * this config + * @return the read bytes, or null if the included config should be ignored + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * if something went wrong while reading the config + * @since 4.10 + */ + @Nullable + protected byte[] readIncludedConfig(String relPath) + throws ConfigInvalidException { + return null; + } + + private void addIncludedConfig(final List newEntries, + ConfigLine line, int depth) throws ConfigInvalidException { + if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$ + line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) { + throw new ConfigInvalidException(MessageFormat.format( + JGitText.get().invalidLineInConfigFileWithParam, line)); + } + byte[] bytes = readIncludedConfig(line.value); + if (bytes == null) { + return; + } + + String decoded; + if (isUtf8(bytes)) { + decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, bytes, 3, + bytes.length); + } else { + decoded = RawParseUtils.decode(bytes); + } + try { + newEntries.addAll(fromTextRecurse(decoded, depth + 1)); + } catch (ConfigInvalidException e) { + throw new ConfigInvalidException(MessageFormat + .format(JGitText.get().cannotReadFile, line.value), e); + } } private ConfigSnapshot newState() { @@ -1107,6 +1196,19 @@ state.set(newState()); } + /** + * Check if bytes should be treated as UTF-8 or not. + * + * @param bytes + * the bytes to check encoding for. + * @return true if bytes should be treated as UTF-8, false otherwise. + * @since 4.4 + */ + protected boolean isUtf8(final byte[] bytes) { + return bytes.length >= 3 && bytes[0] == (byte) 0xEF + && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF; + } + private static String readSectionName(final StringReader in) throws ConfigInvalidException { final StringBuilder name = new StringBuilder(); @@ -1193,10 +1295,9 @@ return name.toString(); } - private static String readValue(final StringReader in, boolean quote, - final int eol) throws ConfigInvalidException { - final StringBuilder value = new StringBuilder(); - boolean space = false; + private static String readSubsectionName(StringReader in) + throws ConfigInvalidException { + StringBuilder r = new StringBuilder(); for (;;) { int c = in.read(); if (c < 0) { @@ -1204,30 +1305,81 @@ } if ('\n' == c) { - if (quote) - throw new ConfigInvalidException(JGitText.get().newlineInQuotesNotAllowed); + throw new ConfigInvalidException( + JGitText.get().newlineInQuotesNotAllowed); + } + if ('\\' == c) { + c = in.read(); + switch (c) { + case -1: + throw new ConfigInvalidException(JGitText.get().endOfFileInEscape); + + case '\\': + case '"': + r.append((char) c); + continue; + + default: + // C git simply drops backslashes if the escape sequence is not + // recognized. + r.append((char) c); + continue; + } + } + if ('"' == c) { + break; + } + + r.append((char) c); + } + return r.toString(); + } + + private static String readValue(final StringReader in) + throws ConfigInvalidException { + StringBuilder value = new StringBuilder(); + StringBuilder trailingSpaces = null; + boolean quote = false; + boolean inLeadingSpace = true; + + for (;;) { + int c = in.read(); + if (c < 0) { + break; + } + if ('\n' == c) { + if (quote) { + throw new ConfigInvalidException( + JGitText.get().newlineInQuotesNotAllowed); + } in.reset(); break; } - if (eol == c) + if (!quote && (';' == c || '#' == c)) { + if (trailingSpaces != null) { + trailingSpaces.setLength(0); + } + in.reset(); break; + } - if (!quote) { - if (Character.isWhitespace((char) c)) { - space = true; + char cc = (char) c; + if (Character.isWhitespace(cc)) { + if (inLeadingSpace) { continue; } - if (';' == c || '#' == c) { - in.reset(); - break; + if (trailingSpaces == null) { + trailingSpaces = new StringBuilder(); + } + trailingSpaces.append(cc); + continue; + } else { + inLeadingSpace = false; + if (trailingSpaces != null) { + value.append(trailingSpaces); + trailingSpaces.setLength(0); } - } - - if (space) { - if (value.length() > 0) - value.append(' '); - space = false; } if ('\\' == c) { @@ -1264,7 +1416,7 @@ continue; } - value.append((char) c); + value.append(cc); } return value.length() > 0 ? value.toString() : null; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java 2019-09-03 12:37:49.000000000 +0000 @@ -112,6 +112,7 @@ return a.equals(b); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java 2019-09-03 12:37:49.000000000 +0000 @@ -78,7 +78,7 @@ ConfigSnapshot(List entries, ConfigSnapshot base) { entryList = entries; - cache = new ConcurrentHashMap(16, 0.75f, 1); + cache = new ConcurrentHashMap<>(16, 0.75f, 1); baseState = base; } @@ -112,7 +112,7 @@ if (idx < 0) idx = -(idx + 1); - Map m = new LinkedHashMap(); + Map m = new LinkedHashMap<>(); while (idx < s.size()) { ConfigLine e = s.get(idx++); if (!e.match(section, subsection)) @@ -187,7 +187,7 @@ } private static List sort(List in) { - List sorted = new ArrayList(in.size()); + List sorted = new ArrayList<>(in.size()); for (ConfigLine line : in) { if (line.section != null && line.name != null) sorted.add(line); @@ -217,6 +217,7 @@ } private static class LineComparator implements Comparator { + @Override public int compare(ConfigLine a, ConfigLine b) { return compare2( a.section, a.subsection, a.name, @@ -236,8 +237,8 @@ final Map> subsections; SectionNames(ConfigSnapshot cfg) { - Map sec = new LinkedHashMap(); - Map> sub = new HashMap>(); + Map sec = new LinkedHashMap<>(); + Map> sub = new HashMap<>(); while (cfg != null) { for (ConfigLine e : cfg.entryList) { if (e.section == null) @@ -252,7 +253,7 @@ Set m = sub.get(l1); if (m == null) { - m = new LinkedHashSet(); + m = new LinkedHashSet<>(); sub.put(l1, m); } m.add(e.subsection); @@ -286,14 +287,17 @@ public Iterator iterator() { final Iterator i = names.values().iterator(); return new Iterator() { + @Override public boolean hasNext() { return i.hasNext(); } + @Override public String next() { return i.next(); } + @Override public void remove() { throw new UnsupportedOperationException(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,7 +1,7 @@ /* * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2006-2012, Shawn O. Pearce + * Copyright (C) 2006-2017, Shawn O. Pearce * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -55,7 +55,9 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.MutableInteger; -/** Misc. constants used throughout JGit. */ +/** + * Misc. constants used throughout JGit. + */ @SuppressWarnings("nls") public final class Constants { /** Hash function used natively by Git for all objects. */ @@ -273,6 +275,13 @@ public static final String INFO_EXCLUDE = "info/exclude"; /** + * Attributes-override-file + * + * @since 4.2 + */ + public static final String INFO_ATTRIBUTES = "info/attributes"; + + /** * The system property that contains the system user name * * @since 3.6 @@ -363,6 +372,34 @@ */ public static final String DOT_GIT_ATTRIBUTES = ".gitattributes"; + /** + * Key for filters in .gitattributes + * + * @since 4.2 + */ + public static final String ATTR_FILTER = "filter"; + + /** + * clean command name, used to call filter driver + * + * @since 4.2 + */ + public static final String ATTR_FILTER_TYPE_CLEAN = "clean"; + + /** + * smudge command name, used to call filter driver + * + * @since 4.2 + */ + public static final String ATTR_FILTER_TYPE_SMUDGE = "smudge"; + + /** + * Builtin filter commands start with this prefix + * + * @since 4.6 + */ + public static final String BUILTIN_FILTER_PREFIX = "jgit://builtin/"; + /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; @@ -394,10 +431,31 @@ public static final String HOOKS = "hooks"; /** + * Merge attribute. + * + * @since 4.9 + */ + public static final String ATTR_MERGE = "merge"; //$NON-NLS-1$ + + /** + * Diff attribute. + * + * @since 4.11 + */ + public static final String ATTR_DIFF = "diff"; //$NON-NLS-1$ + + /** + * Binary value for custom merger. + * + * @since 4.9 + */ + public static final String ATTR_BUILTIN_BINARY_MERGER = "binary"; //$NON-NLS-1$ + + /** * Create a new digest function for objects. * * @return a new digest object. - * @throws RuntimeException + * @throws java.lang.RuntimeException * this Java virtual machine does not support the required hash * function. Very unlikely given that JGit uses a hash function * that is in the Java reference specification. @@ -475,7 +533,7 @@ * endMark when the parse is successful. * @return a type code constant (one of {@link #OBJ_BLOB}, * {@link #OBJ_COMMIT}, {@link #OBJ_TAG}, {@link #OBJ_TREE}. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * there is no valid type identified by typeString. */ public static int decodeTypeString(final AnyObjectId id, @@ -553,7 +611,7 @@ * 127 (outside of 7-bit ASCII). * @return a byte array of the same length as the input string, holding the * same characters, in the same order. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the input string contains one or more characters outside of * the 7-bit ASCII character space. */ @@ -630,6 +688,13 @@ public static final ObjectId EMPTY_BLOB_ID = ObjectId .fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); + /** + * Suffix of lock file name + * + * @since 4.7 + */ + public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$ + private Constants() { // Hide the default constructor } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,11 +57,7 @@ */ public class CoreConfig { /** Key for {@link Config#get(SectionParser)}. */ - public static final Config.SectionParser KEY = new SectionParser() { - public CoreConfig parse(final Config cfg) { - return new CoreConfig(cfg); - } - }; + public static final Config.SectionParser KEY = CoreConfig::new; /** Permissible values for {@code core.autocrlf}. */ public static enum AutoCRLF { @@ -76,6 +72,46 @@ } /** + * Permissible values for {@code core.eol}. + *

    + * https://git-scm.com/docs/gitattributes + * + * @since 4.3 + */ + public static enum EOL { + /** checkin with LF, checkout with CRLF. */ + CRLF, + + /** checkin with LF, checkout without conversion. */ + LF, + + /** use the platform's native line ending. */ + NATIVE; + } + + /** + * EOL stream conversion protocol + * + * @since 4.3 + */ + public static enum EolStreamType { + /** convert to CRLF without binary detection */ + TEXT_CRLF, + + /** convert to LF without binary detection */ + TEXT_LF, + + /** convert to CRLF with binary detection */ + AUTO_CRLF, + + /** convert to LF with binary detection */ + AUTO_LF, + + /** do not convert */ + DIRECT; + } + + /** * Permissible values for {@code core.checkstat} * * @since 3.0 @@ -144,6 +180,8 @@ } /** + * Get the compression level to use when storing loose objects + * * @return The compression level to use when storing loose objects */ public int getCompression() { @@ -151,6 +189,8 @@ } /** + * Get the preferred pack index file format; 0 for oldest possible. + * * @return the preferred pack index file format; 0 for oldest possible. */ public int getPackIndexVersion() { @@ -158,6 +198,8 @@ } /** + * Whether to log all refUpdates + * * @return whether to log all refUpdates */ public boolean isLogAllRefUpdates() { @@ -165,6 +207,8 @@ } /** + * Get path of excludesfile + * * @return path of excludesfile */ public String getExcludesFile() { @@ -172,6 +216,8 @@ } /** + * Get path of attributesfile + * * @return path of attributesfile * @since 3.7 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config.ConfigEnum; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.util.StringUtils; + +/** + * An {@link org.eclipse.jgit.lib.TypedConfigGetter} that throws + * {@link java.lang.IllegalArgumentException} on invalid values. + * + * @since 4.9 + */ +public class DefaultTypedConfigGetter implements TypedConfigGetter { + + /** {@inheritDoc} */ + @Override + public boolean getBoolean(Config config, String section, String subsection, + String name, boolean defaultValue) { + String n = config.getRawString(section, subsection, name); + if (n == null) { + return defaultValue; + } + if (Config.MAGIC_EMPTY_VALUE == n) { + return true; + } + try { + return StringUtils.toBoolean(n); + } catch (IllegalArgumentException err) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().invalidBooleanValue, section, name, n)); + } + } + + /** {@inheritDoc} */ + @Override + public > T getEnum(Config config, T[] all, String section, + String subsection, String name, T defaultValue) { + String value = config.getString(section, subsection, name); + if (value == null) { + return defaultValue; + } + if (all[0] instanceof ConfigEnum) { + for (T t : all) { + if (((ConfigEnum) t).matchConfigValue(value)) { + return t; + } + } + } + + String n = value.replace(' ', '_'); + + // Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in + // the real world before its breakage was fully understood, we must + // also accept '-' as though it were ' '. + n = n.replace('-', '_'); + + T trueState = null; + T falseState = null; + for (T e : all) { + if (StringUtils.equalsIgnoreCase(e.name(), n)) { + return e; + } else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) { //$NON-NLS-1$ + trueState = e; + } else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) { //$NON-NLS-1$ + falseState = e; + } + } + + // This is an odd little fallback. C Git sometimes allows boolean + // values in a tri-state with other things. If we have both a true + // and a false value in our enumeration, assume its one of those. + // + if (trueState != null && falseState != null) { + try { + return StringUtils.toBoolean(n) ? trueState : falseState; + } catch (IllegalArgumentException err) { + // Fall through and use our custom error below. + } + } + + if (subsection != null) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().enumValueNotSupported3, + section, subsection, name, value)); + } else { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().enumValueNotSupported2, + section, name, value)); + } + } + + /** {@inheritDoc} */ + @Override + public int getInt(Config config, String section, String subsection, + String name, int defaultValue) { + long val = config.getLong(section, subsection, name, defaultValue); + if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) { + return (int) val; + } + throw new IllegalArgumentException(MessageFormat + .format(JGitText.get().integerValueOutOfRange, section, name)); + } + + /** {@inheritDoc} */ + @Override + public long getLong(Config config, String section, String subsection, + String name, long defaultValue) { + final String str = config.getString(section, subsection, name); + if (str == null) { + return defaultValue; + } + String n = str.trim(); + if (n.length() == 0) { + return defaultValue; + } + long mul = 1; + switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) { + case 'g': + mul = Config.GiB; + break; + case 'm': + mul = Config.MiB; + break; + case 'k': + mul = Config.KiB; + break; + } + if (mul > 1) { + n = n.substring(0, n.length() - 1).trim(); + } + if (n.length() == 0) { + return defaultValue; + } + try { + return mul * Long.parseLong(n); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().invalidIntegerValue, section, name, str)); + } + } + + /** {@inheritDoc} */ + @Override + public long getTimeUnit(Config config, String section, String subsection, + String name, long defaultValue, TimeUnit wantUnit) { + String valueString = config.getString(section, subsection, name); + + if (valueString == null) { + return defaultValue; + } + + String s = valueString.trim(); + if (s.length() == 0) { + return defaultValue; + } + + if (s.startsWith("-")/* negative */) { //$NON-NLS-1$ + throw notTimeUnit(section, subsection, name, valueString); + } + + Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$") //$NON-NLS-1$ + .matcher(valueString); + if (!m.matches()) { + return defaultValue; + } + + String digits = m.group(1); + String unitName = m.group(2).trim(); + + TimeUnit inputUnit; + int inputMul; + + if (unitName.isEmpty()) { + inputUnit = wantUnit; + inputMul = 1; + + } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ + inputUnit = TimeUnit.MILLISECONDS; + inputMul = 1; + + } else if (match(unitName, "s", "sec", "second", "seconds")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + inputUnit = TimeUnit.SECONDS; + inputMul = 1; + + } else if (match(unitName, "m", "min", "minute", "minutes")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + inputUnit = TimeUnit.MINUTES; + inputMul = 1; + + } else if (match(unitName, "h", "hr", "hour", "hours")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + inputUnit = TimeUnit.HOURS; + inputMul = 1; + + } else if (match(unitName, "d", "day", "days")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + inputUnit = TimeUnit.DAYS; + inputMul = 1; + + } else if (match(unitName, "w", "week", "weeks")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + inputUnit = TimeUnit.DAYS; + inputMul = 7; + + } else if (match(unitName, "mon", "month", "months")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + inputUnit = TimeUnit.DAYS; + inputMul = 30; + + } else if (match(unitName, "y", "year", "years")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + inputUnit = TimeUnit.DAYS; + inputMul = 365; + + } else { + throw notTimeUnit(section, subsection, name, valueString); + } + + try { + return wantUnit.convert(Long.parseLong(digits) * inputMul, + inputUnit); + } catch (NumberFormatException nfe) { + throw notTimeUnit(section, subsection, unitName, valueString); + } + } + + private static boolean match(final String a, final String... cases) { + for (final String b : cases) { + if (b != null && b.equalsIgnoreCase(a)) { + return true; + } + } + return false; + } + + private static IllegalArgumentException notTimeUnit(String section, + String subsection, String name, String valueString) { + if (subsection != null) { + return new IllegalArgumentException( + MessageFormat.format(JGitText.get().invalidTimeUnitValue3, + section, subsection, name, valueString)); + } + return new IllegalArgumentException( + MessageFormat.format(JGitText.get().invalidTimeUnitValue2, + section, name, valueString)); + } + + /** {@inheritDoc} */ + @Override + public @NonNull List getRefSpecs(Config config, String section, + String subsection, String name) { + String[] values = config.getStringList(section, subsection, name); + List result = new ArrayList<>(values.length); + for (String spec : values) { + result.add(new RefSpec(spec)); + } + return result; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,22 +51,32 @@ */ public abstract class EmptyProgressMonitor implements ProgressMonitor { + /** {@inheritDoc} */ + @Override public void start(int totalTasks) { // empty } + /** {@inheritDoc} */ + @Override public void beginTask(String title, int totalWork) { // empty } + /** {@inheritDoc} */ + @Override public void update(int completed) { // empty } + /** {@inheritDoc} */ + @Override public void endTask() { // empty } + /** {@inheritDoc} */ + @Override public boolean isCancelled() { return false; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java 2019-09-03 12:37:49.000000000 +0000 @@ -82,55 +82,57 @@ /** Bit pattern for {@link #TYPE_MASK} matching {@link #MISSING}. */ public static final int TYPE_MISSING = 0000000; - /** Mode indicating an entry is a tree (aka directory). */ - @SuppressWarnings("synthetic-access") + /** + * Mode indicating an entry is a tree (aka directory). + */ public static final FileMode TREE = new FileMode(TYPE_TREE, Constants.OBJ_TREE) { + @Override public boolean equals(final int modeBits) { return (modeBits & TYPE_MASK) == TYPE_TREE; } }; /** Mode indicating an entry is a symbolic link. */ - @SuppressWarnings("synthetic-access") public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK, Constants.OBJ_BLOB) { + @Override public boolean equals(final int modeBits) { return (modeBits & TYPE_MASK) == TYPE_SYMLINK; } }; /** Mode indicating an entry is a non-executable file. */ - @SuppressWarnings("synthetic-access") public static final FileMode REGULAR_FILE = new FileMode(0100644, Constants.OBJ_BLOB) { + @Override public boolean equals(final int modeBits) { return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) == 0; } }; /** Mode indicating an entry is an executable file. */ - @SuppressWarnings("synthetic-access") public static final FileMode EXECUTABLE_FILE = new FileMode(0100755, Constants.OBJ_BLOB) { + @Override public boolean equals(final int modeBits) { return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) != 0; } }; /** Mode indicating an entry is a submodule commit in another repository. */ - @SuppressWarnings("synthetic-access") public static final FileMode GITLINK = new FileMode(TYPE_GITLINK, Constants.OBJ_COMMIT) { + @Override public boolean equals(final int modeBits) { return (modeBits & TYPE_MASK) == TYPE_GITLINK; } }; /** Mode indicating an entry is missing during parallel walks. */ - @SuppressWarnings("synthetic-access") public static final FileMode MISSING = new FileMode(TYPE_MISSING, Constants.OBJ_BAD) { + @Override public boolean equals(final int modeBits) { return modeBits == 0; } @@ -197,9 +199,11 @@ } /** - * Test a file mode for equality with this {@link FileMode} object. + * Test a file mode for equality with this + * {@link org.eclipse.jgit.lib.FileMode} object. * * @param modebits + * a int. * @return true if the mode bits represent the same mode as this object */ public abstract boolean equals(final int modebits); @@ -215,7 +219,7 @@ * * @param os * stream to copy the mode to. - * @throws IOException + * @throws java.io.IOException * the stream encountered an error during the copy. */ public void copyTo(final OutputStream os) throws IOException { @@ -240,6 +244,8 @@ } /** + * Copy the number of bytes written by {@link #copyTo(OutputStream)}. + * * @return the number of bytes written by {@link #copyTo(OutputStream)}. */ public int copyToLength() { @@ -249,7 +255,7 @@ /** * Get the object type that should appear for this type of mode. *

    - * See the object type constants in {@link Constants}. + * See the object type constants in {@link org.eclipse.jgit.lib.Constants}. * * @return one of the well known object type constants. */ @@ -257,12 +263,19 @@ return objectType; } - /** Format this mode as an octal string (for debugging only). */ + /** + * {@inheritDoc} + *

    + * Format this mode as an octal string (for debugging only). + */ + @Override public String toString() { return Integer.toOctalString(modeBits); } /** + * Get the mode bits as an integer. + * * @return The mode bits as an integer. */ public int getBits() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -/** - * A representation of a file (blob) object in a {@link Tree}. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class FileTreeEntry extends TreeEntry { - private FileMode mode; - - /** - * Constructor for a File (blob) object. - * - * @param parent - * The {@link Tree} holding this object (or null) - * @param id - * the SHA-1 of the blob (or null for a yet unhashed file) - * @param nameUTF8 - * raw object name in the parent tree - * @param execute - * true if the executable flag is set - */ - public FileTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8, final boolean execute) { - super(parent, id, nameUTF8); - setExecutable(execute); - } - - public FileMode getMode() { - return mode; - } - - /** - * @return true if this file is executable - */ - public boolean isExecutable() { - return getMode().equals(FileMode.EXECUTABLE_FILE); - } - - /** - * @param execute set/reset the executable flag - */ - public void setExecutable(final boolean execute) { - mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; - } - - /** - * @return an {@link ObjectLoader} that will return the data - * @throws IOException - */ - public ObjectLoader openReader() throws IOException { - return getRepository().open(getId(), Constants.OBJ_BLOB); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(' '); - r.append(isExecutable() ? 'X' : 'F'); - r.append(' '); - r.append(getFullName()); - return r.toString(); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2009, Jonas Fonseca - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a gitlink entry used for submodules. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class GitlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public GitlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.GITLINK; - } - - @Override - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" G "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib; + +import org.eclipse.jgit.lib.AnyObjectId; + +/** + * A .gitmodules file found in the pack. Store the blob of the file itself (e.g. + * to access its contents) and the tree where it was found (e.g. to check if it + * is in the root) + * + * @since 4.7.5 + */ +public final class GitmoduleEntry { + private final AnyObjectId treeId; + + private final AnyObjectId blobId; + + /** + * A record of (tree, blob) for a .gitmodule file in a pack + * + * @param treeId + * tree id containing a .gitmodules entry + * @param blobId + * id of the blob of the .gitmodules file + */ + public GitmoduleEntry(AnyObjectId treeId, AnyObjectId blobId) { + // AnyObjectId's are reused, must keep a copy. + this.treeId = treeId.copy(); + this.blobId = blobId.copy(); + } + + /** + * @return Id of a .gitmodules file found in the pack + */ + public AnyObjectId getBlobId() { + return blobId; + } + + /** + * @return Id of a tree object where the .gitmodules file was found + */ + public AnyObjectId getTreeId() { + return treeId; + } +} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,7 +65,6 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode; @@ -73,6 +72,7 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; @@ -247,25 +247,25 @@ private final Repository repository; - private final RevTree tree; + private final AnyObjectId tree; private TreeFilter filter = null; private final WorkingTreeIterator initialWorkingTreeIterator; - private Set added = new HashSet(); + private Set added = new HashSet<>(); - private Set changed = new HashSet(); + private Set changed = new HashSet<>(); - private Set removed = new HashSet(); + private Set removed = new HashSet<>(); - private Set missing = new HashSet(); + private Set missing = new HashSet<>(); - private Set modified = new HashSet(); + private Set modified = new HashSet<>(); - private Set untracked = new HashSet(); + private Set untracked = new HashSet<>(); - private Map conflicts = new HashMap(); + private Map conflicts = new HashMap<>(); private Set ignored; @@ -275,22 +275,23 @@ private IndexDiffFilter indexDiffFilter; - private Map submoduleIndexDiffs = new HashMap(); + private Map submoduleIndexDiffs = new HashMap<>(); private IgnoreSubmoduleMode ignoreSubmoduleMode = null; - private Map> fileModes = new HashMap>(); + private Map> fileModes = new HashMap<>(); /** * Construct an IndexDiff * * @param repository + * a {@link org.eclipse.jgit.lib.Repository} object. * @param revstr - * symbolic name e.g. HEAD - * An EmptyTreeIterator is used if revstr cannot be resolved. + * symbolic name e.g. HEAD An EmptyTreeIterator is used if + * revstr cannot be resolved. * @param workingTreeIterator * iterator for working directory - * @throws IOException + * @throws java.io.IOException */ public IndexDiff(Repository repository, String revstr, WorkingTreeIterator workingTreeIterator) throws IOException { @@ -301,23 +302,29 @@ * Construct an Indexdiff * * @param repository + * a {@link org.eclipse.jgit.lib.Repository} object. * @param objectId * tree id. If null, an EmptyTreeIterator is used. * @param workingTreeIterator * iterator for working directory - * @throws IOException + * @throws java.io.IOException */ public IndexDiff(Repository repository, ObjectId objectId, WorkingTreeIterator workingTreeIterator) throws IOException { this.repository = repository; - if (objectId != null) - tree = new RevWalk(repository).parseTree(objectId); - else + if (objectId != null) { + try (RevWalk rw = new RevWalk(repository)) { + tree = rw.parseTree(objectId); + } + } else { tree = null; + } this.initialWorkingTreeIterator = workingTreeIterator; } /** + * Defines how modifications in submodules are treated + * * @param mode * defines how modifications in submodules are treated * @since 3.6 @@ -333,12 +340,14 @@ public interface WorkingTreeIteratorFactory { /** * @param repo - * @return a WorkingTreeIterator for repo + * the repository + * @return working tree iterator */ public WorkingTreeIterator getWorkingTreeIterator(Repository repo); } private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() { + @Override public WorkingTreeIterator getWorkingTreeIterator(Repository repo) { return new FileTreeIterator(repo); } @@ -359,6 +368,7 @@ * files. * * @param filter + * a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object. */ public void setFilter(TreeFilter filter) { this.filter = filter; @@ -370,7 +380,7 @@ * monitor is required. * * @return if anything is different between index, tree, and workdir - * @throws IOException + * @throws java.io.IOException */ public boolean diff() throws IOException { return diff(null, 0, 0, ""); //$NON-NLS-1$ @@ -392,10 +402,9 @@ * number or estimated files in the working tree * @param estIndexSize * number of estimated entries in the cache - * @param title - * + * @param title a {@link java.lang.String} object. * @return if anything is different between index, tree, and workdir - * @throws IOException + * @throws java.io.IOException */ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize, int estIndexSize, final String title) @@ -403,6 +412,7 @@ dirCache = repository.readDirCache(); try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.setOperationType(OperationType.CHECKIN_OP); treeWalk.setRecursive(true); // add the trees (tree, dirchache, workdir) if (tree != null) @@ -411,7 +421,8 @@ treeWalk.addTree(new EmptyTreeIterator()); treeWalk.addTree(new DirCacheIterator(dirCache)); treeWalk.addTree(initialWorkingTreeIterator); - Collection filters = new ArrayList(4); + initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1); + Collection filters = new ArrayList<>(4); if (monitor != null) { // Get the maximum size of the work tree and index @@ -507,14 +518,10 @@ } } - for (int i = 0; i < treeWalk.getTreeCount(); i++) { - Set values = fileModes.get(treeWalk.getFileMode(i)); - String path = treeWalk.getPathString(); - if (path != null) { - if (values == null) - values = new HashSet(); - values.add(path); - fileModes.put(treeWalk.getFileMode(i), values); + String path = treeWalk.getPathString(); + if (path != null) { + for (int i = 0; i < treeWalk.getTreeCount(); i++) { + recordFileMode(path, treeWalk.getFileMode(i)); } } } @@ -531,27 +538,27 @@ .equals(localIgnoreSubmoduleMode)) continue; } catch (ConfigInvalidException e) { - IOException e1 = new IOException(MessageFormat.format( + throw new IOException(MessageFormat.format( JGitText.get().invalidIgnoreParamSubmodule, - smw.getPath())); - e1.initCause(e); - throw e1; + smw.getPath()), e); } Repository subRepo = smw.getRepository(); if (subRepo != null) { + String subRepoPath = smw.getPath(); try { ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$ if (subHead != null - && !subHead.equals(smw.getObjectId())) - modified.add(smw.getPath()); - else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) { + && !subHead.equals(smw.getObjectId())) { + modified.add(subRepoPath); + recordFileMode(subRepoPath, FileMode.GITLINK); + } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) { IndexDiff smid = submoduleIndexDiffs.get(smw .getPath()); if (smid == null) { smid = new IndexDiff(subRepo, smw.getObjectId(), wTreeIt.getWorkingTreeIterator(subRepo)); - submoduleIndexDiffs.put(smw.getPath(), smid); + submoduleIndexDiffs.put(subRepoPath, smid); } if (smid.diff()) { if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED @@ -563,7 +570,8 @@ && smid.getRemoved().isEmpty()) { continue; } - modified.add(smw.getPath()); + modified.add(subRepoPath); + recordFileMode(subRepoPath, FileMode.GITLINK); } } } finally { @@ -587,6 +595,17 @@ return true; } + private void recordFileMode(String path, FileMode mode) { + Set values = fileModes.get(mode); + if (path != null) { + if (values == null) { + values = new HashSet<>(); + fileModes.put(mode, values); + } + values.add(path); + } + } + private boolean isEntryGitLink(AbstractTreeIterator ti) { return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK .getBits())); @@ -605,6 +624,8 @@ } /** + * Get list of files added to the index, not in the tree + * * @return list of files added to the index, not in the tree */ public Set getAdded() { @@ -612,6 +633,8 @@ } /** + * Get list of files changed from tree to index + * * @return list of files changed from tree to index */ public Set getChanged() { @@ -619,6 +642,8 @@ } /** + * Get list of files removed from index, but in tree + * * @return list of files removed from index, but in tree */ public Set getRemoved() { @@ -626,6 +651,8 @@ } /** + * Get list of files in index, but not filesystem + * * @return list of files in index, but not filesystem */ public Set getMissing() { @@ -633,6 +660,8 @@ } /** + * Get list of files modified on disk relative to the index + * * @return list of files modified on disk relative to the index */ public Set getModified() { @@ -640,6 +669,8 @@ } /** + * Get list of files that are not ignored, and not in the index. + * * @return list of files that are not ignored, and not in the index. */ public Set getUntracked() { @@ -647,6 +678,9 @@ } /** + * Get list of files that are in conflict, corresponds to the keys of + * {@link #getConflictingStageStates()} + * * @return list of files that are in conflict, corresponds to the keys of * {@link #getConflictingStageStates()} */ @@ -655,8 +689,11 @@ } /** + * Get the map from each path of {@link #getConflicting()} to its + * corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState} + * * @return the map from each path of {@link #getConflicting()} to its - * corresponding {@link StageState} + * corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState} * @since 3.0 */ public Map getConflictingStageStates() { @@ -677,11 +714,13 @@ } /** + * Get list of files with the flag assume-unchanged + * * @return list of files with the flag assume-unchanged */ public Set getAssumeUnchanged() { if (assumeUnchanged == null) { - HashSet unchanged = new HashSet(); + HashSet unchanged = new HashSet<>(); for (int i = 0; i < dirCache.getEntryCount(); i++) if (dirCache.getEntry(i).isAssumeValid()) unchanged.add(dirCache.getEntry(i).getPathString()); @@ -691,17 +730,19 @@ } /** + * Get list of folders containing only untracked files/folders + * * @return list of folders containing only untracked files/folders */ public Set getUntrackedFolders() { return ((indexDiffFilter == null) ? Collections. emptySet() - : new HashSet(indexDiffFilter.getUntrackedFolders())); + : new HashSet<>(indexDiffFilter.getUntrackedFolders())); } /** * Get the file mode of the given path in the index * - * @param path + * @param path a {@link java.lang.String} object. * @return file mode */ public FileMode getIndexMode(final String path) { @@ -713,7 +754,7 @@ * Get the list of paths that IndexDiff has detected to differ and have the * given file mode * - * @param mode + * @param mode a {@link org.eclipse.jgit.lib.FileMode} object. * @return the list of paths that IndexDiff has detected to differ and have * the given file mode * @since 3.6 @@ -721,7 +762,7 @@ public Set getPathsWithIndexMode(final FileMode mode) { Set paths = fileModes.get(mode); if (paths == null) - paths = new HashSet(); + paths = new HashSet<>(); return paths; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import java.util.zip.Inflater; -/** Creates zlib based inflaters as necessary for object decompression. */ +/** + * Creates zlib based inflaters as necessary for object decompression. + */ public class InflaterCache { private static final int SZ = 4; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008-2016, 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.lib.internal; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +/** + * Simple work queue to run tasks in the background + */ +public class WorkQueue { + private static final ScheduledThreadPoolExecutor executor; + + static final Object executorKiller; + + static { + // To support garbage collection, start our thread but + // swap out the thread factory. When our class is GC'd + // the executorKiller will finalize and ask the executor + // to shutdown, ending the worker. + // + int threads = 1; + executor = new ScheduledThreadPoolExecutor(threads, + new ThreadFactory() { + private final ThreadFactory baseFactory = Executors + .defaultThreadFactory(); + + @Override + public Thread newThread(Runnable taskBody) { + Thread thr = baseFactory.newThread(taskBody); + thr.setName("JGit-WorkQueue"); //$NON-NLS-1$ + thr.setContextClassLoader(null); + thr.setDaemon(true); + return thr; + } + }); + executor.setRemoveOnCancelPolicy(true); + executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + executor.prestartAllCoreThreads(); + + // Now that the threads are running, its critical to swap out + // our own thread factory for one that isn't in the ClassLoader. + // This allows the class to GC. + // + executor.setThreadFactory(Executors.defaultThreadFactory()); + + executorKiller = new Object() { + @Override + protected void finalize() { + executor.shutdownNow(); + } + }; + } + + /** + * Get the WorkQueue's executor + * + * @return the WorkQueue's executor + */ + public static ScheduledThreadPoolExecutor getExecutor() { + return executor; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -79,15 +79,17 @@ * * @param index * index of the byte to set in the raw form of the ObjectId. Must - * be in range [0, {@link Constants#OBJECT_ID_LENGTH}). + * be in range [0, + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}). * @param value * the value of the specified byte at {@code index}. Values are * unsigned and thus are in the range [0,255] rather than the * signed byte range of [-128, 127]. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * {@code index} is less than 0, equal to - * {@link Constants#OBJECT_ID_LENGTH}, or greater than - * {@link Constants#OBJECT_ID_LENGTH}. + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}, or + * greater than + * {@link org.eclipse.jgit.lib.Constants#OBJECT_ID_LENGTH}. */ public void setByte(int index, int value) { switch (index >> 2) { @@ -128,7 +130,9 @@ } } - /** Make this id match {@link ObjectId#zeroId()}. */ + /** + * Make this id match {@link org.eclipse.jgit.lib.ObjectId#zeroId()}. + */ public void clear() { w1 = 0; w2 = 0; @@ -198,7 +202,6 @@ * must be available within this integers array. * @param p * position to read the first integer of data from. - * */ public void fromRaw(final int[] ints, final int p) { w1 = ints[p]; @@ -209,6 +212,29 @@ } /** + * Convert an ObjectId from binary representation expressed in integers. + * + * @param a + * an int. + * @param b + * an int. + * @param c + * an int. + * @param d + * an int. + * @param e + * an int. + * @since 4.7 + */ + public void set(int a, int b, int c, int d, int e) { + w1 = a; + w2 = b; + w3 = c; + w4 = d; + w5 = e; + } + + /** * Convert an ObjectId from hex characters (US-ASCII). * * @param buf @@ -247,6 +273,7 @@ } } + /** {@inheritDoc} */ @Override public ObjectId toObjectId() { return new ObjectId(this); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,22 +56,32 @@ // Do not let others instantiate } + /** {@inheritDoc} */ + @Override public void start(int totalTasks) { // Do not report. } + /** {@inheritDoc} */ + @Override public void beginTask(String title, int totalWork) { // Do not report. } + /** {@inheritDoc} */ + @Override public void update(int completed) { // Do not report. } + /** {@inheritDoc} */ + @Override public boolean isCancelled() { return false; } + /** {@inheritDoc} */ + @Override public void endTask() { // Do not report. } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,21 +44,61 @@ package org.eclipse.jgit.lib; -import static org.eclipse.jgit.util.RawParseUtils.match; +import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BAD; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; +import static org.eclipse.jgit.util.Paths.compare; +import static org.eclipse.jgit.util.Paths.compareSameName; import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.text.MessageFormat; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; /** * Verifies that an object is formatted correctly. @@ -99,16 +139,124 @@ /** Header "tagger " */ public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$ - private final MutableObjectId tempId = new MutableObjectId(); + /** Path ".gitmodules" */ + private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES); - private final MutableInteger ptrout = new MutableInteger(); + /** + * Potential issues identified by the checker. + * + * @since 4.2 + */ + public enum ErrorType { + // @formatter:off + // These names match git-core so that fsck section keys also match. + /***/ NULL_SHA1, + /***/ DUPLICATE_ENTRIES, + /***/ TREE_NOT_SORTED, + /***/ ZERO_PADDED_FILEMODE, + /***/ EMPTY_NAME, + /***/ FULL_PATHNAME, + /***/ HAS_DOT, + /***/ HAS_DOTDOT, + /***/ HAS_DOTGIT, + /***/ BAD_OBJECT_SHA1, + /***/ BAD_PARENT_SHA1, + /***/ BAD_TREE_SHA1, + /***/ MISSING_AUTHOR, + /***/ MISSING_COMMITTER, + /***/ MISSING_OBJECT, + /***/ MISSING_TREE, + /***/ MISSING_TYPE_ENTRY, + /***/ MISSING_TAG_ENTRY, + /***/ BAD_DATE, + /***/ BAD_EMAIL, + /***/ BAD_TIMEZONE, + /***/ MISSING_EMAIL, + /***/ MISSING_SPACE_BEFORE_DATE, + /***/ UNKNOWN_TYPE, + + // These are unique to JGit. + /***/ WIN32_BAD_NAME, + /***/ BAD_UTF8; + // @formatter:on + + /** @return camelCaseVersion of the name. */ + public String getMessageId() { + String n = name(); + StringBuilder r = new StringBuilder(n.length()); + for (int i = 0; i < n.length(); i++) { + char c = n.charAt(i); + if (c != '_') { + r.append(StringUtils.toLowerCase(c)); + } else { + r.append(n.charAt(++i)); + } + } + return r.toString(); + } + } - private boolean allowZeroMode; + private final MutableObjectId tempId = new MutableObjectId(); + private final MutableInteger bufPtr = new MutableInteger(); + private EnumSet errors = EnumSet.allOf(ErrorType.class); + private ObjectIdSet skipList; private boolean allowInvalidPersonIdent; private boolean windows; private boolean macosx; + private final List gitsubmodules = new ArrayList<>(); + + /** + * Enable accepting specific malformed (but not horribly broken) objects. + * + * @param objects + * collection of object names known to be broken in a non-fatal + * way that should be ignored by the checker. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) { + skipList = objects; + return this; + } + + /** + * Configure error types to be ignored across all objects. + * + * @param ids + * error types to ignore. The caller's set is copied. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setIgnore(@Nullable Set ids) { + errors = EnumSet.allOf(ErrorType.class); + if (ids != null) { + errors.removeAll(ids); + } + return this; + } + + /** + * Add message type to be ignored across all objects. + * + * @param id + * error type to ignore. + * @param ignore + * true to ignore this error; false to treat the error as an + * error and throw. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setIgnore(ErrorType id, boolean ignore) { + if (ignore) { + errors.remove(id); + } else { + errors.add(id); + } + return this; + } + /** * Enable accepting leading zero mode in tree entries. *

    @@ -116,14 +264,15 @@ * tree entries. This is technically incorrect but gracefully allowed by * git-core. JGit rejects such trees by default, but may need to accept * them on broken histories. + *

    + * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}. * * @param allow allow leading zero mode. * @return {@code this}. * @since 3.4 */ public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) { - allowZeroMode = allow; - return this; + return setIgnore(ZERO_PADDED_FILEMODE, allow); } /** @@ -176,70 +325,131 @@ * * @param objType * type of the object. Must be a valid object type code in - * {@link Constants}. + * {@link org.eclipse.jgit.lib.Constants}. * @param raw * the raw data which comprises the object. This should be in the * canonical format (that is the format used to generate the * ObjectId of the object). The array is never modified. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * if an error is identified. */ - public void check(final int objType, final byte[] raw) + public void check(int objType, byte[] raw) + throws CorruptObjectException { + check(idFor(objType, raw), objType, raw); + } + + /** + * Check an object for parsing errors. + * + * @param id + * identify of the object being checked. + * @param objType + * type of the object. Must be a valid object type code in + * {@link org.eclipse.jgit.lib.Constants}. + * @param raw + * the raw data which comprises the object. This should be in the + * canonical format (that is the format used to generate the + * ObjectId of the object). The array is never modified. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * if an error is identified. + * @since 4.2 + */ + public void check(@Nullable AnyObjectId id, int objType, byte[] raw) throws CorruptObjectException { switch (objType) { - case Constants.OBJ_COMMIT: - checkCommit(raw); + case OBJ_COMMIT: + checkCommit(id, raw); break; - case Constants.OBJ_TAG: - checkTag(raw); + case OBJ_TAG: + checkTag(id, raw); break; - case Constants.OBJ_TREE: - checkTree(raw); + case OBJ_TREE: + checkTree(id, raw); break; - case Constants.OBJ_BLOB: - checkBlob(raw); + case OBJ_BLOB: + BlobObjectChecker checker = newBlobObjectChecker(); + if (checker == null) { + checkBlob(raw); + } else { + checker.update(raw, 0, raw.length); + checker.endBlob(id); + } break; default: - throw new CorruptObjectException(MessageFormat.format( + report(UNKNOWN_TYPE, id, MessageFormat.format( JGitText.get().corruptObjectInvalidType2, Integer.valueOf(objType))); } } - private int id(final byte[] raw, final int ptr) { + private boolean checkId(byte[] raw) { + int p = bufPtr.value; try { - tempId.fromString(raw, ptr); - return ptr + Constants.OBJECT_ID_STRING_LENGTH; + tempId.fromString(raw, p); } catch (IllegalArgumentException e) { - return -1; + bufPtr.value = nextLF(raw, p); + return false; } + + p += OBJECT_ID_STRING_LENGTH; + if (raw[p] == '\n') { + bufPtr.value = p + 1; + return true; + } + bufPtr.value = nextLF(raw, p); + return false; } - private int personIdent(final byte[] raw, int ptr) { - if (allowInvalidPersonIdent) - return nextLF(raw, ptr) - 1; + private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id) + throws CorruptObjectException { + if (allowInvalidPersonIdent) { + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } - final int emailB = nextLF(raw, ptr, '<'); - if (emailB == ptr || raw[emailB - 1] != '<') - return -1; + final int emailB = nextLF(raw, bufPtr.value, '<'); + if (emailB == bufPtr.value || raw[emailB - 1] != '<') { + report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } final int emailE = nextLF(raw, emailB, '>'); - if (emailE == emailB || raw[emailE - 1] != '>') - return -1; - if (emailE == raw.length || raw[emailE] != ' ') - return -1; - - parseBase10(raw, emailE + 1, ptrout); // when - ptr = ptrout.value; - if (emailE + 1 == ptr) - return -1; - if (ptr == raw.length || raw[ptr] != ' ') - return -1; - - parseBase10(raw, ptr + 1, ptrout); // tz offset - if (ptr + 1 == ptrout.value) - return -1; - return ptrout.value; + if (emailE == emailB || raw[emailE - 1] != '>') { + report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } + if (emailE == raw.length || raw[emailE] != ' ') { + report(MISSING_SPACE_BEFORE_DATE, id, + JGitText.get().corruptObjectBadDate); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } + + parseBase10(raw, emailE + 1, bufPtr); // when + if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length + || raw[bufPtr.value] != ' ') { + report(BAD_DATE, id, JGitText.get().corruptObjectBadDate); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } + + int p = bufPtr.value + 1; + parseBase10(raw, p, bufPtr); // tz offset + if (p == bufPtr.value) { + report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } + + p = bufPtr.value; + if (raw[p] == '\n') { + bufPtr.value = p + 1; + } else { + report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); + bufPtr.value = nextLF(raw, p); + } } /** @@ -247,39 +457,53 @@ * * @param raw * the commit data. The array is never modified. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * if any error was detected. */ - public void checkCommit(final byte[] raw) throws CorruptObjectException { - int ptr = 0; + public void checkCommit(byte[] raw) throws CorruptObjectException { + checkCommit(idFor(OBJ_COMMIT, raw), raw); + } - if ((ptr = match(raw, ptr, tree)) < 0) - throw new CorruptObjectException( - JGitText.get().corruptObjectNotreeHeader); - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidTree); + /** + * Check a commit for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the commit data. The array is never modified. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * if any error was detected. + * @since 4.2 + */ + public void checkCommit(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { + bufPtr.value = 0; - while (match(raw, ptr, parent) >= 0) { - ptr += parent.length; - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( + if (!match(raw, tree)) { + report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader); + } else if (!checkId(raw)) { + report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree); + } + + while (match(raw, parent)) { + if (!checkId(raw)) { + report(BAD_PARENT_SHA1, id, JGitText.get().corruptObjectInvalidParent); + } } - if ((ptr = match(raw, ptr, author)) < 0) - throw new CorruptObjectException( - JGitText.get().corruptObjectNoAuthor); - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidAuthor); + if (match(raw, author)) { + checkPersonIdent(raw, id); + } else { + report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor); + } - if ((ptr = match(raw, ptr, committer)) < 0) - throw new CorruptObjectException( + if (match(raw, committer)) { + checkPersonIdent(raw, id); + } else { + report(MISSING_COMMITTER, id, JGitText.get().corruptObjectNoCommitter); - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidCommitter); + } } /** @@ -287,53 +511,50 @@ * * @param raw * the tag data. The array is never modified. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * if any error was detected. */ - public void checkTag(final byte[] raw) throws CorruptObjectException { - int ptr = 0; + public void checkTag(byte[] raw) throws CorruptObjectException { + checkTag(idFor(OBJ_TAG, raw), raw); + } - if ((ptr = match(raw, ptr, object)) < 0) - throw new CorruptObjectException( + /** + * Check an annotated tag for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the tag data. The array is never modified. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * if any error was detected. + * @since 4.2 + */ + public void checkTag(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { + bufPtr.value = 0; + if (!match(raw, object)) { + report(MISSING_OBJECT, id, JGitText.get().corruptObjectNoObjectHeader); - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( + } else if (!checkId(raw)) { + report(BAD_OBJECT_SHA1, id, JGitText.get().corruptObjectInvalidObject); + } - if ((ptr = match(raw, ptr, type)) < 0) - throw new CorruptObjectException( + if (!match(raw, type)) { + report(MISSING_TYPE_ENTRY, id, JGitText.get().corruptObjectNoTypeHeader); - ptr = nextLF(raw, ptr); + } + bufPtr.value = nextLF(raw, bufPtr.value); - if ((ptr = match(raw, ptr, tag)) < 0) - throw new CorruptObjectException( + if (!match(raw, tag)) { + report(MISSING_TAG_ENTRY, id, JGitText.get().corruptObjectNoTagHeader); - ptr = nextLF(raw, ptr); - - if ((ptr = match(raw, ptr, tagger)) > 0) { - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidTagger); } - } + bufPtr.value = nextLF(raw, bufPtr.value); - private static int lastPathChar(final int mode) { - return FileMode.TREE.equals(mode) ? '/' : '\0'; - } - - private static int pathCompare(final byte[] raw, int aPos, final int aEnd, - final int aMode, int bPos, final int bEnd, final int bMode) { - while (aPos < aEnd && bPos < bEnd) { - final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff); - if (cmp != 0) - return cmp; + if (match(raw, tagger)) { + checkPersonIdent(raw, id); } - - if (aPos < aEnd) - return (raw[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(aMode) - (raw[bPos] & 0xff); - return 0; } private static boolean duplicateName(final byte[] raw, @@ -363,8 +584,9 @@ if (nextNamePos + 1 == nextPtr) return false; - final int cmp = pathCompare(raw, thisNamePos, thisNameEnd, - FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode); + int cmp = compareSameName( + raw, thisNamePos, thisNameEnd, + raw, nextNamePos, nextPtr - 1, nextMode); if (cmp < 0) return false; else if (cmp == 0) @@ -379,88 +601,126 @@ * * @param raw * the raw tree data. The array is never modified. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * if any error was detected. + */ + public void checkTree(byte[] raw) throws CorruptObjectException { + checkTree(idFor(OBJ_TREE, raw), raw); + } + + /** + * Check a canonical formatted tree for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the raw tree data. The array is never modified. + * @throws org.eclipse.jgit.errors.CorruptObjectException * if any error was detected. + * @since 4.2 */ - public void checkTree(final byte[] raw) throws CorruptObjectException { + public void checkTree(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { final int sz = raw.length; int ptr = 0; int lastNameB = 0, lastNameE = 0, lastMode = 0; Set normalized = windows || macosx - ? new HashSet() + ? new HashSet<>() : null; while (ptr < sz) { int thisMode = 0; for (;;) { - if (ptr == sz) + if (ptr == sz) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInMode); + } final byte c = raw[ptr++]; if (' ' == c) break; - if (c < '0' || c > '7') + if (c < '0' || c > '7') { throw new CorruptObjectException( JGitText.get().corruptObjectInvalidModeChar); - if (thisMode == 0 && c == '0' && !allowZeroMode) - throw new CorruptObjectException( + } + if (thisMode == 0 && c == '0') { + report(ZERO_PADDED_FILEMODE, id, JGitText.get().corruptObjectInvalidModeStartsZero); + } thisMode <<= 3; thisMode += c - '0'; } - if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD) + if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) { throw new CorruptObjectException(MessageFormat.format( JGitText.get().corruptObjectInvalidMode2, Integer.valueOf(thisMode))); + } final int thisNameB = ptr; - ptr = scanPathSegment(raw, ptr, sz); - if (ptr == sz || raw[ptr] != 0) + ptr = scanPathSegment(raw, ptr, sz, id); + if (ptr == sz || raw[ptr] != 0) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInName); - checkPathSegment2(raw, thisNameB, ptr); + } + checkPathSegment2(raw, thisNameB, ptr, id); if (normalized != null) { - if (!normalized.add(normalize(raw, thisNameB, ptr))) - throw new CorruptObjectException( + if (!normalized.add(normalize(raw, thisNameB, ptr))) { + report(DUPLICATE_ENTRIES, id, JGitText.get().corruptObjectDuplicateEntryNames); - } else if (duplicateName(raw, thisNameB, ptr)) - throw new CorruptObjectException( + } + } else if (duplicateName(raw, thisNameB, ptr)) { + report(DUPLICATE_ENTRIES, id, JGitText.get().corruptObjectDuplicateEntryNames); + } if (lastNameB != 0) { - final int cmp = pathCompare(raw, lastNameB, lastNameE, - lastMode, thisNameB, ptr, thisMode); - if (cmp > 0) - throw new CorruptObjectException( + int cmp = compare( + raw, lastNameB, lastNameE, lastMode, + raw, thisNameB, ptr, thisMode); + if (cmp > 0) { + report(TREE_NOT_SORTED, id, JGitText.get().corruptObjectIncorrectSorting); + } } lastNameB = thisNameB; lastNameE = ptr; lastMode = thisMode; - ptr += 1 + Constants.OBJECT_ID_LENGTH; - if (ptr > sz) + ptr += 1 + OBJECT_ID_LENGTH; + if (ptr > sz) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInObjectId); + } + + if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) { + report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId); + } + + if (id != null && isGitmodules(raw, lastNameB, lastNameE, id)) { + ObjectId blob = ObjectId.fromRaw(raw, ptr - OBJECT_ID_LENGTH); + gitsubmodules.add(new GitmoduleEntry(id, blob)); + } } } - private int scanPathSegment(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private int scanPathSegment(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { for (; ptr < end; ptr++) { byte c = raw[ptr]; - if (c == 0) + if (c == 0) { return ptr; - if (c == '/') - throw new CorruptObjectException( + } + if (c == '/') { + report(FULL_PATHNAME, id, JGitText.get().corruptObjectNameContainsSlash); + } if (windows && isInvalidOnWindows(c)) { - if (c > 31) + if (c > 31) { throw new CorruptObjectException(String.format( JGitText.get().corruptObjectNameContainsChar, Byte.valueOf(c))); + } throw new CorruptObjectException(String.format( JGitText.get().corruptObjectNameContainsByte, Integer.valueOf(c & 0xff))); @@ -469,14 +729,37 @@ return ptr; } + @Nullable + private ObjectId idFor(int objType, byte[] raw) { + if (skipList != null) { + try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { + return fmt.idFor(objType, raw); + } + } + return null; + } + + private void report(@NonNull ErrorType err, @Nullable AnyObjectId id, + String why) throws CorruptObjectException { + if (errors.contains(err) + && (id == null || skipList == null || !skipList.contains(id))) { + if (id != null) { + throw new CorruptObjectException(err, id, why); + } + throw new CorruptObjectException(why); + } + } + /** * Check tree path entry for validity. *

    - * Unlike {@link #checkPathSegment(byte[], int, int)}, this version - * scans a multi-directory path string such as {@code "src/main.c"}. + * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a + * multi-directory path string such as {@code "src/main.c"}. * - * @param path path string to scan. - * @throws CorruptObjectException path is invalid. + * @param path + * path string to scan. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * path is invalid. * @since 3.6 */ public void checkPath(String path) throws CorruptObjectException { @@ -487,13 +770,17 @@ /** * Check tree path entry for validity. *

    - * Unlike {@link #checkPathSegment(byte[], int, int)}, this version - * scans a multi-directory path string such as {@code "src/main.c"}. + * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a + * multi-directory path string such as {@code "src/main.c"}. * - * @param raw buffer to scan. - * @param ptr offset to first byte of the name. - * @param end offset to one past last byte of name. - * @throws CorruptObjectException path is invalid. + * @param raw + * buffer to scan. + * @param ptr + * offset to first byte of the name. + * @param end + * offset to one past last byte of name. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * path is invalid. * @since 3.6 */ public void checkPath(byte[] raw, int ptr, int end) @@ -511,81 +798,93 @@ /** * Check tree path entry for validity. * - * @param raw buffer to scan. - * @param ptr offset to first byte of the name. - * @param end offset to one past last byte of name. - * @throws CorruptObjectException name is invalid. + * @param raw + * buffer to scan. + * @param ptr + * offset to first byte of the name. + * @param end + * offset to one past last byte of name. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * name is invalid. * @since 3.4 */ public void checkPathSegment(byte[] raw, int ptr, int end) throws CorruptObjectException { - int e = scanPathSegment(raw, ptr, end); + int e = scanPathSegment(raw, ptr, end, null); if (e < end && raw[e] == 0) throw new CorruptObjectException( JGitText.get().corruptObjectNameContainsNullByte); - checkPathSegment2(raw, ptr, end); + checkPathSegment2(raw, ptr, end, null); } - private void checkPathSegment2(byte[] raw, int ptr, int end) - throws CorruptObjectException { - if (ptr == end) - throw new CorruptObjectException( - JGitText.get().corruptObjectNameZeroLength); + private void checkPathSegment2(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + if (ptr == end) { + report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength); + return; + } + if (raw[ptr] == '.') { switch (end - ptr) { case 1: - throw new CorruptObjectException( - JGitText.get().corruptObjectNameDot); + report(HAS_DOT, id, JGitText.get().corruptObjectNameDot); + break; case 2: - if (raw[ptr + 1] == '.') - throw new CorruptObjectException( + if (raw[ptr + 1] == '.') { + report(HAS_DOTDOT, id, JGitText.get().corruptObjectNameDotDot); + } break; case 4: - if (isGit(raw, ptr + 1)) - throw new CorruptObjectException(String.format( + if (isGit(raw, ptr + 1)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); + } break; default: - if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) - throw new CorruptObjectException(String.format( + if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); + } } } else if (isGitTilde1(raw, ptr, end)) { - throw new CorruptObjectException(String.format( + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); } - - if (macosx && isMacHFSGit(raw, ptr, end)) - throw new CorruptObjectException(String.format( + if (macosx && isMacHFSGit(raw, ptr, end, id)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidNameIgnorableUnicode, RawParseUtils.decode(raw, ptr, end))); + } if (windows) { // Windows ignores space and dot at end of file name. - if (raw[end - 1] == ' ' || raw[end - 1] == '.') - throw new CorruptObjectException(String.format( + if (raw[end - 1] == ' ' || raw[end - 1] == '.') { + report(WIN32_BAD_NAME, id, String.format( JGitText.get().corruptObjectInvalidNameEnd, Character.valueOf(((char) raw[end - 1])))); - if (end - ptr >= 3) - checkNotWindowsDevice(raw, ptr, end); + } + if (end - ptr >= 3) { + checkNotWindowsDevice(raw, ptr, end, id); + } } } // Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters // to ".git" therefore we should prevent such names - private static boolean isMacHFSGit(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private boolean isMacHFSPath(byte[] raw, int ptr, int end, byte[] path, + @Nullable AnyObjectId id) throws CorruptObjectException { boolean ignorable = false; - byte[] git = new byte[] { '.', 'g', 'i', 't' }; int g = 0; while (ptr < end) { switch (raw[ptr]) { case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192 - checkTruncatedIgnorableUTF8(raw, ptr, end); + if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { + return false; + } switch (raw[ptr + 1]) { case (byte) 0x80: switch (raw[ptr + 2]) { @@ -622,7 +921,9 @@ return false; } case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024 - checkTruncatedIgnorableUTF8(raw, ptr, end); + if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { + return false; + } // U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE if ((raw[ptr + 1] == (byte) 0xbb) && (raw[ptr + 2] == (byte) 0xbf)) { @@ -632,23 +933,40 @@ } return false; default: - if (g == 4) + if (g == path.length) { return false; - if (raw[ptr++] != git[g++]) + } + if (toLower(raw[ptr++]) != path[g++]) { return false; + } } } - if (g == 4 && ignorable) + if (g == path.length && ignorable) { return true; + } return false; } - private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end) - throws CorruptObjectException { - if ((ptr + 2) >= end) - throw new CorruptObjectException(MessageFormat.format( + private boolean isMacHFSGit(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + byte[] git = new byte[] { '.', 'g', 'i', 't' }; + return isMacHFSPath(raw, ptr, end, git, id); + } + + private boolean isMacHFSGitmodules(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + return isMacHFSPath(raw, ptr, end, dotGitmodules, id); + } + + private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + if ((ptr + 2) >= end) { + report(BAD_UTF8, id, MessageFormat.format( JGitText.get().corruptObjectInvalidNameInvalidUtf8, toHexString(raw, ptr, end))); + return false; + } + return true; } private static String toHexString(byte[] raw, int ptr, int end) { @@ -658,33 +976,36 @@ return b.toString(); } - private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private void checkNotWindowsDevice(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { switch (toLower(raw[ptr])) { case 'a': // AUX if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'u' && toLower(raw[ptr + 2]) == 'x' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameAux); + } break; case 'c': // CON, COM[1-9] if (end - ptr >= 3 && toLower(raw[ptr + 2]) == 'n' && toLower(raw[ptr + 1]) == 'o' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameCon); + } if (end - ptr >= 4 && toLower(raw[ptr + 2]) == 'm' && toLower(raw[ptr + 1]) == 'o' && isPositiveDigit(raw[ptr + 3]) - && (end - ptr == 4 || raw[ptr + 4] == '.')) - throw new CorruptObjectException(String.format( + && (end - ptr == 4 || raw[ptr + 4] == '.')) { + report(WIN32_BAD_NAME, id, String.format( JGitText.get().corruptObjectInvalidNameCom, Character.valueOf(((char) raw[ptr + 3])))); + } break; case 'l': // LPT[1-9] @@ -692,28 +1013,31 @@ && toLower(raw[ptr + 1]) == 'p' && toLower(raw[ptr + 2]) == 't' && isPositiveDigit(raw[ptr + 3]) - && (end - ptr == 4 || raw[ptr + 4] == '.')) - throw new CorruptObjectException(String.format( + && (end - ptr == 4 || raw[ptr + 4] == '.')) { + report(WIN32_BAD_NAME, id, String.format( JGitText.get().corruptObjectInvalidNameLpt, Character.valueOf(((char) raw[ptr + 3])))); + } break; case 'n': // NUL if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'u' && toLower(raw[ptr + 2]) == 'l' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameNul); + } break; case 'p': // PRN if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'r' && toLower(raw[ptr + 2]) == 'n' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNamePrn); + } break; } } @@ -740,6 +1064,104 @@ && toLower(buf[p + 2]) == 't'; } + /** + * Check if the filename contained in buf[start:end] could be read as a + * .gitmodules file when checked out to the working directory. + * + * This ought to be a simple comparison, but some filesystems have peculiar + * rules for normalizing filenames: + * + * NTFS has backward-compatibility support for 8.3 synonyms of long file + * names (see + * https://web.archive.org/web/20160318181041/https://usn.pw/blog/gen/2015/06/09/filenames/ + * for details). NTFS is also case-insensitive. + * + * MacOS's HFS+ folds away ignorable Unicode characters in addition to case + * folding. + * + * @param buf + * byte array to decode + * @param start + * position where a supposed filename is starting + * @param end + * position where a supposed filename is ending + * @param id + * object id for error reporting + * + * @return true if the filename in buf could be a ".gitmodules" file + * @throws CorruptObjectException + */ + private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id) + throws CorruptObjectException { + // Simple cases first. + if (end - start < 8) { + return false; + } + return (end - start == dotGitmodules.length + && RawParseUtils.match(buf, start, dotGitmodules) != -1) + || (macosx && isMacHFSGitmodules(buf, start, end, id)) + || (windows && isNTFSGitmodules(buf, start, end)); + } + + private boolean matchLowerCase(byte[] b, int ptr, byte[] src) { + if (ptr + src.length > b.length) { + return false; + } + for (int i = 0; i < src.length; i++, ptr++) { + if (toLower(b[ptr]) != src[i]) { + return false; + } + } + return true; + } + + // .gitmodules, case-insensitive, or an 8.3 abbreviation of the same. + private boolean isNTFSGitmodules(byte[] buf, int start, int end) { + if (end - start == 11) { + return matchLowerCase(buf, start, dotGitmodules); + } + + if (end - start != 8) { + return false; + } + + // "gitmod" or a prefix of "gi7eba", followed by... + byte[] gitmod = new byte[]{'g', 'i', 't', 'm', 'o', 'd', '~'}; + if (matchLowerCase(buf, start, gitmod)) { + start += 6; + } else { + byte[] gi7eba = new byte[]{'g', 'i', '7', 'e', 'b', 'a'}; + for (int i = 0; i < gi7eba.length; i++, start++) { + byte c = (byte) toLower(buf[start]); + if (c == '~') { + break; + } + if (c != gi7eba[i]) { + return false; + } + } + } + + // ... ~ and a number + if (end - start < 2) { + return false; + } + if (buf[start] != '~') { + return false; + } + start++; + if (buf[start] < '1' || buf[start] > '9') { + return false; + } + start++; + for (; start != end; start++) { + if (buf[start] < '0' || buf[start] > '9') { + return false; + } + } + return true; + } + private static boolean isGitTilde1(byte[] buf, int p, int end) { if (end - p != 5) return false; @@ -766,6 +1188,15 @@ return false; } + private boolean match(byte[] b, byte[] src) { + int r = RawParseUtils.match(b, bufPtr.value, src); + if (r < 0) { + return false; + } + bufPtr.value = r; + return true; + } + private static char toLower(byte b) { if ('A' <= b && b <= 'Z') return (char) (b + ('a' - 'A')); @@ -777,11 +1208,26 @@ } /** + * Create a new {@link org.eclipse.jgit.lib.BlobObjectChecker}. + * + * @return new BlobObjectChecker or null if it's not provided. + * @since 4.9 + */ + @Nullable + public BlobObjectChecker newBlobObjectChecker() { + return null; + } + + /** * Check a blob for errors. * + *

    + * This may not be called from PackParser in some cases. Use + * {@link #newBlobObjectChecker} instead. + * * @param raw * the blob data. The array is never modified. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * if any error was detected. */ public void checkBlob(final byte[] raw) throws CorruptObjectException { @@ -790,58 +1236,19 @@ private String normalize(byte[] raw, int ptr, int end) { String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US); - return macosx ? Normalizer.normalize(n) : n; + return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n; } - private static class Normalizer { - // TODO Simplify invocation to Normalizer after dropping Java 5. - private static final Method normalize; - private static final Object nfc; - static { - Method method; - Object formNfc; - try { - Class formClazz = Class.forName("java.text.Normalizer$Form"); //$NON-NLS-1$ - formNfc = formClazz.getField("NFC").get(null); //$NON-NLS-1$ - method = Class.forName("java.text.Normalizer") //$NON-NLS-1$ - .getMethod("normalize", CharSequence.class, formClazz); //$NON-NLS-1$ - } catch (ClassNotFoundException e) { - method = null; - formNfc = null; - } catch (NoSuchFieldException e) { - method = null; - formNfc = null; - } catch (NoSuchMethodException e) { - method = null; - formNfc = null; - } catch (SecurityException e) { - method = null; - formNfc = null; - } catch (IllegalArgumentException e) { - method = null; - formNfc = null; - } catch (IllegalAccessException e) { - method = null; - formNfc = null; - } - normalize = method; - nfc = formNfc; - } - - static String normalize(String in) { - if (normalize == null) - return in; - try { - return (String) normalize.invoke(null, in, nfc); - } catch (IllegalAccessException e) { - return in; - } catch (InvocationTargetException e) { - if (e.getCause() instanceof RuntimeException) - throw (RuntimeException) e.getCause(); - if (e.getCause() instanceof Error) - throw (Error) e.getCause(); - return in; - } - } + /** + * Get the list of ".gitmodules" files found in the pack. For each, report + * its blob id (e.g. to validate its contents) and the tree where it was + * found (e.g. to check if it is in the root) + * + * @return List of pairs of ids {@literal }. + * + * @since 4.7.5 + */ + public List getGitsubmodules() { + return gitsubmodules; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,10 +52,12 @@ * Abstraction of arbitrary object storage. *

    * An object database stores one or more Git objects, indexed by their unique - * {@link ObjectId}. + * {@link org.eclipse.jgit.lib.ObjectId}. */ public abstract class ObjectDatabase { - /** Initialize a new database instance for access. */ + /** + * Initialize a new database instance for access. + */ protected ObjectDatabase() { // Protected to force extension. } @@ -73,7 +75,7 @@ /** * Initialize a new object database at this location. * - * @throws IOException + * @throws java.io.IOException * the database could not be created. */ public void create() throws IOException { @@ -116,7 +118,7 @@ * @param objectId * identity of the object to test for existence of. * @return true if the specified object is stored in this database. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public boolean has(final AnyObjectId objectId) throws IOException { @@ -133,10 +135,10 @@ * * @param objectId * identity of the object to open. - * @return a {@link ObjectLoader} for accessing the object. + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public ObjectLoader open(final AnyObjectId objectId) @@ -154,16 +156,17 @@ * identity of the object to open. * @param typeHint * hint about the type of object being requested, e.g. - * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if - * the object type is not known, or does not matter to the - * caller. - * @return a {@link ObjectLoader} for accessing the object. - * @throws MissingObjectException + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}; + * {@link org.eclipse.jgit.lib.ObjectReader#OBJ_ANY} if the + * object type is not known, or does not matter to the caller. + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the + * object. + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does * not match typeHint. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public ObjectLoader open(AnyObjectId objectId, int typeHint) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,15 +44,15 @@ package org.eclipse.jgit.lib; -import org.eclipse.jgit.errors.InvalidObjectIdException; -import org.eclipse.jgit.util.NB; -import org.eclipse.jgit.util.RawParseUtils; - import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import org.eclipse.jgit.errors.InvalidObjectIdException; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + /** * A SHA-1 abstraction. */ @@ -248,8 +248,22 @@ } } - ObjectId(final int new_1, final int new_2, final int new_3, - final int new_4, final int new_5) { + /** + * Construct an ObjectId from 160 bits provided in 5 words. + * + * @param new_1 + * an int + * @param new_2 + * an int + * @param new_3 + * an int + * @param new_4 + * an int + * @param new_5 + * an int + * @since 4.7 + */ + public ObjectId(int new_1, int new_2, int new_3, int new_4, int new_5) { w1 = new_1; w2 = new_2; w3 = new_3; @@ -275,6 +289,7 @@ w5 = src.w5; } + /** {@inheritDoc} */ @Override public ObjectId toObjectId() { return this; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,10 +48,12 @@ import java.util.NoSuchElementException; /** - * Fast, efficient map for {@link ObjectId} subclasses in only one map. + * Fast, efficient map for {@link org.eclipse.jgit.lib.ObjectId} subclasses in + * only one map. *

    * To use this map type, applications must have their entry value type extend - * from {@link ObjectIdOwnerMap.Entry}, which itself extends from ObjectId. + * from {@link org.eclipse.jgit.lib.ObjectIdOwnerMap.Entry}, which itself + * extends from ObjectId. *

    * Object instances may only be stored in ONE ObjectIdOwnerMap. This * restriction exists because the map stores internal map state within each @@ -59,16 +61,17 @@ * could corrupt one or both map's internal state. *

    * If an object instance must be in more than one map, applications may use - * ObjectIdOwnerMap for one of the maps, and {@link ObjectIdSubclassMap} for the - * other map(s). It is encouraged to use ObjectIdOwnerMap for the map that is - * accessed most often, as this implementation runs faster than the more general - * ObjectIdSubclassMap implementation. + * ObjectIdOwnerMap for one of the maps, and + * {@link org.eclipse.jgit.lib.ObjectIdSubclassMap} for the other map(s). It is + * encouraged to use ObjectIdOwnerMap for the map that is accessed most often, + * as this implementation runs faster than the more general ObjectIdSubclassMap + * implementation. * * @param * type of subclass of ObjectId that will be stored in the map. */ -public class ObjectIdOwnerMap implements - Iterable { +public class ObjectIdOwnerMap + implements Iterable, ObjectIdSet { /** Size of the initial directory, will grow as necessary. */ private static final int INITIAL_DIRECTORY = 1024; @@ -83,21 +86,23 @@ * The low {@link #bits} of the SHA-1 are used to select the segment from * this directory. Each segment is constant sized at 2^SEGMENT_BITS. */ - private V[][] directory; + V[][] directory; /** Total number of objects in this map. */ - private int size; + int size; /** The map doubles in capacity when {@link #size} reaches this target. */ private int grow; /** Number of low bits used to form the index into {@link #directory}. */ - private int bits; + int bits; /** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */ private int mask; - /** Create an empty map. */ + /** + * Create an empty map. + */ @SuppressWarnings("unchecked") public ObjectIdOwnerMap() { bits = 0; @@ -108,7 +113,9 @@ directory[0] = newSegment(); } - /** Remove all entries from this map. */ + /** + * Remove all entries from this map. + */ public void clear() { size = 0; @@ -137,12 +144,11 @@ } /** + * {@inheritDoc} + *

    * Returns true if this map contains the specified object. - * - * @param toFind - * object to find. - * @return true if the mapping exists for this object; false otherwise. */ + @Override public boolean contains(final AnyObjectId toFind) { return get(toFind) != null; } @@ -156,8 +162,6 @@ * * @param newValue * the object to store. - * @param - * type of instance to store. */ public void add(final Q newValue) { if (++size == grow) @@ -188,8 +192,6 @@ * @return {@code newValue} if stored, or the prior value already stored and * that would have been returned had the caller used * {@code get(newValue)} first. - * @param - * type of instance to store. */ @SuppressWarnings("unchecked") public V addIfAbsent(final Q newValue) { @@ -209,30 +211,39 @@ return newValue; } - /** @return number of objects in this map. */ + /** + * Get number of objects in this map. + * + * @return number of objects in this map. + */ public int size() { return size; } - /** @return true if {@link #size()} is 0. */ + /** + * Whether this map is empty + * + * @return true if {@link #size()} is 0. + */ public boolean isEmpty() { return size == 0; } + /** {@inheritDoc} */ + @Override public Iterator iterator() { return new Iterator() { private int found; - private int dirIdx; - private int tblIdx; - private V next; + @Override public boolean hasNext() { return found < size; } + @Override public V next() { if (next != null) return found(next); @@ -261,6 +272,7 @@ return v; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -341,7 +353,7 @@ /** Type of entry stored in the {@link ObjectIdOwnerMap}. */ public static abstract class Entry extends ObjectId { - Entry next; + transient Entry next; /** * Initialize this entry with a specific ObjectId. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,13 @@ package org.eclipse.jgit.lib; -/** A {@link Ref} that points directly at an {@link ObjectId}. */ +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + +/** + * A {@link org.eclipse.jgit.lib.Ref} that points directly at an + * {@link org.eclipse.jgit.lib.ObjectId}. + */ public abstract class ObjectIdRef implements Ref { /** Any reference whose peeled value is not yet known. */ public static class Unpeeled extends ObjectIdRef { @@ -56,17 +62,21 @@ * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref - * that does not exist yet. + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. */ - public Unpeeled(Storage st, String name, ObjectId id) { + public Unpeeled(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { super(st, name, id); } + @Override + @Nullable public ObjectId getPeeledObjectId() { return null; } + @Override public boolean isPeeled() { return false; } @@ -88,15 +98,19 @@ * @param p * the first non-tag object that tag {@code id} points to. */ - public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) { + public PeeledTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id, @NonNull ObjectId p) { super(st, name, id); peeledObjectId = p; } + @Override + @NonNull public ObjectId getPeeledObjectId() { return peeledObjectId; } + @Override public boolean isPeeled() { return true; } @@ -112,17 +126,21 @@ * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref - * that does not exist yet. + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. */ - public PeeledNonTag(Storage st, String name, ObjectId id) { + public PeeledNonTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { super(st, name, id); } + @Override + @Nullable public ObjectId getPeeledObjectId() { return null; } + @Override public boolean isPeeled() { return true; } @@ -142,39 +160,59 @@ * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref that - * does not exist yet. + * current value of the ref. May be {@code null} to indicate a + * ref that does not exist yet. */ - protected ObjectIdRef(Storage st, String name, ObjectId id) { + protected ObjectIdRef(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { this.name = name; this.storage = st; this.objectId = id; } + /** {@inheritDoc} */ + @Override + @NonNull public String getName() { return name; } + /** {@inheritDoc} */ + @Override public boolean isSymbolic() { return false; } + /** {@inheritDoc} */ + @Override + @NonNull public Ref getLeaf() { return this; } + /** {@inheritDoc} */ + @Override + @NonNull public Ref getTarget() { return this; } + /** {@inheritDoc} */ + @Override + @Nullable public ObjectId getObjectId() { return objectId; } + /** {@inheritDoc} */ + @Override + @NonNull public Storage getStorage() { return storage; } + /** {@inheritDoc} */ + @NonNull @Override public String toString() { StringBuilder r = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSerializer.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSerializer.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSerializer.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSerializer.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2009, The Android Open Source Project + * Copyright (C) 2009, Shawn O. Pearce + * Copyright (C) 2018, David Pursehouse + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.util.IO; + +/** + * Helper to serialize {@link ObjectId} instances. {@link ObjectId} is already + * serializable, but this class provides methods to handle null and non-null + * instances. + * + * @since 4.11 + */ +public class ObjectIdSerializer { + /* + * Marker to indicate a null ObjectId instance. + */ + private static final byte NULL_MARKER = 0; + + /* + * Marker to indicate a non-null ObjectId instance. + */ + private static final byte NON_NULL_MARKER = 1; + + /** + * Write a possibly null {@link ObjectId} to the stream, using markers to + * differentiate null and non-null instances. + * + *

    + * If the id is non-null, writes a {@link #NON_NULL_MARKER} followed by the + * id's words. If it is null, writes a {@link #NULL_MARKER} and nothing + * else. + * + * @param out + * the output stream + * @param id + * the object id to serialize; may be null + * @throws IOException + * the stream writing failed + */ + public static void write(OutputStream out, @Nullable AnyObjectId id) + throws IOException { + if (id != null) { + out.write(NON_NULL_MARKER); + writeWithoutMarker(out, id); + } else { + out.write(NULL_MARKER); + } + } + + /** + * Write a non-null {@link ObjectId} to the stream. + * + * @param out + * the output stream + * @param id + * the object id to serialize; never null + * @throws IOException + * the stream writing failed + * @since 4.11 + */ + public static void writeWithoutMarker(OutputStream out, @NonNull AnyObjectId id) + throws IOException { + id.copyRawTo(out); + } + + /** + * Read a possibly null {@link ObjectId} from the stream. + * + * Reads the first byte of the stream, which is expected to be either + * {@link #NON_NULL_MARKER} or {@link #NULL_MARKER}. + * + * @param in + * the input stream + * @return the object id, or null + * @throws IOException + * there was an error reading the stream + */ + @Nullable + public static ObjectId read(InputStream in) throws IOException { + byte marker = (byte) in.read(); + switch (marker) { + case NULL_MARKER: + return null; + case NON_NULL_MARKER: + return readWithoutMarker(in); + default: + throw new IOException("Invalid flag before ObjectId: " + marker); //$NON-NLS-1$ + } + } + + /** + * Read a non-null {@link ObjectId} from the stream. + * + * @param in + * the input stream + * @return the object id; never null + * @throws IOException + * there was an error reading the stream + * @since 4.11 + */ + @NonNull + public static ObjectId readWithoutMarker(InputStream in) throws IOException { + final byte[] b = new byte[OBJECT_ID_LENGTH]; + IO.readFully(in, b, 0, OBJECT_ID_LENGTH); + return ObjectId.fromRaw(b); + } + + private ObjectIdSerializer() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,65 @@ +/* + * 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.lib; + +/** + * Simple set of ObjectIds. + *

    + * Usually backed by a read-only data structure such as + * {@link org.eclipse.jgit.internal.storage.file.PackIndex}. Mutable types like + * {@link org.eclipse.jgit.lib.ObjectIdOwnerMap} also implement the interface by + * checking keys. + * + * @since 4.2 + */ +public interface ObjectIdSet { + /** + * Returns true if the objectId is contained within the collection. + * + * @param objectId + * the objectId to find + * @return whether the collection contains the objectId. + */ + boolean contains(AnyObjectId objectId); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,34 +49,41 @@ import java.util.NoSuchElementException; /** - * Fast, efficient map specifically for {@link ObjectId} subclasses. + * Fast, efficient map specifically for {@link org.eclipse.jgit.lib.ObjectId} + * subclasses. *

    * This map provides an efficient translation from any ObjectId instance to a * cached subclass of ObjectId that has the same value. *

    - * If object instances are stored in only one map, {@link ObjectIdOwnerMap} is a - * more efficient implementation. + * If object instances are stored in only one map, + * {@link org.eclipse.jgit.lib.ObjectIdOwnerMap} is a more efficient + * implementation. * * @param * type of subclass of ObjectId that will be stored in the map. */ -public class ObjectIdSubclassMap implements Iterable { +public class ObjectIdSubclassMap + implements Iterable, ObjectIdSet { private static final int INITIAL_TABLE_SIZE = 2048; - private int size; + int size; private int grow; private int mask; - private V[] table; + V[] table; - /** Create an empty map. */ + /** + * Create an empty map. + */ public ObjectIdSubclassMap() { initTable(INITIAL_TABLE_SIZE); } - /** Remove all entries from this map. */ + /** + * Remove all entries from this map. + */ public void clear() { size = 0; initTable(INITIAL_TABLE_SIZE); @@ -104,12 +111,11 @@ } /** + * {@inheritDoc} + *

    * Returns true if this map contains the specified object. - * - * @param toFind - * object to find. - * @return true if the mapping exists for this object; false otherwise. */ + @Override public boolean contains(final AnyObjectId toFind) { return get(toFind) != null; } @@ -124,8 +130,6 @@ * * @param newValue * the object to store. - * @param - * type of instance to store. */ public void add(final Q newValue) { if (++size == grow) @@ -150,8 +154,6 @@ * @return {@code newValue} if stored, or the prior value already stored and * that would have been returned had the caller used * {@code get(newValue)} first. - * @param - * type of instance to store. */ public V addIfAbsent(final Q newValue) { final int msk = mask; @@ -175,27 +177,37 @@ } /** + * Get number of objects in map + * * @return number of objects in map */ public int size() { return size; } - /** @return true if {@link #size()} is 0. */ + /** + * Whether {@link #size()} is 0. + * + * @return true if {@link #size()} is 0. + */ public boolean isEmpty() { return size == 0; } + /** {@inheritDoc} */ + @Override public Iterator iterator() { return new Iterator() { private int found; private int i; + @Override public boolean hasNext() { return found < size; } + @Override public V next() { while (i < table.length) { final V v = table[i++]; @@ -207,6 +219,7 @@ throw new NoSuchElementException(); } + @Override public void remove() { throw new UnsupportedOperationException(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,10 +50,10 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.security.MessageDigest; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.PackParser; +import org.eclipse.jgit.util.sha1.SHA1; /** * Inserts objects into an existing {@code ObjectDatabase}. @@ -107,63 +107,85 @@ return delegate().buffer(); } + @Override public ObjectId idFor(int type, byte[] data) { return delegate().idFor(type, data); } + @Override public ObjectId idFor(int type, byte[] data, int off, int len) { return delegate().idFor(type, data, off, len); } + @Override public ObjectId idFor(int objectType, long length, InputStream in) throws IOException { return delegate().idFor(objectType, length, in); } + @Override public ObjectId idFor(TreeFormatter formatter) { return delegate().idFor(formatter); } + @Override public ObjectId insert(int type, byte[] data) throws IOException { return delegate().insert(type, data); } + @Override public ObjectId insert(int type, byte[] data, int off, int len) throws IOException { return delegate().insert(type, data, off, len); } + @Override public ObjectId insert(int objectType, long length, InputStream in) throws IOException { return delegate().insert(objectType, length, in); } + @Override public PackParser newPackParser(InputStream in) throws IOException { return delegate().newPackParser(in); } + @Override public ObjectReader newReader() { - return delegate().newReader(); + final ObjectReader dr = delegate().newReader(); + return new ObjectReader.Filter() { + @Override + protected ObjectReader delegate() { + return dr; + } + + @Override + public ObjectInserter getCreatedFromInserter() { + return ObjectInserter.Filter.this; + } + }; } + @Override public void flush() throws IOException { delegate().flush(); } + @Override public void close() { delegate().close(); } } - /** Digest to compute the name of an object. */ - private final MessageDigest digest; + private final SHA1 hasher = SHA1.newInstance(); /** Temporary working buffer for streaming data through. */ private byte[] tempBuffer; - /** Create a new inserter for a database. */ + /** + * Create a new inserter for a database. + */ protected ObjectInserter() { - digest = Constants.newMessageDigest(); } /** @@ -197,10 +219,14 @@ return b; } - /** @return digest to help compute an ObjectId */ - protected MessageDigest digest() { - digest.reset(); - return digest; + /** + * Compute digest to help compute an ObjectId + * + * @return digest to help compute an ObjectId + * @since 4.7 + */ + protected SHA1 digest() { + return hasher.reset(); } /** @@ -230,13 +256,13 @@ * @return the name of the object. */ public ObjectId idFor(int type, byte[] data, int off, int len) { - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); md.update(Constants.encodedTypeString(type)); md.update((byte) ' '); md.update(Constants.encodeASCII(len)); md.update((byte) 0); md.update(data, off, len); - return ObjectId.fromRaw(md.digest()); + return md.toObjectId(); } /** @@ -250,12 +276,12 @@ * stream providing the object content. The caller is responsible * for closing the stream. * @return the name of the object. - * @throws IOException + * @throws java.io.IOException * the source stream could not be read. */ public ObjectId idFor(int objectType, long length, InputStream in) throws IOException { - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); md.update(Constants.encodedTypeString(objectType)); md.update((byte) ' '); md.update(Constants.encodeASCII(length)); @@ -268,13 +294,14 @@ md.update(buf, 0, n); length -= n; } - return ObjectId.fromRaw(md.digest()); + return md.toObjectId(); } /** * Compute the ObjectId for the given tree without inserting it. * * @param formatter + * a {@link org.eclipse.jgit.lib.TreeFormatter} object. * @return the computed ObjectId */ public ObjectId idFor(TreeFormatter formatter) { @@ -287,7 +314,7 @@ * @param formatter * the formatter containing the proposed tree's data. * @return the name of the tree object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored. */ public final ObjectId insert(TreeFormatter formatter) throws IOException { @@ -303,7 +330,7 @@ * @param builder * the builder containing the proposed commit's data. * @return the name of the commit object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored. */ public final ObjectId insert(CommitBuilder builder) throws IOException { @@ -316,7 +343,7 @@ * @param builder * the builder containing the proposed tag's data. * @return the name of the tag object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored. */ public final ObjectId insert(TagBuilder builder) throws IOException { @@ -331,7 +358,7 @@ * @param data * complete content of the object. * @return the name of the object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored. */ public ObjectId insert(final int type, final byte[] data) @@ -351,7 +378,7 @@ * @param len * number of bytes to copy from {@code data}. * @return the name of the object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored. */ public ObjectId insert(int type, byte[] data, int off, int len) @@ -370,7 +397,7 @@ * stream providing the object content. The caller is responsible * for closing the stream. * @return the name of the object. - * @throws IOException + * @throws java.io.IOException * the object could not be stored, or the source stream could * not be read. */ @@ -384,7 +411,7 @@ * the input stream. The stream is not closed by the parser, and * must instead be closed by the caller once parsing is complete. * @return the pack parser. - * @throws IOException + * @throws java.io.IOException * the parser instance, which can be configured and then used to * parse objects into the ObjectDatabase. */ @@ -398,6 +425,16 @@ * visible to the repository. The returned reader should only be used from * the same thread as the inserter. Objects written by this inserter may not * be visible to {@code this.newReader().newReader()}. + *

    + * The returned reader should return this inserter instance from {@link + * ObjectReader#getCreatedFromInserter()}. + *

    + * Behavior is undefined if an insert method is called on the inserter in the + * middle of reading from an {@link ObjectStream} opened from this reader. For + * example, reading the remainder of the object may fail, or newly written + * data may even be corrupted. Interleaving whole object reads (including + * streaming reads) with inserts is fine, just not interleaving streaming + * partial object reads with inserts. * * @since 3.5 * @return reader for any object, including an object recently inserted by @@ -411,13 +448,15 @@ * The flush may take some period of time to make the objects available to * other threads. * - * @throws IOException + * @throws java.io.IOException * the flush could not be completed; objects inserted thus far * are in an indeterminate state. */ public abstract void flush() throws IOException; /** + * {@inheritDoc} + *

    * Release any resources used by this inserter. *

    * An inserter that has been released can be used again, but may need to be diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,16 +61,23 @@ */ public abstract class ObjectLoader { /** - * @return Git in pack object type, see {@link Constants}. + * Get Git in pack object type + * + * @return Git in pack object type, see + * {@link org.eclipse.jgit.lib.Constants}. */ public abstract int getType(); /** + * Get size of object in bytes + * * @return size of object in bytes */ public abstract long getSize(); /** + * Whether this object is too large to obtain as a byte array. + * * @return true if this object is too large to obtain as a byte array. * Objects over a certain threshold should be accessed only by their * {@link #openStream()} to prevent overflowing the JVM heap. @@ -91,7 +98,7 @@ * be modified by the caller. * * @return the bytes of this object. - * @throws LargeObjectException + * @throws org.eclipse.jgit.errors.LargeObjectException * if the object won't fit into a byte array, because * {@link #isLarge()} returns true. Callers should use * {@link #openStream()} instead to access the contents. @@ -113,16 +120,18 @@ * * @param sizeLimit * maximum number of bytes to return. If the object is larger - * than this limit, {@link LargeObjectException} will be thrown. + * than this limit, + * {@link org.eclipse.jgit.errors.LargeObjectException} will be + * thrown. * @return the bytes of this object. - * @throws LargeObjectException + * @throws org.eclipse.jgit.errors.LargeObjectException * if the object is bigger than {@code sizeLimit}, or if - * {@link OutOfMemoryError} occurs during allocation of the - * result array. Callers should use {@link #openStream()} + * {@link java.lang.OutOfMemoryError} occurs during allocation + * of the result array. Callers should use {@link #openStream()} * instead to access the contents. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object is large, and it no longer exists. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public final byte[] getBytes(int sizeLimit) throws LargeObjectException, @@ -144,7 +153,7 @@ * Changes (if made) will affect the cache but not the repository itself. * * @return the cached bytes of this object. Do not modify it. - * @throws LargeObjectException + * @throws org.eclipse.jgit.errors.LargeObjectException * if the object won't fit into a byte array, because * {@link #isLarge()} returns true. Callers should use * {@link #openStream()} instead to access the contents. @@ -167,16 +176,17 @@ * @param sizeLimit * maximum number of bytes to return. If the object size is * larger than this limit and {@link #isLarge()} is true, - * {@link LargeObjectException} will be thrown. + * {@link org.eclipse.jgit.errors.LargeObjectException} will be + * thrown. * @return the cached bytes of this object. Do not modify it. - * @throws LargeObjectException + * @throws org.eclipse.jgit.errors.LargeObjectException * if the object is bigger than {@code sizeLimit}, or if - * {@link OutOfMemoryError} occurs during allocation of the - * result array. Callers should use {@link #openStream()} + * {@link java.lang.OutOfMemoryError} occurs during allocation + * of the result array. Callers should use {@link #openStream()} * instead to access the contents. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object is large, and it no longer exists. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public byte[] getCachedBytes(int sizeLimit) throws LargeObjectException, @@ -213,9 +223,9 @@ * @return a stream of this object's data. Caller must close the stream when * through with it. The returned stream is buffered with a * reasonable buffer size. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object no longer exists. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public abstract ObjectStream openStream() throws MissingObjectException, @@ -236,9 +246,9 @@ * stream to receive the complete copy of this object's data. * Caller is responsible for flushing or closing this stream * after this method returns. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object no longer exists. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed, or the stream cannot be * written to. */ @@ -323,4 +333,42 @@ return new ObjectStream.SmallStream(this); } } + + /** + * Wraps a delegate ObjectLoader. + * + * @since 4.10 + */ + public static abstract class Filter extends ObjectLoader { + /** + * @return delegate ObjectLoader to handle all processing. + * @since 4.10 + */ + protected abstract ObjectLoader delegate(); + + @Override + public int getType() { + return delegate().getType(); + } + + @Override + public long getSize() { + return delegate().getSize(); + } + + @Override + public boolean isLarge() { + return delegate().isLarge(); + } + + @Override + public byte[] getCachedBytes() { + return delegate().getCachedBytes(); + } + + @Override + public ObjectStream openStream() throws IOException { + return delegate().openStream(); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,21 +50,29 @@ import java.util.List; import java.util.Set; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs; /** - * Reads an {@link ObjectDatabase} for a single thread. + * Reads an {@link org.eclipse.jgit.lib.ObjectDatabase} for a single thread. *

    * Readers that can support efficient reuse of pack encoded objects should also - * implement the companion interface {@link ObjectReuseAsIs}. + * implement the companion interface + * {@link org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs}. */ public abstract class ObjectReader implements AutoCloseable { /** Type hint indicating the caller doesn't know the type. */ public static final int OBJ_ANY = -1; /** + * The threshold at which a file will be streamed rather than loaded + * entirely into memory. + * @since 4.6 + */ + protected int streamFileThreshold; + + /** * Construct a new reader from the same data. *

    * Applications can use this method to build a new reader from the same data @@ -87,7 +95,7 @@ * @param objectId * object identity that needs to be abbreviated. * @return SHA-1 abbreviation. - * @throws IOException + * @throws java.io.IOException * the object store cannot be read. */ public AbbreviatedObjectId abbreviate(AnyObjectId objectId) @@ -114,7 +122,7 @@ * [2, {@value Constants#OBJECT_ID_STRING_LENGTH}]. * @return SHA-1 abbreviation. If no matching objects exist in the * repository, the abbreviation will match the minimum length. - * @throws IOException + * @throws java.io.IOException * the object store cannot be read. */ public AbbreviatedObjectId abbreviate(AnyObjectId objectId, int len) @@ -126,7 +134,7 @@ Collection matches = resolve(abbrev); while (1 < matches.size() && len < Constants.OBJECT_ID_STRING_LENGTH) { abbrev = objectId.abbreviate(++len); - List n = new ArrayList(8); + List n = new ArrayList<>(8); for (ObjectId candidate : matches) { if (abbrev.prefixCompare(candidate) == 0) n.add(candidate); @@ -166,7 +174,7 @@ * abbreviated id to resolve to a complete identity. The * abbreviation must have a length of at least 2. * @return candidates that begin with the abbreviated identity. - * @throws IOException + * @throws java.io.IOException * the object store cannot be read. */ public abstract Collection resolve(AbbreviatedObjectId id) @@ -178,7 +186,7 @@ * @param objectId * identity of the object to test for existence of. * @return true if the specified object is stored in this database. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public boolean has(AnyObjectId objectId) throws IOException { @@ -192,13 +200,14 @@ * identity of the object to test for existence of. * @param typeHint * hint about the type of object being requested, e.g. - * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object - * type is not known, or does not matter to the caller. + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. * @return true if the specified object is stored in this database. * @throws IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does * not match typeHint. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public boolean has(AnyObjectId objectId, int typeHint) throws IOException { @@ -215,10 +224,11 @@ * * @param objectId * identity of the object to open. - * @return a {@link ObjectLoader} for accessing the object. - * @throws MissingObjectException + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the + * object. + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public ObjectLoader open(AnyObjectId objectId) @@ -233,15 +243,17 @@ * identity of the object to open. * @param typeHint * hint about the type of object being requested, e.g. - * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object - * type is not known, or does not matter to the caller. - * @return a {@link ObjectLoader} for accessing the object. - * @throws MissingObjectException + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the + * object. + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does * not match typeHint. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public abstract ObjectLoader open(AnyObjectId objectId, int typeHint) @@ -252,15 +264,13 @@ * Returns IDs for those commits which should be considered as shallow. * * @return IDs of shallow commits - * @throws IOException + * @throws java.io.IOException */ public abstract Set getShallowCommits() throws IOException; /** * Asynchronous object opening. * - * @param - * type of identifier being supplied. * @param objectIds * objects to open from the object store. The supplied collection * must not be modified until the queue has finished. @@ -278,6 +288,7 @@ return new AsyncObjectLoaderQueue() { private T cur; + @Override public boolean next() throws MissingObjectException, IOException { if (idItr.hasNext()) { cur = idItr.next(); @@ -287,22 +298,27 @@ } } + @Override public T getCurrent() { return cur; } + @Override public ObjectId getObjectId() { return cur; } + @Override public ObjectLoader open() throws IOException { return ObjectReader.this.open(cur, OBJ_ANY); } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public void release() { // Since we are sequential by default, we don't // have any state to clean up if we terminate early. @@ -321,15 +337,16 @@ * identity of the object to open. * @param typeHint * hint about the type of object being requested, e.g. - * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object - * type is not known, or does not matter to the caller. + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. * @return size of object in bytes. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does * not match typeHint. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public long getObjectSize(AnyObjectId objectId, int typeHint) @@ -341,8 +358,6 @@ /** * Asynchronous object size lookup. * - * @param - * type of identifier being supplied. * @param objectIds * objects to get the size of from the object store. The supplied * collection must not be modified until the queue has finished. @@ -362,6 +377,7 @@ private long sz; + @Override public boolean next() throws MissingObjectException, IOException { if (idItr.hasNext()) { cur = idItr.next(); @@ -372,22 +388,27 @@ } } + @Override public T getCurrent() { return cur; } + @Override public ObjectId getObjectId() { return cur; } + @Override public long getSize() { return sz; } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public void release() { // Since we are sequential by default, we don't // have any state to clean up if we terminate early. @@ -413,7 +434,7 @@ * An index that can be used to speed up ObjectWalks. * * @return the index or null if one does not exist. - * @throws IOException + * @throws java.io.IOException * when the index fails to load * @since 3.0 */ @@ -422,6 +443,22 @@ } /** + * Get the {@link org.eclipse.jgit.lib.ObjectInserter} from which this + * reader was created using {@code inserter.newReader()} + * + * @return the {@link org.eclipse.jgit.lib.ObjectInserter} from which this + * reader was created using {@code inserter.newReader()}, or null if + * this reader was not created from an inserter. + * @since 4.4 + */ + @Nullable + public ObjectInserter getCreatedFromInserter() { + return null; + } + + /** + * {@inheritDoc} + *

    * Release any resources used by this reader. *

    * A reader that has been released can be used again, but may need to be @@ -431,4 +468,131 @@ */ @Override public abstract void close(); + + /** + * Sets the threshold at which a file will be streamed rather than loaded + * entirely into memory + * + * @param threshold + * the new threshold + * @since 4.6 + */ + public void setStreamFileThreshold(int threshold) { + streamFileThreshold = threshold; + } + + /** + * Returns the threshold at which a file will be streamed rather than loaded + * entirely into memory + * + * @return the threshold in bytes + * @since 4.6 + */ + public int getStreamFileThreshold() { + return streamFileThreshold; + } + + /** + * Wraps a delegate ObjectReader. + * + * @since 4.4 + */ + public static abstract class Filter extends ObjectReader { + /** + * @return delegate ObjectReader to handle all processing. + * @since 4.4 + */ + protected abstract ObjectReader delegate(); + + @Override + public ObjectReader newReader() { + return delegate().newReader(); + } + + @Override + public AbbreviatedObjectId abbreviate(AnyObjectId objectId) + throws IOException { + return delegate().abbreviate(objectId); + } + + @Override + public AbbreviatedObjectId abbreviate(AnyObjectId objectId, int len) + throws IOException { + return delegate().abbreviate(objectId, len); + } + + @Override + public Collection resolve(AbbreviatedObjectId id) + throws IOException { + return delegate().resolve(id); + } + + @Override + public boolean has(AnyObjectId objectId) throws IOException { + return delegate().has(objectId); + } + + @Override + public boolean has(AnyObjectId objectId, int typeHint) throws IOException { + return delegate().has(objectId, typeHint); + } + + @Override + public ObjectLoader open(AnyObjectId objectId) + throws MissingObjectException, IOException { + return delegate().open(objectId); + } + + @Override + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return delegate().open(objectId, typeHint); + } + + @Override + public Set getShallowCommits() throws IOException { + return delegate().getShallowCommits(); + } + + @Override + public AsyncObjectLoaderQueue open( + Iterable objectIds, boolean reportMissing) { + return delegate().open(objectIds, reportMissing); + } + + @Override + public long getObjectSize(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return delegate().getObjectSize(objectId, typeHint); + } + + @Override + public AsyncObjectSizeQueue getObjectSize( + Iterable objectIds, boolean reportMissing) { + return delegate().getObjectSize(objectIds, reportMissing); + } + + @Override + public void setAvoidUnreachableObjects(boolean avoid) { + delegate().setAvoidUnreachableObjects(avoid); + } + + @Override + public BitmapIndex getBitmapIndex() throws IOException { + return delegate().getBitmapIndex(); + } + + @Override + @Nullable + public ObjectInserter getCreatedFromInserter() { + return delegate().getCreatedFromInserter(); + } + + @Override + public void close() { + delegate().close(); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,12 +46,22 @@ import java.io.IOException; import java.io.InputStream; -/** Stream of data coming from an object loaded by {@link ObjectLoader}. */ +/** + * Stream of data coming from an object loaded by {@link org.eclipse.jgit.lib.ObjectLoader}. + */ public abstract class ObjectStream extends InputStream { - /** @return Git object type, see {@link Constants}. */ + /** + * Get Git object type, see {@link Constants}. + * + * @return Git object type, see {@link Constants}. + */ public abstract int getType(); - /** @return total size of object in bytes */ + /** + * Get total size of object in bytes + * + * @return total size of object in bytes + */ public abstract long getSize(); /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.SystemReader; +import org.eclipse.jgit.util.time.ProposedTimestamp; /** * A combination of a person identity and time in Git. @@ -64,6 +65,8 @@ private static final long serialVersionUID = 1L; /** + * Get timezone object for the given offset. + * * @param tzOffset * timezone offset as in {@link #getTimeZoneOffset()}. * @return time zone object for the given offset. @@ -111,6 +114,44 @@ r.append(offsetMins); } + /** + * Sanitize the given string for use in an identity and append to output. + *

    + * Trims whitespace from both ends and special characters {@code \n < >} that + * interfere with parsing; appends all other characters to the output. + * Analogous to the C git function {@code strbuf_addstr_without_crud}. + * + * @param r + * string builder to append to. + * @param str + * input string. + * @since 4.4 + */ + public static void appendSanitized(StringBuilder r, String str) { + // Trim any whitespace less than \u0020 as in String#trim(). + int i = 0; + while (i < str.length() && str.charAt(i) <= ' ') { + i++; + } + int end = str.length(); + while (end > i && str.charAt(end - 1) <= ' ') { + end--; + } + + for (; i < end; i++) { + char c = str.charAt(i); + switch (c) { + case '\n': + case '<': + case '>': + continue; + default: + r.append(c); + break; + } + } + } + private final String name; private final String emailAddress; @@ -124,37 +165,57 @@ * This new PersonIdent gets the info from the default committer as available * from the configuration. * - * @param repo + * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ public PersonIdent(final Repository repo) { this(repo.getConfig().get(UserConfig.KEY)); } /** - * Copy a {@link PersonIdent}. + * Copy a {@link org.eclipse.jgit.lib.PersonIdent}. * * @param pi - * Original {@link PersonIdent} + * Original {@link org.eclipse.jgit.lib.PersonIdent} */ public PersonIdent(final PersonIdent pi) { this(pi.getName(), pi.getEmailAddress()); } /** - * Construct a new {@link PersonIdent} with current time. + * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current + * time. * * @param aName + * a {@link java.lang.String} object. * @param aEmailAddress + * a {@link java.lang.String} object. */ public PersonIdent(final String aName, final String aEmailAddress) { this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime()); } /** + * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current + * time. + * + * @param aName + * a {@link java.lang.String} object. + * @param aEmailAddress + * a {@link java.lang.String} object. + * @param when + * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. + * @since 4.6 + */ + public PersonIdent(String aName, String aEmailAddress, + ProposedTimestamp when) { + this(aName, aEmailAddress, when.millis()); + } + + /** * Copy a PersonIdent, but alter the clone's time stamp * * @param pi - * original {@link PersonIdent} + * original {@link org.eclipse.jgit.lib.PersonIdent} * @param when * local time * @param tz @@ -165,10 +226,11 @@ } /** - * Copy a {@link PersonIdent}, but alter the clone's time stamp + * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's + * time stamp * * @param pi - * original {@link PersonIdent} + * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time */ @@ -179,8 +241,8 @@ /** * Construct a PersonIdent from simple data * - * @param aName - * @param aEmailAddress + * @param aName a {@link java.lang.String} object. + * @param aEmailAddress a {@link java.lang.String} object. * @param aWhen * local time stamp * @param aTZ @@ -196,7 +258,7 @@ * Copy a PersonIdent, but alter the clone's time stamp * * @param pi - * original {@link PersonIdent} + * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time stamp * @param aTZ @@ -217,10 +279,17 @@ } /** - * Construct a {@link PersonIdent} + * Construct a {@link org.eclipse.jgit.lib.PersonIdent}. + *

    + * Whitespace in the name and email is preserved for the lifetime of this + * object, but are trimmed by {@link #toExternalString()}. This means that + * parsing the result of {@link #toExternalString()} may not return an + * equivalent instance. * * @param aName + * a {@link java.lang.String} object. * @param aEmailAddress + * a {@link java.lang.String} object. * @param aWhen * local time stamp * @param aTZ @@ -241,6 +310,8 @@ } /** + * Get name of person + * * @return Name of person */ public String getName() { @@ -248,6 +319,8 @@ } /** + * Get email address of person + * * @return email address of person */ public String getEmailAddress() { @@ -255,6 +328,8 @@ } /** + * Get timestamp + * * @return timestamp */ public Date getWhen() { @@ -262,6 +337,8 @@ } /** + * Get this person's declared time zone + * * @return this person's declared time zone; null if time zone is unknown. */ public TimeZone getTimeZone() { @@ -269,6 +346,8 @@ } /** + * Get this person's declared time zone as minutes east of UTC. + * * @return this person's declared time zone as minutes east of UTC. If the * timezone is to the west of UTC it is negative. */ @@ -276,6 +355,12 @@ return tzOffset; } + /** + * {@inheritDoc} + *

    + * Hashcode is based only on the email address and timestamp. + */ + @Override public int hashCode() { int hc = getEmailAddress().hashCode(); hc *= 31; @@ -283,6 +368,8 @@ return hc; } + /** {@inheritDoc} */ + @Override public boolean equals(final Object o) { if (o instanceof PersonIdent) { final PersonIdent p = (PersonIdent) o; @@ -300,9 +387,9 @@ */ public String toExternalString() { final StringBuilder r = new StringBuilder(); - r.append(getName().trim()); + appendSanitized(r, getName()); r.append(" <"); //$NON-NLS-1$ - r.append(getEmailAddress().trim()); + appendSanitized(r, getEmailAddress()); r.append("> "); //$NON-NLS-1$ r.append(when / 1000); r.append(' '); @@ -310,6 +397,8 @@ return r.toString(); } + /** {@inheritDoc} */ + @Override @SuppressWarnings("nls") public String toString() { final StringBuilder r = new StringBuilder(); @@ -328,3 +417,4 @@ return r.toString(); } } + diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,9 @@ package org.eclipse.jgit.lib; -/** A progress reporting interface. */ +/** + * A progress reporting interface. + */ public interface ProgressMonitor { /** Constant indicating the total work units cannot be predicted. */ public static final int UNKNOWN = 0; @@ -85,7 +87,9 @@ */ void update(int completed); - /** Finish the current task, so the next can begin. */ + /** + * Finish the current task, so the next can begin. + */ void endTask(); /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,9 @@ package org.eclipse.jgit.lib; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -53,7 +56,6 @@ import org.eclipse.jgit.lib.RebaseTodoLine.Action; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * Offers methods to read and write files formatted like the git-rebase-todo @@ -65,7 +67,10 @@ private Repository repo; /** + * Constructor for RebaseTodoFile. + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. */ public RebaseTodoFile(Repository repo) { this.repo = repo; @@ -82,14 +87,14 @@ * @param includeComments * true if also comments should be reported * @return the list of steps - * @throws IOException + * @throws java.io.IOException */ public List readRebaseTodo(String path, boolean includeComments) throws IOException { byte[] buf = IO.readFully(new File(repo.getDirectory(), path)); int ptr = 0; int tokenBegin = 0; - List r = new LinkedList(); + List r = new LinkedList<>(); while (ptr < buf.length) { tokenBegin = ptr; ptr = RawParseUtils.nextLF(buf, ptr); @@ -177,8 +182,8 @@ while (tokenCount < 3 && nextSpace < lineEnd) { switch (tokenCount) { case 0: - String actionToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); + String actionToken = new String(buf, tokenBegin, + nextSpace - tokenBegin - 1, UTF_8); tokenBegin = nextSpace; action = RebaseTodoLine.Action.parse(actionToken); if (action == null) @@ -186,14 +191,14 @@ break; case 1: nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); - String commitToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); + String commitToken = new String(buf, tokenBegin, + nextSpace - tokenBegin - 1, UTF_8); tokenBegin = nextSpace; commit = AbbreviatedObjectId.fromString(commitToken); break; case 2: - return new RebaseTodoLine(action, commit, RawParseUtils.decode( - buf, tokenBegin, 1 + lineEnd)); + return new RebaseTodoLine(action, commit, + RawParseUtils.decode(buf, tokenBegin, 1 + lineEnd)); } tokenCount++; } @@ -212,13 +217,12 @@ * the steps to be written * @param append * whether to append to an existing file or to write a new file - * @throws IOException + * @throws java.io.IOException */ public void writeRebaseTodoFile(String path, List steps, boolean append) throws IOException { - OutputStream fw = new SafeBufferedOutputStream(new FileOutputStream( - new File(repo.getDirectory(), path), append)); - try { + try (OutputStream fw = new BufferedOutputStream(new FileOutputStream( + new File(repo.getDirectory(), path), append))) { StringBuilder sb = new StringBuilder(); for (RebaseTodoLine step : steps) { sb.setLength(0); @@ -234,8 +238,6 @@ sb.append('\n'); fw.write(Constants.encode(sb.toString())); } - } finally { - fw.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java 2019-09-03 12:37:49.000000000 +0000 @@ -142,8 +142,11 @@ * Create a new non-comment line * * @param action + * a {@link org.eclipse.jgit.lib.RebaseTodoLine.Action} object. * @param commit + * a {@link org.eclipse.jgit.lib.AbbreviatedObjectId} object. * @param shortMessage + * a {@link java.lang.String} object. */ public RebaseTodoLine(Action action, AbbreviatedObjectId commit, String shortMessage) { @@ -154,6 +157,8 @@ } /** + * Get rebase action type + * * @return rebase action type */ public Action getAction() { @@ -167,7 +172,8 @@ * non-comment. * * @param newAction - * @throws IllegalTodoFileModification + * a {@link org.eclipse.jgit.lib.RebaseTodoLine.Action} object. + * @throws org.eclipse.jgit.errors.IllegalTodoFileModification * on attempt to set a non-comment action on a line which was a * comment line before. */ @@ -193,7 +199,7 @@ /** *

    * Set a comment for this line that is used if this line's - * {@link RebaseTodoLine#action} is a {@link Action#COMMENT} + * {@link org.eclipse.jgit.lib.RebaseTodoLine#action} is a {@link org.eclipse.jgit.lib.RebaseTodoLine.Action#COMMENT} *

    * It's allowed to unset the comment by calling * setComment(null)
    @@ -230,6 +236,8 @@ } /** + * Get abbreviated commit SHA-1 of commit that action will be performed on + * * @return abbreviated commit SHA-1 of commit that action will be performed * on */ @@ -238,6 +246,9 @@ } /** + * Get the first line of the commit message of the commit the action will be + * performed on. + * * @return the first line of the commit message of the commit the action * will be performed on. */ @@ -246,13 +257,18 @@ } /** + * Set short message + * * @param shortMessage + * a short message. */ public void setShortMessage(String shortMessage) { this.shortMessage = shortMessage; } /** + * Get a comment + * * @return a comment. If the line is a comment line then the comment is * returned. Lines starting with # or blank lines or lines * containing only spaces and tabs are considered as comment lines. @@ -262,6 +278,7 @@ return comment; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,8 @@ /** Singleton instance of RefComparator */ public static final RefComparator INSTANCE = new RefComparator(); + /** {@inheritDoc} */ + @Override public int compare(final Ref o1, final Ref o2) { return compareTo(o1, o2); } @@ -72,7 +74,7 @@ * @return sorted collection of refs */ public static Collection sort(final Collection refs) { - final List r = new ArrayList(refs); + final List r = new ArrayList<>(refs); Collections.sort(r, INSTANCE); return r; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,12 +51,17 @@ import java.util.List; import java.util.Map; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** - * Abstraction of name to {@link ObjectId} mapping. + * Abstraction of name to {@link org.eclipse.jgit.lib.ObjectId} mapping. *

    - * A reference database stores a mapping of reference names to {@link ObjectId}. - * Every {@link Repository} has a single reference database, mapping names to - * the tips of the object graph contained by the {@link ObjectDatabase}. + * A reference database stores a mapping of reference names to + * {@link org.eclipse.jgit.lib.ObjectId}. Every + * {@link org.eclipse.jgit.lib.Repository} has a single reference database, + * mapping names to the tips of the object graph contained by the + * {@link org.eclipse.jgit.lib.ObjectDatabase}. */ public abstract class RefDatabase { /** @@ -79,8 +84,10 @@ *

    * If the reference is nested deeper than this depth, the implementation * should either fail, or at least claim the reference does not exist. + * + * @since 4.2 */ - protected static final int MAX_SYMBOLIC_REF_DEPTH = 5; + public static final int MAX_SYMBOLIC_REF_DEPTH = 5; /** Magic value for {@link #getRefs(String)} to return all references. */ public static final String ALL = "";//$NON-NLS-1$ @@ -88,12 +95,14 @@ /** * Initialize a new reference database at this location. * - * @throws IOException + * @throws java.io.IOException * the database could not be created. */ public abstract void create() throws IOException; - /** Close any resources held by this database. */ + /** + * Close any resources held by this database. + */ public abstract void close(); /** @@ -113,7 +122,7 @@ * proposed name. * @return true if the name overlaps with an existing reference; false if * using this name right now would be safe. - * @throws IOException + * @throws java.io.IOException * the database could not be read to check for conflicts. * @see #getConflictingNames(String) */ @@ -128,10 +137,11 @@ * @return a collection of full names of existing refs which would conflict * with the passed ref name; empty collection when there are no * conflicts - * @throws IOException + * @throws java.io.IOException * @since 2.3 * @see #isNameConflicting(String) */ + @NonNull public Collection getConflictingNames(String name) throws IOException { Map allRefs = getRefs(ALL); @@ -144,7 +154,7 @@ lastSlash = name.lastIndexOf('/', lastSlash - 1); } - List conflicting = new ArrayList(); + List conflicting = new ArrayList<>(); // Cannot be the container of an existing reference. String prefix = name + '/'; for (String existing : allRefs.keySet()) @@ -161,14 +171,16 @@ * the name of the reference. * @param detach * if {@code true} and {@code name} is currently a - * {@link SymbolicRef}, the update will replace it with an - * {@link ObjectIdRef}. Otherwise, the update will recursively - * traverse {@link SymbolicRef}s and operate on the leaf - * {@link ObjectIdRef}. + * {@link org.eclipse.jgit.lib.SymbolicRef}, the update will + * replace it with an {@link org.eclipse.jgit.lib.ObjectIdRef}. + * Otherwise, the update will recursively traverse + * {@link org.eclipse.jgit.lib.SymbolicRef}s and operate on the + * leaf {@link org.eclipse.jgit.lib.ObjectIdRef}. * @return a new update for the requested name; never null. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. */ + @NonNull public abstract RefUpdate newUpdate(String name, boolean detach) throws IOException; @@ -180,9 +192,10 @@ * @param toName * name of reference to rename to * @return an update command that knows how to rename a branch to another. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. */ + @NonNull public abstract RefRename newRename(String fromName, String toName) throws IOException; @@ -193,13 +206,32 @@ * * @return a new batch update object. */ + @NonNull public BatchRefUpdate newBatchUpdate() { return new BatchRefUpdate(this); } /** - * @return if the database performs {@code newBatchUpdate()} as an atomic - * transaction. + * Whether the database is capable of performing batch updates as atomic + * transactions. + *

    + * If true, by default {@link org.eclipse.jgit.lib.BatchRefUpdate} instances + * will perform updates atomically, meaning either all updates will succeed, + * or all updates will fail. It is still possible to turn off this behavior + * on a per-batch basis by calling {@code update.setAtomic(false)}. + *

    + * If false, {@link org.eclipse.jgit.lib.BatchRefUpdate} instances will + * never perform updates atomically, and calling + * {@code update.setAtomic(true)} will cause the entire batch to fail with + * {@code REJECTED_OTHER_REASON}. + *

    + * This definition of atomicity is stronger than what is provided by + * {@link org.eclipse.jgit.transport.ReceivePack}. {@code ReceivePack} will + * attempt to reject all commands if it knows in advance some commands may + * fail, even if the storage layer does not support atomic transactions. + * Here, atomicity applies even in the case of unforeseeable errors. + * + * @return whether transactions are atomic by default. * @since 3.6 */ public boolean performsAtomicTransactions() { @@ -220,9 +252,10 @@ * the name of the reference. May be a short name which must be * searched for using the standard {@link #SEARCH_PATH}. * @return the reference (if it exists); else {@code null}. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. */ + @Nullable public abstract Ref getRef(String name) throws IOException; /** @@ -234,10 +267,11 @@ * @param name * the unabbreviated name of the reference. * @return the reference (if it exists); else {@code null}. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. * @since 4.1 */ + @Nullable public Ref exactRef(String name) throws IOException { Ref ref = getRef(name); if (ref == null || !name.equals(ref.getName())) { @@ -257,10 +291,11 @@ * 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 + * @throws java.io.IOException * the reference space cannot be accessed. * @since 4.1 */ + @NonNull public Map exactRef(String... refs) throws IOException { Map result = new HashMap<>(refs.length); for (String name : refs) { @@ -281,10 +316,11 @@ * @param refs * the unabbreviated names of references to look up. * @return the first named reference that exists (if any); else {@code null}. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. * @since 4.1 */ + @Nullable public Ref firstExactRef(String... refs) throws IOException { for (String name : refs) { Ref ref = exactRef(name); @@ -305,9 +341,10 @@ * @return modifiable map that is a complete snapshot of the current * reference namespace, with {@code prefix} removed from the start * of each key. The map can be an unsorted map. - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. */ + @NonNull public abstract Map getRefs(String prefix) throws IOException; /** @@ -319,29 +356,32 @@ * and {@link #exactRef(String)}. * * @return a list of additional refs - * @throws IOException + * @throws java.io.IOException * the reference space cannot be accessed. */ + @NonNull public abstract List getAdditionalRefs() throws IOException; /** * Peel a possibly unpeeled reference by traversing the annotated tags. *

    * If the reference cannot be peeled (as it does not refer to an annotated - * tag) the peeled id stays null, but {@link Ref#isPeeled()} will be true. + * tag) the peeled id stays null, but + * {@link org.eclipse.jgit.lib.Ref#isPeeled()} will be true. *

    - * Implementors should check {@link Ref#isPeeled()} before performing any - * additional work effort. + * Implementors should check {@link org.eclipse.jgit.lib.Ref#isPeeled()} + * before performing any additional work effort. * * @param ref * The reference to peel * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new * Ref object representing the same data as Ref, but isPeeled() will * be true and getPeeledObjectId() will contain the peeled object - * (or null). - * @throws IOException + * (or {@code null}). + * @throws java.io.IOException * the reference space or object space cannot be accessed. */ + @NonNull public abstract Ref peel(Ref ref) throws IOException; /** @@ -365,9 +405,10 @@ * @param name * short name of ref to find, e.g. "master" to find * "refs/heads/master" in map. - * @return The first ref matching the name, or null if not found. + * @return The first ref matching the name, or {@code null} if not found. * @since 3.4 */ + @Nullable public static Ref findRef(Map map, String name) { for (String prefix : SEARCH_PATH) { String fullname = prefix + name; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,8 +43,12 @@ package org.eclipse.jgit.lib; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** - * Pairing of a name and the {@link ObjectId} it currently has. + * Pairing of a name and the {@link org.eclipse.jgit.lib.ObjectId} it currently + * has. *

    * A ref in Git is (more or less) a variable that holds a single object * identifier. The object identifier can be any valid Git object (blob, tree, @@ -126,14 +130,16 @@ * * @return name of this ref. */ + @NonNull public String getName(); /** * Test if this reference is a symbolic reference. *

    - * A symbolic reference does not have its own {@link ObjectId} value, but - * instead points to another {@code Ref} in the same database and always - * uses that other reference's value as its own. + * A symbolic reference does not have its own + * {@link org.eclipse.jgit.lib.ObjectId} value, but instead points to + * another {@code Ref} in the same database and always uses that other + * reference's value as its own. * * @return true if this is a symbolic reference; false if this reference * contains its own ObjectId. @@ -156,6 +162,7 @@ * * @return the reference that actually stores the ObjectId value. */ + @NonNull public abstract Ref getLeaf(); /** @@ -170,26 +177,33 @@ * * @return the target reference, or {@code this}. */ + @NonNull public abstract Ref getTarget(); /** * Cached value of this ref. * - * @return the value of this ref at the last time we read it. + * @return the value of this ref at the last time we read it. May be + * {@code null} to indicate a ref that does not exist yet or a + * symbolic ref pointing to an unborn branch. */ + @Nullable public abstract ObjectId getObjectId(); /** * Cached value of ref^{} (the ref peeled to commit). * * @return if this ref is an annotated tag the id of the commit (or tree or - * blob) that the annotated tag refers to; null if this ref does not - * refer to an annotated tag. + * blob) that the annotated tag refers to; {@code null} if this ref + * does not refer to an annotated tag. */ + @Nullable public abstract ObjectId getPeeledObjectId(); /** - * @return whether the Ref represents a peeled tag + * Whether the Ref represents a peeled tag. + * + * @return whether the Ref represents a peeled tag. */ public abstract boolean isPeeled(); @@ -201,5 +215,6 @@ * * @return type of ref. */ + @NonNull public abstract Storage getStorage(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,9 +42,6 @@ */ package org.eclipse.jgit.lib; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PersonIdent; - /** * Parsed reflog entry * @@ -53,29 +50,73 @@ public interface ReflogEntry { /** + * Prefix used in reflog messages when the ref was first created. + *

    + * Does not have a corresponding constant in C git, but is untranslated like + * the other constants. + * + * @since 4.9 + */ + public static final String PREFIX_CREATED = "created"; //$NON-NLS-1$ + + /** + * Prefix used in reflog messages when the ref was updated with a fast + * forward. + *

    + * Untranslated, and exactly matches the + * + * untranslated string in C git. + * + * @since 4.9 + */ + public static final String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$ + + /** + * Prefix used in reflog messages when the ref was force updated. + *

    + * Untranslated, and exactly matches the + * + * untranslated string in C git. + * + * @since 4.9 + */ + public static final String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$ + + /** + * Get the commit id before the change + * * @return the commit id before the change */ public abstract ObjectId getOldId(); /** + * Get the commit id after the change + * * @return the commit id after the change */ public abstract ObjectId getNewId(); /** + * Get user performing the change + * * @return user performing the change */ public abstract PersonIdent getWho(); /** + * Get textual description of the change + * * @return textual description of the change */ public abstract String getComment(); /** - * @return a {@link CheckoutEntry} with parsed information about a branch - * switch, or null if the entry is not a checkout + * Parse checkout + * + * @return a {@link org.eclipse.jgit.lib.CheckoutEntry} with parsed + * information about a branch switch, or null if the entry is not a + * checkout */ public abstract CheckoutEntry parseCheckout(); -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,13 +57,15 @@ * Get the last entry in the reflog * * @return the latest reflog entry, or null if no log - * @throws IOException + * @throws java.io.IOException */ public abstract ReflogEntry getLastEntry() throws IOException; /** + * Get all reflog entries in reverse order + * * @return all reflog entries in reverse order - * @throws IOException + * @throws java.io.IOException */ public abstract List getReverseEntries() throws IOException; @@ -71,19 +73,21 @@ * Get specific entry in the reflog relative to the last entry which is * considered entry zero. * - * @param number + * @param number a int. * @return reflog entry or null if not found - * @throws IOException + * @throws java.io.IOException */ public abstract ReflogEntry getReverseEntry(int number) throws IOException; /** + * Get all reflog entries in reverse order + * * @param max * max number of entries to read * @return all reflog entries in reverse order - * @throws IOException + * @throws java.io.IOException */ public abstract List getReverseEntries(int max) throws IOException; -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java 2019-09-03 12:37:49.000000000 +0000 @@ -86,7 +86,11 @@ + Repository.shortenRefName(destination.getName())); } - /** @return identity of the user making the change in the reflog. */ + /** + * Get identity of the user making the change in the reflog. + * + * @return identity of the user making the change in the reflog. + */ public PersonIdent getRefLogIdent() { return destination.getRefLogIdent(); } @@ -130,12 +134,16 @@ destination.setRefLogMessage(msg, false); } - /** Don't record this rename in the ref's associated reflog. */ + /** + * Don't record this rename in the ref's associated reflog. + */ public void disableRefLog() { destination.setRefLogMessage("", false); //$NON-NLS-1$ } /** + * Get result of rename operation + * * @return result of rename operation */ public Result getResult() { @@ -143,8 +151,10 @@ } /** + * Rename + * * @return the result of the new ref update - * @throws IOException + * @throws java.io.IOException */ public Result rename() throws IOException { try { @@ -157,20 +167,25 @@ } /** + * Do the actual rename + * * @return the result of the rename operation. - * @throws IOException + * @throws java.io.IOException */ protected abstract Result doRename() throws IOException; /** + * Whether the {@code Constants#HEAD} reference needs to be linked to the + * new destination name. + * * @return true if the {@code Constants#HEAD} reference needs to be linked * to the new destination name. - * @throws IOException + * @throws java.io.IOException * the current value of {@code HEAD} cannot be read. */ protected boolean needToUpdateHEAD() throws IOException { Ref head = source.getRefDatabase().getRef(Constants.HEAD); - if (head.isSymbolic()) { + if (head != null && head.isSymbolic()) { head = head.getTarget(); return head.getName().equals(source.getName()); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,7 +58,13 @@ * Creates, updates or deletes any reference. */ public abstract class RefUpdate { - /** Status of an update request. */ + /** + * Status of an update request. + *

    + * New values may be added to this enum in the future. Callers may assume that + * unknown values are failures, and may generally treat them the same as + * {@link #REJECTED_OTHER_REASON}. + */ public static enum Result { /** The ref update/delete has not been attempted by the caller. */ NOT_ATTEMPTED, @@ -114,6 +120,10 @@ * merged into the new value. The configuration did not allow a forced * update/delete to take place, so ref still contains the old value. No * previous history was lost. + *

    + * Note: Despite the general name, this result only refers to the + * non-fast-forward case. For more general errors, see {@link + * #REJECTED_OTHER_REASON}. */ REJECTED, @@ -139,7 +149,25 @@ * The ref was renamed from another name *

    */ - RENAMED + RENAMED, + + /** + * One or more objects aren't in the repository. + *

    + * This is severe indication of either repository corruption on the + * server side, or a bug in the client wherein the client did not supply + * all required objects during the pack transfer. + * + * @since 4.9 + */ + REJECTED_MISSING_OBJECT, + + /** + * Rejected for some other reason not covered by another enum value. + * + * @since 4.9 + */ + REJECTED_OTHER_REASON; } /** New value the caller wants this ref to have. */ @@ -157,6 +185,12 @@ /** Should the Result value be appended to {@link #refLogMessage}. */ private boolean refLogIncludeResult; + /** + * Should reflogs be written even if the configured default for this ref is + * not to write it. + */ + private boolean forceRefLog; + /** Old value of the ref, obtained after we lock it. */ private ObjectId oldValue; @@ -199,10 +233,18 @@ refLogMessage = ""; //$NON-NLS-1$ } - /** @return the reference database this update modifies. */ + /** + * Get the reference database this update modifies. + * + * @return the reference database this update modifies. + */ protected abstract RefDatabase getRefDatabase(); - /** @return the repository storing the database's objects. */ + /** + * Get the repository storing the database's objects. + * + * @return the repository storing the database's objects. + */ protected abstract Repository getRepository(); /** @@ -217,33 +259,44 @@ * current reference. * @return true if the lock was acquired and the reference is likely * protected from concurrent modification; false if it failed. - * @throws IOException + * @throws java.io.IOException * the lock couldn't be taken due to an unexpected storage * failure, and not because of a concurrent update. */ protected abstract boolean tryLock(boolean deref) throws IOException; - /** Releases the lock taken by {@link #tryLock} if it succeeded. */ + /** + * Releases the lock taken by {@link #tryLock} if it succeeded. + */ protected abstract void unlock(); /** + * Do update + * * @param desiredResult + * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object. * @return {@code result} - * @throws IOException + * @throws java.io.IOException */ protected abstract Result doUpdate(Result desiredResult) throws IOException; /** + * Do delete + * * @param desiredResult + * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object. * @return {@code result} - * @throws IOException + * @throws java.io.IOException */ protected abstract Result doDelete(Result desiredResult) throws IOException; /** + * Do link + * * @param target - * @return {@link Result#NEW} on success. - * @throws IOException + * a {@link java.lang.String} object. + * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} on success. + * @throws java.io.IOException */ protected abstract Result doLink(String target) throws IOException; @@ -256,7 +309,11 @@ return getRef().getName(); } - /** @return the reference this update will create or modify. */ + /** + * Get the reference this update will create or modify. + * + * @return the reference this update will create or modify. + */ public Ref getRef() { return ref; } @@ -278,6 +335,16 @@ } /** + * Return whether this update is actually detaching a symbolic ref. + * + * @return true if detaching a symref. + * @since 4.9 + */ + public boolean isDetachingSymbolicRef() { + return detachingSymbolicRef; + } + + /** * Set the new value the ref will update to. * * @param id @@ -288,21 +355,27 @@ } /** + * Get the expected value of the ref after the lock is taken, but before + * update occurs. + * * @return the expected value of the ref after the lock is taken, but before * update occurs. Null to avoid the compare and swap test. Use - * {@link ObjectId#zeroId()} to indicate expectation of a - * non-existant ref. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate + * expectation of a non-existant ref. */ public ObjectId getExpectedOldObjectId() { return expValue; } /** + * Set the expected value of the ref after the lock is taken, but before + * update occurs. + * * @param id * the expected value of the ref after the lock is taken, but * before update occurs. Null to avoid the compare and swap test. - * Use {@link ObjectId#zeroId()} to indicate expectation of a - * non-existant ref. + * Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate + * expectation of a non-existant ref. */ public void setExpectedOldObjectId(final AnyObjectId id) { expValue = id != null ? id.toObjectId() : null; @@ -327,7 +400,11 @@ force = b; } - /** @return identity of the user making the change in the reflog. */ + /** + * Get identity of the user making the change in the reflog. + * + * @return identity of the user making the change in the reflog. + */ public PersonIdent getRefLogIdent() { return refLogIdent; } @@ -358,13 +435,23 @@ return refLogMessage; } - /** @return {@code true} if the ref log message should show the result. */ + /** + * Whether the ref log message should show the result. + * + * @return {@code true} if the ref log message should show the result. + */ protected boolean isRefLogIncludingResult() { return refLogIncludeResult; } /** * Set the message to include in the reflog. + *

    + * Repository implementations may limit which reflogs are written by default, + * based on the project configuration. If a repo is not configured to write + * logs for this ref by default, setting the message alone may have no effect. + * To indicate that the repo should write logs for this update in spite of + * configured defaults, use {@link #setForceRefLog(boolean)}. * * @param msg * the message to describe this change. It may be null if @@ -386,13 +473,35 @@ } } - /** Don't record this update in the ref's associated reflog. */ + /** + * Don't record this update in the ref's associated reflog. + */ public void disableRefLog() { refLogMessage = null; refLogIncludeResult = false; } /** + * Force writing a reflog for the updated ref. + * + * @param force whether to force. + * @since 4.9 + */ + public void setForceRefLog(boolean force) { + forceRefLog = force; + } + + /** + * Check whether the reflog should be written regardless of repo defaults. + * + * @return whether force writing is enabled. + * @since 4.9 + */ + protected boolean isForceRefLog() { + return forceRefLog; + } + + /** * The old value of the ref, prior to the update being attempted. *

    * This value may differ before and after the update method. Initially it is @@ -465,7 +574,7 @@ * the merge test is performed. * * @return the result status of the update. - * @throws IOException + * @throws java.io.IOException * an unexpected IO error occurred while writing changes. */ public Result forceUpdate() throws IOException { @@ -485,7 +594,7 @@ * * * @return the result status of the update. - * @throws IOException + * @throws java.io.IOException * an unexpected IO error occurred while writing changes. */ public Result update() throws IOException { @@ -503,7 +612,7 @@ * a RevWalk instance this update command can borrow to perform * the merge test. The walk will be reset to perform the test. * @return the result status of the update. - * @throws IOException + * @throws java.io.IOException * an unexpected IO error occurred while writing changes. */ public Result update(final RevWalk walk) throws IOException { @@ -533,7 +642,7 @@ * * * @return the result status of the delete. - * @throws IOException + * @throws java.io.IOException */ public Result delete() throws IOException { try (RevWalk rw = new RevWalk(getRepository())) { @@ -548,11 +657,14 @@ * a RevWalk instance this delete command can borrow to perform * the merge test. The walk will be reset to perform the test. * @return the result status of the delete. - * @throws IOException + * @throws java.io.IOException */ public Result delete(final RevWalk walk) throws IOException { - final String myName = getRef().getLeaf().getName(); - if (myName.startsWith(Constants.R_HEADS)) { + final String myName = detachingSymbolicRef + ? getRef().getName() + : getRef().getLeaf().getName(); + if (myName.startsWith(Constants.R_HEADS) && !getRepository().isBare()) { + // Don't allow the currently checked out branch to be deleted. Ref head = getRefDatabase().getRef(Constants.HEAD); while (head != null && head.isSymbolic()) { head = head.getTarget(); @@ -583,8 +695,9 @@ * @param target * name of the new target for this reference. The new target name * must be absolute, so it must begin with {@code refs/}. - * @return {@link Result#NEW} or {@link Result#FORCED} on success. - * @throws IOException + * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} or + * {@link org.eclipse.jgit.lib.RefUpdate.Result#FORCED} on success. + * @throws java.io.IOException */ public Result link(String target) throws IOException { if (!target.startsWith(Constants.R_REFS)) @@ -624,31 +737,47 @@ RevObject oldObj; // don't make expensive conflict check if this is an existing Ref - if (oldValue == null && checkConflicting && getRefDatabase().isNameConflicting(getName())) + if (oldValue == null && checkConflicting + && getRefDatabase().isNameConflicting(getName())) { return Result.LOCK_FAILURE; + } try { - if (!tryLock(true)) + // If we're detaching a symbolic reference, we should update the reference + // itself. Otherwise, we will update the leaf reference, which should be + // an ObjectIdRef. + if (!tryLock(!detachingSymbolicRef)) { return Result.LOCK_FAILURE; + } if (expValue != null) { final ObjectId o; o = oldValue != null ? oldValue : ObjectId.zeroId(); - if (!AnyObjectId.equals(expValue, o)) + if (!AnyObjectId.equals(expValue, o)) { return Result.LOCK_FAILURE; + } + } + try { + newObj = safeParseNew(walk, newValue); + } catch (MissingObjectException e) { + return Result.REJECTED_MISSING_OBJECT; } - if (oldValue == null) + + if (oldValue == null) { return store.execute(Result.NEW); + } - newObj = safeParse(walk, newValue); - oldObj = safeParse(walk, oldValue); - if (newObj == oldObj && !detachingSymbolicRef) + oldObj = safeParseOld(walk, oldValue); + if (newObj == oldObj && !detachingSymbolicRef) { return store.execute(Result.NO_CHANGE); + } - if (isForceUpdate()) + if (isForceUpdate()) { return store.execute(Result.FORCED); + } if (newObj instanceof RevCommit && oldObj instanceof RevCommit) { - if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj)) + if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj)) { return store.execute(Result.FAST_FORWARD); + } } return Result.REJECTED; @@ -662,22 +791,30 @@ * are checked explicitly. * * @param check + * whether to enable the check for conflicting ref names. * @since 3.0 */ public void setCheckConflicting(boolean check) { checkConflicting = check; } - private static RevObject safeParse(final RevWalk rw, final AnyObjectId id) + private static RevObject safeParseNew(RevWalk rw, AnyObjectId newId) + throws IOException { + if (newId == null || ObjectId.zeroId().equals(newId)) { + return null; + } + return rw.parseAny(newId); + } + + private static RevObject safeParseOld(RevWalk rw, AnyObjectId oldId) throws IOException { try { - return id != null ? rw.parseAny(id) : null; + return oldId != null ? rw.parseAny(oldId) : null; } catch (MissingObjectException e) { - // We can expect some objects to be missing, like if we are - // trying to force a deletion of a branch and the object it - // points to has been pruned from the database due to freak - // corruption accidents (it happens with 'git new-work-dir'). - // + // We can expect some old objects to be missing, like if we are trying to + // force a deletion of a branch and the object it points to has been + // pruned from the database due to freak corruption accidents (it happens + // with 'git new-work-dir'). return null; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,8 +56,8 @@ import org.eclipse.jgit.util.RefMap; /** - * Writes out refs to the {@link Constants#INFO_REFS} and - * {@link Constants#PACKED_REFS} files. + * Writes out refs to the {@link org.eclipse.jgit.lib.Constants#INFO_REFS} and + * {@link org.eclipse.jgit.lib.Constants#PACKED_REFS} files. * * This class is abstract as the writing of the files must be handled by the * caller. This is because it is used by transport classes as well. @@ -67,6 +67,8 @@ private final Collection refs; /** + *

    Constructor for RefWriter.

    + * * @param refs * the complete set of references. This should have been computed * by applying updates to the advertised refs already discovered. @@ -76,6 +78,8 @@ } /** + *

    Constructor for RefWriter.

    + * * @param refs * the complete set of references. This should have been computed * by applying updates to the advertised refs already discovered. @@ -88,6 +92,8 @@ } /** + *

    Constructor for RefWriter.

    + * * @param refs * the complete set of references. This should have been computed * by applying updates to the advertised refs already discovered. @@ -97,13 +103,13 @@ } /** - * Rebuild the {@link Constants#INFO_REFS}. + * Rebuild the {@link org.eclipse.jgit.lib.Constants#INFO_REFS}. *

    - * This method rebuilds the contents of the {@link Constants#INFO_REFS} file - * to match the passed list of references. - * + * This method rebuilds the contents of the + * {@link org.eclipse.jgit.lib.Constants#INFO_REFS} file to match the passed + * list of references. * - * @throws IOException + * @throws java.io.IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ @@ -119,13 +125,20 @@ continue; } - r.getObjectId().copyTo(tmp, w); + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + // Symrefs to unborn branches aren't advertised in the info/refs + // file. + continue; + } + objectId.copyTo(tmp, w); w.write('\t'); w.write(r.getName()); w.write('\n'); - if (r.getPeeledObjectId() != null) { - r.getPeeledObjectId().copyTo(tmp, w); + ObjectId peeledObjectId = r.getPeeledObjectId(); + if (peeledObjectId != null) { + peeledObjectId.copyTo(tmp, w); w.write('\t'); w.write(r.getName()); w.write("^{}\n"); //$NON-NLS-1$ @@ -135,13 +148,14 @@ } /** - * Rebuild the {@link Constants#PACKED_REFS} file. + * Rebuild the {@link org.eclipse.jgit.lib.Constants#PACKED_REFS} file. *

    - * This method rebuilds the contents of the {@link Constants#PACKED_REFS} - * file to match the passed list of references, including only those refs - * that have a storage type of {@link Ref.Storage#PACKED}. + * This method rebuilds the contents of the + * {@link org.eclipse.jgit.lib.Constants#PACKED_REFS} file to match the + * passed list of references, including only those refs that have a storage + * type of {@link org.eclipse.jgit.lib.Ref.Storage#PACKED}. * - * @throws IOException + * @throws java.io.IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ @@ -167,14 +181,21 @@ if (r.getStorage() != Ref.Storage.PACKED) continue; - r.getObjectId().copyTo(tmp, w); + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + // A packed ref cannot be a symref, let alone a symref + // to an unborn branch. + throw new NullPointerException(); + } + objectId.copyTo(tmp, w); w.write(' '); w.write(r.getName()); w.write('\n'); - if (r.getPeeledObjectId() != null) { + ObjectId peeledObjectId = r.getPeeledObjectId(); + if (peeledObjectId != null) { w.write('^'); - r.getPeeledObjectId().copyTo(tmp, w); + peeledObjectId.copyTo(tmp, w); w.write('\n'); } } @@ -189,7 +210,7 @@ * path to ref file. * @param content * byte content of file to be written. - * @throws IOException + * @throws java.io.IOException */ protected abstract void writeFile(String file, byte[] content) throws IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,7 @@ import java.io.File; /** - * Base class to support constructing a {@link Repository}. + * Base class to support constructing a {@link org.eclipse.jgit.lib.Repository}. *

    * Applications must set one of {@link #setGitDir(File)} or * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or @@ -55,7 +55,7 @@ *

    * Single repository applications trying to be compatible with other Git * implementations are encouraged to use a model such as: - * + * *

      * new RepositoryBuilder() //
      * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
    @@ -63,7 +63,7 @@
      * 		.findGitDir() // scan up the file system tree
      * 		.build()
      * 
    - * + * * @see org.eclipse.jgit.storage.file.FileRepositoryBuilder */ public class RepositoryBuilder extends diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 Ericsson + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.lib; + +import java.util.concurrent.TimeUnit; + +/** + * Configuration parameters for JVM-wide repository cache used by JGit. + * + * @since 4.4 + */ +public class RepositoryCacheConfig { + + /** + * Set cleanupDelayMillis to this value in order to switch off time-based + * cache eviction. Expired cache entries will only be evicted when + * RepositoryCache.clearExpired or RepositoryCache.clear are called. + */ + public static final long NO_CLEANUP = 0; + + /** + * Set cleanupDelayMillis to this value in order to auto-set it to minimum + * of 1/10 of expireAfterMillis and 10 minutes + */ + public static final long AUTO_CLEANUP_DELAY = -1; + + private long expireAfterMillis; + + private long cleanupDelayMillis; + + /** + * Create a default configuration. + */ + public RepositoryCacheConfig() { + expireAfterMillis = TimeUnit.HOURS.toMillis(1); + cleanupDelayMillis = AUTO_CLEANUP_DELAY; + } + + /** + * Get the time an unused repository should be expired and be evicted from + * the RepositoryCache in milliseconds. + * + * @return the time an unused repository should be expired and be evicted + * from the RepositoryCache in milliseconds. Default is 1 + * hour. + */ + public long getExpireAfter() { + return expireAfterMillis; + } + + /** + * Set the time an unused repository should be expired and be evicted from + * the RepositoryCache in milliseconds. + * + * @param expireAfterMillis + * the time an unused repository should be expired and be evicted + * from the RepositoryCache in milliseconds. + */ + public void setExpireAfter(long expireAfterMillis) { + this.expireAfterMillis = expireAfterMillis; + } + + /** + * Get the delay between the periodic cleanup of expired repository in + * milliseconds. + * + * @return the delay between the periodic cleanup of expired repository in + * milliseconds. Default is minimum of 1/10 of expireAfterMillis + * and 10 minutes + */ + public long getCleanupDelay() { + if (cleanupDelayMillis < 0) { + return Math.min(expireAfterMillis / 10, + TimeUnit.MINUTES.toMillis(10)); + } + return cleanupDelayMillis; + } + + /** + * Set the delay between the periodic cleanup of expired repository in + * milliseconds. + * + * @param cleanupDelayMillis + * the delay between the periodic cleanup of expired repository + * in milliseconds. Set it to {@link #AUTO_CLEANUP_DELAY} to + * automatically derive cleanup delay from expireAfterMillis. + *

    + * Set it to {@link #NO_CLEANUP} in order to switch off cache + * expiration. + *

    + * If cache expiration is switched off the JVM still can evict + * cache entries when the JVM is running low on available heap + * memory. + */ + public void setCleanupDelay(long cleanupDelayMillis) { + this.cleanupDelayMillis = cleanupDelayMillis; + } + + /** + * Update properties by setting fields from the configuration. + *

    + * If a property is not defined in the configuration, then it is left + * unmodified. + * + * @param config + * configuration to read properties from. + * @return {@code this}. + */ + public RepositoryCacheConfig fromConfig(Config config) { + setExpireAfter( + config.getTimeUnit("core", null, "repositoryCacheExpireAfter", //$NON-NLS-1$//$NON-NLS-2$ + getExpireAfter(), TimeUnit.MILLISECONDS)); + setCleanupDelay( + config.getTimeUnit("core", null, "repositoryCacheCleanupDelay", //$NON-NLS-1$ //$NON-NLS-2$ + AUTO_CLEANUP_DELAY, TimeUnit.MILLISECONDS)); + return this; + } + + /** + * Install this configuration as the live settings. + *

    + * The new configuration is applied immediately. + */ + public void install() { + RepositoryCache.reconfigure(this); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,37 +45,47 @@ 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; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** Cache of active {@link Repository} instances. */ +/** + * Cache of active {@link org.eclipse.jgit.lib.Repository} instances. + */ public class RepositoryCache { + private final static Logger LOG = LoggerFactory + .getLogger(RepositoryCache.class); + private static final RepositoryCache cache = new RepositoryCache(); /** * Open an existing repository, reusing a cached instance if possible. *

    * When done with the repository, the caller must call - * {@link Repository#close()} to decrement the repository's usage counter. + * {@link org.eclipse.jgit.lib.Repository#close()} to decrement the + * repository's usage counter. * * @param location - * where the local repository is. Typically a {@link FileKey}. + * where the local repository is. Typically a + * {@link org.eclipse.jgit.lib.RepositoryCache.FileKey}. * @return the repository instance requested; caller must close when done. - * @throws IOException + * @throws java.io.IOException * the repository could not be read (likely its core.version * property is not supported). - * @throws RepositoryNotFoundException + * @throws org.eclipse.jgit.errors.RepositoryNotFoundException * there is no repository at the given location. */ public static Repository open(final Key location) throws IOException, @@ -87,16 +97,18 @@ * Open a repository, reusing a cached instance if possible. *

    * When done with the repository, the caller must call - * {@link Repository#close()} to decrement the repository's usage counter. + * {@link org.eclipse.jgit.lib.Repository#close()} to decrement the + * repository's usage counter. * * @param location - * where the local repository is. Typically a {@link FileKey}. + * where the local repository is. Typically a + * {@link org.eclipse.jgit.lib.RepositoryCache.FileKey}. * @param mustExist * If true, and the repository is not found, throws {@code * RepositoryNotFoundException}. If false, a repository instance * is created and registered anyway. * @return the repository instance requested; caller must close when done. - * @throws IOException + * @throws java.io.IOException * the repository could not be read (likely its core.version * property is not supported). * @throws RepositoryNotFoundException @@ -112,9 +124,10 @@ * Register one repository into the cache. *

    * During registration the cache automatically increments the usage counter, - * permitting it to retain the reference. A {@link FileKey} for the - * repository's {@link Repository#getDirectory()} is used to index the - * repository in the cache. + * permitting it to retain the reference. A + * {@link org.eclipse.jgit.lib.RepositoryCache.FileKey} for the repository's + * {@link org.eclipse.jgit.lib.Repository#getDirectory()} is used to index + * the repository in the cache. *

    * If another repository already is registered in the cache at this * location, the other instance is closed. @@ -130,26 +143,46 @@ } /** - * Remove a repository from the cache. + * Close and remove a repository from the cache. *

    - * Removes a repository from the cache, if it is still registered here, - * permitting it to close. + * Removes a repository from the cache, if it is still registered here, and + * close it. * * @param db * repository to unregister. */ - public static void close(final Repository db) { + public static void close(@NonNull final Repository db) { if (db.getDirectory() != null) { FileKey key = FileKey.exact(db.getDirectory(), db.getFS()); - cache.unregisterRepository(key); + cache.unregisterAndCloseRepository(key); } } /** * Remove a repository from the cache. *

    - * Removes a repository from the cache, if it is still registered here, - * permitting it to close. + * Removes a repository from the cache, if it is still registered here. This + * method will not close the repository, only remove it from the cache. See + * {@link org.eclipse.jgit.lib.RepositoryCache#close(Repository)} to remove + * and close the repository. + * + * @param db + * repository to unregister. + * @since 4.3 + */ + public static void unregister(final Repository db) { + if (db.getDirectory() != null) { + unregister(FileKey.exact(db.getDirectory(), db.getFS())); + } + } + + /** + * Remove a repository from the cache. + *

    + * Removes a repository from the cache, if it is still registered here. This + * method will not close the repository, only remove it from the cache. See + * {@link org.eclipse.jgit.lib.RepositoryCache#close(Repository)} to remove + * and close the repository. * * @param location * location of the repository to remove. @@ -160,6 +193,8 @@ } /** + * Get the locations of all repositories registered in the cache. + * * @return the locations of all repositories registered in the cache. * @since 4.1 */ @@ -167,75 +202,133 @@ return cache.getKeys(); } - /** Unregister all repositories from the cache. */ + static boolean isCached(@NonNull Repository repo) { + File gitDir = repo.getDirectory(); + if (gitDir == null) { + return false; + } + FileKey key = new FileKey(gitDir, repo.getFS()); + return cache.cacheMap.get(key) == repo; + } + + /** + * Unregister all repositories from the cache. + */ public static void clear() { cache.clearAll(); } - private final ConcurrentHashMap> cacheMap; + static void clearExpired() { + cache.clearAllExpired(); + } + + static void reconfigure(RepositoryCacheConfig repositoryCacheConfig) { + cache.configureEviction(repositoryCacheConfig); + } + + private final ConcurrentHashMap cacheMap; private final Lock[] openLocks; + private ScheduledFuture cleanupTask; + + private volatile long expireAfter; + private RepositoryCache() { - cacheMap = new ConcurrentHashMap>(); + cacheMap = new ConcurrentHashMap<>(); openLocks = new Lock[4]; - for (int i = 0; i < openLocks.length; i++) + for (int i = 0; i < openLocks.length; i++) { openLocks[i] = new Lock(); + } + configureEviction(new RepositoryCacheConfig()); + } + + private void configureEviction( + RepositoryCacheConfig repositoryCacheConfig) { + expireAfter = repositoryCacheConfig.getExpireAfter(); + ScheduledThreadPoolExecutor scheduler = WorkQueue.getExecutor(); + synchronized (scheduler) { + if (cleanupTask != null) { + cleanupTask.cancel(false); + } + long delay = repositoryCacheConfig.getCleanupDelay(); + if (delay == RepositoryCacheConfig.NO_CLEANUP) { + return; + } + cleanupTask = scheduler.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + try { + cache.clearAllExpired(); + } catch (Throwable e) { + LOG.error(e.getMessage(), e); + } + } + }, delay, delay, TimeUnit.MILLISECONDS); + } } - @SuppressWarnings("resource") private Repository openRepository(final Key location, final boolean mustExist) throws IOException { - Reference ref = cacheMap.get(location); - Repository db = ref != null ? ref.get() : null; + Repository db = cacheMap.get(location); if (db == null) { synchronized (lockFor(location)) { - ref = cacheMap.get(location); - db = ref != null ? ref.get() : null; + db = cacheMap.get(location); if (db == null) { db = location.open(mustExist); - ref = new SoftReference(db); - cacheMap.put(location, ref); + cacheMap.put(location, db); + } else { + db.incrementOpen(); } } + } else { + db.incrementOpen(); } - db.incrementOpen(); return db; } private void registerRepository(final Key location, final Repository db) { - db.incrementOpen(); - SoftReference newRef = new SoftReference(db); - Reference oldRef = cacheMap.put(location, newRef); - Repository oldDb = oldRef != null ? oldRef.get() : null; + Repository oldDb = cacheMap.put(location, db); if (oldDb != null) oldDb.close(); } - private void unregisterRepository(final Key location) { - Reference oldRef = cacheMap.remove(location); - Repository oldDb = oldRef != null ? oldRef.get() : null; - if (oldDb != null) - oldDb.close(); + private Repository unregisterRepository(final Key location) { + return cacheMap.remove(location); + } + + private boolean isExpired(Repository db) { + return db != null && db.useCnt.get() <= 0 + && (System.currentTimeMillis() - db.closedAt.get() > expireAfter); + } + + private void unregisterAndCloseRepository(final Key location) { + synchronized (lockFor(location)) { + Repository oldDb = unregisterRepository(location); + if (oldDb != null) { + oldDb.doClose(); + } + } } private Collection getKeys() { - return new ArrayList(cacheMap.keySet()); + return new ArrayList<>(cacheMap.keySet()); } - private void clearAll() { - for (int stage = 0; stage < 2; stage++) { - for (Iterator>> i = cacheMap - .entrySet().iterator(); i.hasNext();) { - final Map.Entry> e = i.next(); - final Repository db = e.getValue().get(); - if (db != null) - db.close(); - i.remove(); + private void clearAllExpired() { + for (Repository db : cacheMap.values()) { + if (isExpired(db)) { + RepositoryCache.close(db); } } } + private void clearAll() { + for (Key k : cacheMap.keySet()) { + unregisterAndCloseRepository(k); + } + } + private Lock lockFor(final Key location) { return openLocks[(location.hashCode() >>> 1) % openLocks.length]; } @@ -346,6 +439,7 @@ return path; } + @Override public Repository open(final boolean mustExist) throws IOException { if (mustExist && !isGitRepository(path, fs)) throw new RepositoryNotFoundException(path); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java 2019-09-03 12:37:49.000000000 +0000 @@ -4,6 +4,7 @@ * Copyright (C) 2006-2010, Robin Rosenberg * Copyright (C) 2006-2012, Shawn O. Pearce * Copyright (C) 2012, Daniel Megert + * Copyright (C) 2017, Wim Jongman * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -47,11 +48,14 @@ package org.eclipse.jgit.lib; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; + import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Collection; @@ -63,7 +67,12 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.CorruptObjectException; @@ -89,7 +98,8 @@ import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a Git repository. @@ -97,17 +107,42 @@ * A repository holds all objects and refs used for managing source code (could * be any type of file, but source code is what SCM's are typically used for). *

    - * This class is thread-safe. + * The thread-safety of a {@link org.eclipse.jgit.lib.Repository} very much + * depends on the concrete implementation. Applications working with a generic + * {@code Repository} type must not assume the instance is thread-safe. + *

      + *
    • {@code FileRepository} is thread-safe. + *
    • {@code DfsRepository} thread-safety is determined by its subclass. + *
    */ public abstract class Repository implements AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(Repository.class); private static final ListenerList globalListeners = new ListenerList(); - /** @return the global listener list observing all events in this JVM. */ + /** + * Branch names containing slashes should not have a name component that is + * one of the reserved device names on Windows. + * + * @see #normalizeBranchName(String) + */ + private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern + .compile( + "(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?", //$NON-NLS-1$ + Pattern.CASE_INSENSITIVE); + + /** + * Get the global listener list observing all events in this JVM. + * + * @return the global listener list observing all events in this JVM. + */ public static ListenerList getGlobalListenerList() { return globalListeners; } - private final AtomicInteger useCnt = new AtomicInteger(1); + /** Use counter */ + final AtomicInteger useCnt = new AtomicInteger(1); + + final AtomicLong closedAt = new AtomicLong(); /** Metadata directory holding the repository's critical files. */ private final File gitDir; @@ -136,7 +171,12 @@ indexFile = options.getIndexFile(); } - /** @return listeners observing only events on this repository. */ + /** + * Get listeners observing only events on this repository. + * + * @return listeners observing only events on this repository. + */ + @NonNull public ListenerList getListenerList() { return myListeners; } @@ -162,7 +202,7 @@ * Repository with working tree is created using this method. This method is * the same as {@code create(false)}. * - * @throws IOException + * @throws java.io.IOException * @see #create(boolean) */ public void create() throws IOException { @@ -176,48 +216,106 @@ * @param bare * if true, a bare repository (a repository without a working * directory) is created. - * @throws IOException + * @throws java.io.IOException * in case of IO problem */ public abstract void create(boolean bare) throws IOException; - /** @return local metadata directory; null if repository isn't local. */ + /** + * Get local metadata directory + * + * @return local metadata directory; {@code null} if repository isn't local. + */ + /* + * TODO This method should be annotated as Nullable, because in some + * specific configurations metadata is not located in the local file system + * (for example in memory databases). In "usual" repositories this + * annotation would only cause compiler errors at places where the actual + * directory can never be null. + */ public File getDirectory() { return gitDir; } /** + * Get the object database which stores this repository's data. + * * @return the object database which stores this repository's data. */ + @NonNull public abstract ObjectDatabase getObjectDatabase(); - /** @return a new inserter to create objects in {@link #getObjectDatabase()} */ + /** + * Create a new inserter to create objects in {@link #getObjectDatabase()}. + * + * @return a new inserter to create objects in {@link #getObjectDatabase()}. + */ + @NonNull public ObjectInserter newObjectInserter() { return getObjectDatabase().newInserter(); } - /** @return a new reader to read objects from {@link #getObjectDatabase()} */ + /** + * Create a new reader to read objects from {@link #getObjectDatabase()}. + * + * @return a new reader to read objects from {@link #getObjectDatabase()}. + */ + @NonNull public ObjectReader newObjectReader() { return getObjectDatabase().newReader(); } - /** @return the reference database which stores the reference namespace. */ + /** + * Get the reference database which stores the reference namespace. + * + * @return the reference database which stores the reference namespace. + */ + @NonNull public abstract RefDatabase getRefDatabase(); /** - * @return the configuration of this repository + * Get the configuration of this repository. + * + * @return the configuration of this repository. */ + @NonNull public abstract StoredConfig getConfig(); /** - * @return the used file system abstraction + * Create a new {@link org.eclipse.jgit.attributes.AttributesNodeProvider}. + * + * @return a new {@link org.eclipse.jgit.attributes.AttributesNodeProvider}. + * This {@link org.eclipse.jgit.attributes.AttributesNodeProvider} + * is lazy loaded only once. It means that it will not be updated + * after loading. Prefer creating new instance for each use. + * @since 4.2 + */ + @NonNull + public abstract AttributesNodeProvider createAttributesNodeProvider(); + + /** + * Get the used file system abstraction. + * + * @return the used file system abstraction, or or {@code null} if + * repository isn't local. + */ + /* + * TODO This method should be annotated as Nullable, because in some + * specific configurations metadata is not located in the local file system + * (for example in memory databases). In "usual" repositories this + * annotation would only cause compiler errors at places where the actual + * directory can never be null. */ public FS getFS() { return fs; } /** + * Whether the specified object is stored in this repo or any of the known + * shared repositories. + * * @param objectId + * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @return true if the specified object is stored in this repo or any of the * known shared repositories. */ @@ -238,12 +336,14 @@ * * @param objectId * identity of the object to open. - * @return a {@link ObjectLoader} for accessing the object. - * @throws MissingObjectException + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the + * object. + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ + @NonNull public ObjectLoader open(final AnyObjectId objectId) throws MissingObjectException, IOException { return getObjectDatabase().open(objectId); @@ -259,18 +359,20 @@ * identity of the object to open. * @param typeHint * hint about the type of object being requested, e.g. - * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if - * the object type is not known, or does not matter to the - * caller. - * @return a {@link ObjectLoader} for accessing the object. - * @throws MissingObjectException + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}; + * {@link org.eclipse.jgit.lib.ObjectReader#OBJ_ANY} if the + * object type is not known, or does not matter to the caller. + * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the + * object. + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does * not match typeHint. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ + @NonNull public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -285,10 +387,11 @@ * @return an update command. The caller must finish populating this command * and then invoke one of the update methods to actually make a * change. - * @throws IOException + * @throws java.io.IOException * a symbolic ref was passed in and could not be resolved back * to the base ref, as the symbolic ref could not be read. */ + @NonNull public RefUpdate updateRef(final String ref) throws IOException { return updateRef(ref, false); } @@ -303,10 +406,11 @@ * @return an update command. The caller must finish populating this command * and then invoke one of the update methods to actually make a * change. - * @throws IOException + * @throws java.io.IOException * a symbolic ref was passed in and could not be resolved back * to the base ref, as the symbolic ref could not be read. */ + @NonNull public RefUpdate updateRef(final String ref, final boolean detach) throws IOException { return getRefDatabase().newUpdate(ref, detach); } @@ -319,10 +423,10 @@ * @param toRef * name of ref to rename to * @return an update command that knows how to rename a branch to another. - * @throws IOException + * @throws java.io.IOException * the rename could not be performed. - * */ + @NonNull public RefRename renameRef(final String fromRef, final String toRef) throws IOException { return getRefDatabase().newRename(fromRef, toRef); } @@ -362,20 +466,22 @@ * * @param revstr * A git object references expression - * @return an ObjectId or null if revstr can't be resolved to any ObjectId - * @throws AmbiguousObjectException + * @return an ObjectId or {@code null} if revstr can't be resolved to any + * ObjectId + * @throws org.eclipse.jgit.errors.AmbiguousObjectException * {@code revstr} contains an abbreviated ObjectId and this * repository contains more than one object which match to the * input abbreviation. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the id parsed does not meet the type required to finish * applying the operators in the expression. - * @throws RevisionSyntaxException + * @throws org.eclipse.jgit.errors.RevisionSyntaxException * the expression is not supported by this implementation, or * does not meet the standard syntax. - * @throws IOException + * @throws java.io.IOException * on serious errors */ + @Nullable public ObjectId resolve(final String revstr) throws AmbiguousObjectException, IncorrectObjectTypeException, RevisionSyntaxException, IOException { @@ -396,11 +502,13 @@ * Thus this method can be used to process an expression to a method that * expects a branch or revision id. * - * @param revstr - * @return object id or ref name from resolved expression - * @throws AmbiguousObjectException - * @throws IOException + * @param revstr a {@link java.lang.String} object. + * @return object id or ref name from resolved expression or {@code null} if + * given expression cannot be resolved + * @throws org.eclipse.jgit.errors.AmbiguousObjectException + * @throws java.io.IOException */ + @Nullable public String simplify(final String revstr) throws AmbiguousObjectException, IOException { try (RevWalk rw = new RevWalk(this)) { @@ -414,6 +522,7 @@ } } + @Nullable private Object resolve(final RevWalk rw, final String revstr) throws IOException { char[] revChars = revstr.toCharArray(); @@ -598,7 +707,10 @@ // detached name = Constants.HEAD; if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$ - throw new RevisionSyntaxException(revstr); + throw new RevisionSyntaxException(MessageFormat + .format(JGitText.get().invalidRefName, + name), + revstr); Ref ref = getRef(name); name = null; if (ref == null) @@ -648,7 +760,10 @@ if (name.equals("")) //$NON-NLS-1$ name = Constants.HEAD; if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$ - throw new RevisionSyntaxException(revstr); + throw new RevisionSyntaxException(MessageFormat + .format(JGitText.get().invalidRefName, + name), + revstr); Ref ref = getRef(name); name = null; if (ref == null) @@ -697,7 +812,9 @@ return null; name = revstr.substring(done); if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$ - throw new RevisionSyntaxException(revstr); + throw new RevisionSyntaxException( + MessageFormat.format(JGitText.get().invalidRefName, name), + revstr); if (getRef(name) != null) return name; return resolveSimple(name); @@ -717,11 +834,13 @@ return true; } + @Nullable private RevObject parseSimple(RevWalk rw, String revstr) throws IOException { ObjectId id = resolveSimple(revstr); return id != null ? rw.parseAny(id) : null; } + @Nullable private ObjectId resolveSimple(final String revstr) throws IOException { if (ObjectId.isId(revstr)) return ObjectId.fromString(revstr); @@ -749,6 +868,7 @@ return null; } + @Nullable private String resolveReflogCheckout(int checkoutNo) throws IOException { ReflogReader reader = getReflogReader(Constants.HEAD); @@ -790,6 +910,7 @@ return rw.parseCommit(entry.getNewId()); } + @Nullable private ObjectId resolveAbbreviation(final String revstr) throws IOException, AmbiguousObjectException { AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr); @@ -804,15 +925,40 @@ } } - /** Increment the use counter by one, requiring a matched {@link #close()}. */ + /** + * Increment the use counter by one, requiring a matched {@link #close()}. + */ public void incrementOpen() { useCnt.incrementAndGet(); } - /** Decrement the use count, and maybe close resources. */ + /** + * {@inheritDoc} + *

    + * Decrement the use count, and maybe close resources. + */ + @Override public void close() { - if (useCnt.decrementAndGet() == 0) { - doClose(); + int newCount = useCnt.decrementAndGet(); + if (newCount == 0) { + if (RepositoryCache.isCached(this)) { + closedAt.set(System.currentTimeMillis()); + } else { + doClose(); + } + } else if (newCount == -1) { + // should not happen, only log when useCnt became negative to + // minimize number of log entries + String message = MessageFormat.format(JGitText.get().corruptUseCnt, + toString()); + if (LOG.isDebugEnabled()) { + LOG.debug(message, new IllegalStateException()); + } else { + LOG.warn(message); + } + if (RepositoryCache.isCached(this)) { + closedAt.set(System.currentTimeMillis()); + } } } @@ -826,15 +972,18 @@ getRefDatabase().close(); } - @SuppressWarnings("nls") + /** {@inheritDoc} */ + @Override + @NonNull public String toString() { String desc; - if (getDirectory() != null) - desc = getDirectory().getPath(); + File directory = getDirectory(); + if (directory != null) + desc = directory.getPath(); else desc = getClass().getSimpleName() + "-" //$NON-NLS-1$ + System.identityHashCode(this); - return "Repository[" + desc + "]"; //$NON-NLS-1$ + return "Repository[" + desc + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } /** @@ -843,25 +992,31 @@ * This is essentially the same as doing: * *

    -	 * return getRef(Constants.HEAD).getTarget().getName()
    +	 * return exactRef(Constants.HEAD).getTarget().getName()
     	 * 
    * * 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}), - * 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 + * an ObjectId in hex format if the current branch is detached, or + * {@code null} if the repository is corrupt and has no HEAD + * reference. + * @throws java.io.IOException */ + @Nullable public String getFullBranch() throws IOException { - Ref head = getRef(Constants.HEAD); - if (head == null) + Ref head = exactRef(Constants.HEAD); + if (head == null) { return null; - if (head.isSymbolic()) + } + if (head.isSymbolic()) { return head.getTarget().getName(); - if (head.getObjectId() != null) - return head.getObjectId().name(); + } + ObjectId objectId = head.getObjectId(); + if (objectId != null) { + return objectId.name(); + } return null; } @@ -872,16 +1027,17 @@ * 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}), 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 + * @return name of current branch (for example {@code master}), an ObjectId + * in hex format if the current branch is detached, or {@code null} + * if the repository is corrupt and has no HEAD reference. + * @throws java.io.IOException */ + @Nullable public String getBranch() throws IOException { String name = getFullBranch(); if (name != null) return shortenRefName(name); - return name; + return null; } /** @@ -894,6 +1050,7 @@ * * @return unmodifiable collection of other known objects. */ + @NonNull public Set getAdditionalHaves() { return Collections.emptySet(); } @@ -905,34 +1062,77 @@ * the name of the ref to lookup. May be a short-hand form, e.g. * "master" which is is automatically expanded to * "refs/heads/master" if "refs/heads/master" already exists. - * @return the Ref with the given name, or null if it does not exist - * @throws IOException + * @return the Ref with the given name, or {@code null} if it does not exist + * @throws java.io.IOException + * @deprecated Use {@link #exactRef(String)} or {@link #findRef(String)} + * instead. */ + @Deprecated + @Nullable public Ref getRef(final String name) throws IOException { + return findRef(name); + } + + /** + * Get a ref by name. + * + * @param name + * the name of the ref to lookup. Must not be a short-hand + * form; e.g., "master" is not automatically expanded to + * "refs/heads/master". + * @return the Ref with the given name, or {@code null} if it does not exist + * @throws java.io.IOException + * @since 4.2 + */ + @Nullable + public Ref exactRef(String name) throws IOException { + return getRefDatabase().exactRef(name); + } + + /** + * Search for a ref by (possibly abbreviated) name. + * + * @param name + * the name of the ref to lookup. May be a short-hand form, e.g. + * "master" which is is automatically expanded to + * "refs/heads/master" if "refs/heads/master" already exists. + * @return the Ref with the given name, or {@code null} if it does not exist + * @throws java.io.IOException + * @since 4.2 + */ + @Nullable + public Ref findRef(String name) throws IOException { return getRefDatabase().getRef(name); } /** + * Get mutable map of all known refs, including symrefs like HEAD that may + * not point to any object yet. + * * @return mutable map of all known refs (heads, tags, remotes). */ + @NonNull public Map getAllRefs() { try { return getRefDatabase().getRefs(RefDatabase.ALL); } catch (IOException e) { - return new HashMap(); + return new HashMap<>(); } } /** + * Get mutable map of all tags + * * @return mutable map of all tags; key is short tag name ("v1.0") and value * of the entry contains the ref with the full tag name * ("refs/tags/v1.0"). */ + @NonNull public Map getTags() { try { return getRefDatabase().getRefs(Constants.R_TAGS); } catch (IOException e) { - return new HashMap(); + return new HashMap<>(); } } @@ -940,7 +1140,8 @@ * Peel a possibly unpeeled reference to an annotated tag. *

    * If the ref cannot be peeled (as it does not refer to an annotated tag) - * the peeled id stays null, but {@link Ref#isPeeled()} will be true. + * the peeled id stays null, but {@link org.eclipse.jgit.lib.Ref#isPeeled()} + * will be true. * * @param ref * The ref to peel @@ -949,6 +1150,7 @@ * will be true and getPeeledObjectId will contain the peeled object * (or null). */ + @NonNull public Ref peel(final Ref ref) { try { return getRefDatabase().peel(ref); @@ -961,11 +1163,14 @@ } /** + * Get a map with all objects referenced by a peeled ref. + * * @return a map with all objects referenced by a peeled ref. */ + @NonNull public Map> getAllRefsByPeeledObjectId() { Map allRefs = getAllRefs(); - Map> ret = new HashMap>(allRefs.size()); + Map> ret = new HashMap<>(allRefs.size()); for (Ref ref : allRefs.values()) { ref = peel(ref); AnyObjectId target = ref.getPeeledObjectId(); @@ -977,7 +1182,7 @@ // that was not the case (rare) if (oset.size() == 1) { // Was a read-only singleton, we must copy to a new Set - oset = new HashSet(oset); + oset = new HashSet<>(oset); } ret.put(target, oset); oset.add(ref); @@ -987,11 +1192,15 @@ } /** - * @return the index file location - * @throws NoWorkTreeException + * Get the index file location or {@code null} if repository isn't local. + * + * @return the index file location or {@code null} if repository isn't + * local. + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @NonNull public File getIndexFile() throws NoWorkTreeException { if (isBare()) throw new NoWorkTreeException(); @@ -999,6 +1208,33 @@ } /** + * Locate a reference to a commit and immediately parse its content. + *

    + * This method only returns successfully if the commit object exists, + * is verified to be a commit, and was parsed without error. + * + * @param id + * name of the commit object. + * @return reference to the commit object. Never null. + * @throws org.eclipse.jgit.errors.MissingObjectException + * the supplied commit does not exist. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * the supplied id is not a commit or an annotated tag. + * @throws java.io.IOException + * a pack file or loose object could not be read. + * @since 4.8 + */ + public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException, + IOException, MissingObjectException { + if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) { + return (RevCommit) id; + } + try (RevWalk walk = new RevWalk(this)) { + return walk.parseCommit(id); + } + } + + /** * Create a new in-core index representation and read an index from disk. *

    * The new index will be read before it is returned to the caller. Read @@ -1007,15 +1243,16 @@ * * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws NoWorkTreeException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ + @NonNull public DirCache readDirCache() throws NoWorkTreeException, CorruptObjectException, IOException { return DirCache.read(this); @@ -1030,22 +1267,23 @@ * * @return a cache representing the contents of the specified index file (if * it exists) or an empty cache if the file does not exist. - * @throws NoWorkTreeException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. - * @throws IOException + * @throws java.io.IOException * the index file is present but could not be read, or the lock * could not be obtained. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the index file is using a format or extension that this * library does not support. */ + @NonNull public DirCache lockDirCache() throws NoWorkTreeException, CorruptObjectException, IOException { // we want DirCache to inform us so that we can inform registered // listeners about index changes IndexChangedListener l = new IndexChangedListener() { - + @Override public void onIndexChanged(IndexChangedEvent event) { notifyIndexChanged(); } @@ -1053,18 +1291,12 @@ return DirCache.lock(this, l); } - static byte[] gitInternalSlash(byte[] bytes) { - if (File.separatorChar == '/') - return bytes; - for (int i=0; i + * Future implementations of this method could be more restrictive or more + * lenient about the validity of specific characters in the returned name. + *

    + * The current implementation returns the trimmed input string if this is + * already a valid branch name. Otherwise it returns a trimmed string with + * special characters not allowed by {@link #isValidRefName(String)} + * replaced by hyphens ('-') and blanks replaced by underscores ('_'). + * Leading and trailing slashes, dots, hyphens, and underscores are removed. + * + * @param name + * to normalize + * @return The normalized name or an empty String if it is {@code null} or + * empty. + * @since 4.7 + * @see #isValidRefName(String) + */ + public static String normalizeBranchName(String name) { + if (name == null || name.isEmpty()) { + return ""; //$NON-NLS-1$ + } + String result = name.trim(); + String fullName = result.startsWith(Constants.R_HEADS) ? result + : Constants.R_HEADS + result; + if (isValidRefName(fullName)) { + return result; + } + + // All Unicode blanks to underscore + result = result.replaceAll("(?:\\h|\\v)+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ + StringBuilder b = new StringBuilder(); + char p = '/'; + for (int i = 0, len = result.length(); i < len; i++) { + char c = result.charAt(i); + if (c < ' ' || c == 127) { + continue; + } + // Substitute a dash for problematic characters + switch (c) { + case '\\': + case '^': + case '~': + case ':': + case '?': + case '*': + case '[': + case '@': + case '<': + case '>': + case '|': + case '"': + c = '-'; + break; + default: + break; + } + // Collapse multiple slashes, dashes, dots, underscores, and omit + // dashes, dots, and underscores following a slash. + switch (c) { + case '/': + if (p == '/') { + continue; + } + p = '/'; + break; + case '.': + case '_': + case '-': + if (p == '/' || p == '-') { + continue; + } + p = '-'; + break; + default: + p = c; + break; + } + b.append(c); + } + // Strip trailing special characters, and avoid the .lock extension + result = b.toString().replaceFirst("[/_.-]+$", "") //$NON-NLS-1$ //$NON-NLS-2$ + .replaceAll("\\.lock($|/)", "_lock$1"); //$NON-NLS-1$ //$NON-NLS-2$ + return FORBIDDEN_BRANCH_NAME_COMPONENTS.matcher(result) + .replaceAll("$1+$2$3"); //$NON-NLS-1$ + } + + /** * Strip work dir and return normalized repository path. * - * @param workDir Work dir - * @param file File whose path shall be stripped of its workdir - * @return normalized repository relative path or the empty - * string if the file is not relative to the work directory. + * @param workDir + * Work dir + * @param file + * File whose path shall be stripped of its workdir + * @return normalized repository relative path or the empty string if the + * file is not relative to the work directory. */ + @NonNull public static String stripWorkDir(File workDir, File file) { final String filePath = file.getPath(); final String workDirPath = workDir.getPath(); @@ -1228,6 +1554,8 @@ } /** + * Whether this repository is bare + * * @return true if this is bare, which implies it has no working directory. */ public boolean isBare() { @@ -1235,12 +1563,16 @@ } /** + * Get the root directory of the working tree, where files are checked out + * for viewing and editing. + * * @return the root directory of the working tree, where files are checked * out for viewing and editing. - * @throws NoWorkTreeException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @NonNull public File getWorkTree() throws NoWorkTreeException { if (isBare()) throw new NoWorkTreeException(); @@ -1250,7 +1582,7 @@ /** * Force a scan for changed refs. * - * @throws IOException + * @throws java.io.IOException */ public abstract void scanForRepoChanges() throws IOException; @@ -1260,10 +1592,13 @@ public abstract void notifyIndexChanged(); /** - * @param refName + * Get a shortened more user friendly ref name * + * @param refName + * a {@link java.lang.String} object. * @return a more user friendly ref name */ + @NonNull public static String shortenRefName(String refName) { if (refName.startsWith(Constants.R_HEADS)) return refName.substring(Constants.R_HEADS.length()); @@ -1275,13 +1610,17 @@ } /** + * Get a shortened more user friendly remote tracking branch name + * * @param refName + * a {@link java.lang.String} object. * @return the remote branch name part of refName, i.e. without * the refs/remotes/<remote> prefix, if * refName represents a remote tracking branch; - * otherwise null. + * otherwise {@code null}. * @since 3.4 */ + @Nullable public String shortenRemoteBranchName(String refName) { for (String remote : getRemoteNames()) { String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$ @@ -1292,13 +1631,17 @@ } /** + * Get remote name + * * @param refName + * a {@link java.lang.String} object. * @return the remote name part of refName, i.e. without the * refs/remotes/<remote> prefix, if * refName represents a remote tracking branch; - * otherwise null. + * otherwise {@code null}. * @since 3.4 */ + @Nullable public String getRemoteName(String refName) { for (String remote : getRemoteNames()) { String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$ @@ -1309,13 +1652,44 @@ } /** + * Read the {@code GIT_DIR/description} file for gitweb. + * + * @return description text; null if no description has been configured. + * @throws java.io.IOException + * description cannot be accessed. + * @since 4.6 + */ + @Nullable + public String getGitwebDescription() throws IOException { + return null; + } + + /** + * Set the {@code GIT_DIR/description} file for gitweb. + * + * @param description + * new description; null to clear the description. + * @throws java.io.IOException + * description cannot be persisted. + * @since 4.6 + */ + public void setGitwebDescription(@Nullable String description) + throws IOException { + throw new IOException(JGitText.get().unsupportedRepositoryDescription); + } + + /** + * Get the reflog reader + * * @param refName - * @return a {@link ReflogReader} for the supplied refname, or null if the - * named ref does not exist. - * @throws IOException + * a {@link java.lang.String} object. + * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied + * refname, or {@code null} if the named ref does not exist. + * @throws java.io.IOException * the ref could not be accessed. * @since 3.0 */ + @Nullable public abstract ReflogReader getReflogReader(String refName) throws IOException; @@ -1326,11 +1700,12 @@ * * @return a String containing the content of the MERGE_MSG file or * {@code null} if this file doesn't exist - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public String readMergeCommitMsg() throws IOException, NoWorkTreeException { return readCommitMsgFile(Constants.MERGE_MSG); } @@ -1344,8 +1719,7 @@ * @param msg * the message which should be written or null to * delete the file - * - * @throws IOException + * @throws java.io.IOException */ public void writeMergeCommitMsg(String msg) throws IOException { File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG); @@ -1359,12 +1733,13 @@ * * @return a String containing the content of the COMMIT_EDITMSG file or * {@code null} if this file doesn't exist - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. * @since 4.0 */ + @Nullable public String readCommitEditMsg() throws IOException, NoWorkTreeException { return readCommitMsgFile(Constants.COMMIT_EDITMSG); } @@ -1377,8 +1752,7 @@ * @param msg * the message which should be written or {@code null} to delete * the file - * - * @throws IOException + * @throws java.io.IOException * @since 4.0 */ public void writeCommitEditMsg(String msg) throws IOException { @@ -1394,11 +1768,12 @@ * @return a list of commits which IDs are listed in the MERGE_HEAD file or * {@code null} if this file doesn't exist. Also if the file exists * but is empty {@code null} will be returned - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public List readMergeHeads() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1407,7 +1782,7 @@ if (raw == null) return null; - LinkedList heads = new LinkedList(); + LinkedList heads = new LinkedList<>(); for (int p = 0; p < raw.length;) { heads.add(ObjectId.fromString(raw, p)); p = RawParseUtils @@ -1425,7 +1800,7 @@ * @param heads * a list of commits which IDs should be written to * $GIT_DIR/MERGE_HEAD or null to delete the file - * @throws IOException + * @throws java.io.IOException */ public void writeMergeHeads(List heads) throws IOException { writeHeadsFile(heads, Constants.MERGE_HEAD); @@ -1437,11 +1812,12 @@ * @return object id from CHERRY_PICK_HEAD file or {@code null} if this file * doesn't exist. Also if the file exists but is empty {@code null} * will be returned - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readCherryPickHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) @@ -1460,11 +1836,12 @@ * @return object id from REVERT_HEAD file or {@code null} if this file * doesn't exist. Also if the file exists but is empty {@code null} * will be returned - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readRevertHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1482,7 +1859,7 @@ * @param head * an object id of the cherry commit or null to * delete the file - * @throws IOException + * @throws java.io.IOException */ public void writeCherryPickHead(ObjectId head) throws IOException { List heads = (head != null) ? Collections.singletonList(head) @@ -1497,7 +1874,7 @@ * @param head * an object id of the revert commit or null to * delete the file - * @throws IOException + * @throws java.io.IOException */ public void writeRevertHead(ObjectId head) throws IOException { List heads = (head != null) ? Collections.singletonList(head) @@ -1511,7 +1888,7 @@ * @param head * an object id of the original HEAD commit or null * to delete the file - * @throws IOException + * @throws java.io.IOException */ public void writeOrigHead(ObjectId head) throws IOException { List heads = head != null ? Collections.singletonList(head) @@ -1525,11 +1902,12 @@ * @return object id from ORIG_HEAD file or {@code null} if this file * doesn't exist. Also if the file exists but is empty {@code null} * will be returned - * @throws IOException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readOrigHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1545,11 +1923,12 @@ * * @return a String containing the content of the SQUASH_MSG file or * {@code null} if this file doesn't exist - * @throws IOException + * @throws java.io.IOException * @throws NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public String readSquashCommitMsg() throws IOException { return readCommitMsgFile(Constants.SQUASH_MSG); } @@ -1563,14 +1942,14 @@ * @param msg * the message which should be written or null to * delete the file - * - * @throws IOException + * @throws java.io.IOException */ public void writeSquashCommitMsg(String msg) throws IOException { File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG); writeCommitMsg(squashMsgFile, msg); } + @Nullable private String readCommitMsgFile(String msgFilename) throws IOException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1579,6 +1958,9 @@ try { return RawParseUtils.decode(IO.readFully(mergeMsgFile)); } catch (FileNotFoundException e) { + if (mergeMsgFile.exists()) { + throw e; + } // the file has disappeared in the meantime ignore it return null; } @@ -1601,15 +1983,20 @@ * Read a file from the git directory. * * @param filename - * @return the raw contents or null if the file doesn't exist or is empty + * @return the raw contents or {@code null} if the file doesn't exist or is + * empty * @throws IOException */ + @Nullable private byte[] readGitDirectoryFile(String filename) throws IOException { File file = new File(getDirectory(), filename); try { byte[] raw = IO.readFully(file); return raw.length > 0 ? raw : null; } catch (FileNotFoundException notFound) { + if (file.exists()) { + throw notFound; + } return null; } } @@ -1628,15 +2015,12 @@ throws FileNotFoundException, IOException { File headsFile = new File(getDirectory(), filename); if (heads != null) { - BufferedOutputStream bos = new SafeBufferedOutputStream( - new FileOutputStream(headsFile)); - try { + try (OutputStream bos = new BufferedOutputStream( + new FileOutputStream(headsFile))) { for (ObjectId id : heads) { id.copyTo(bos); bos.write('\n'); } - } finally { - bos.close(); } } else { FileUtils.delete(headsFile, FileUtils.SKIP_MISSING); @@ -1654,9 +2038,10 @@ * @param includeComments * true if also comments should be reported * @return the list of steps - * @throws IOException + * @throws java.io.IOException * @since 3.2 */ + @NonNull public List readRebaseTodo(String path, boolean includeComments) throws IOException { @@ -1673,7 +2058,7 @@ * the steps to be written * @param append * whether to append to an existing file or to write a new file - * @throws IOException + * @throws java.io.IOException * @since 3.2 */ public void writeRebaseTodoFile(String path, List steps, @@ -1683,11 +2068,33 @@ } /** + * Get the names of all known remotes + * * @return the names of all known remotes * @since 3.4 */ + @NonNull public Set getRemoteNames() { return getConfig() .getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION); } + + /** + * Check whether any housekeeping is required; if yes, run garbage + * collection; if not, exit without performing any work. Some JGit commands + * run autoGC after performing operations that could create many loose + * objects. + *

    + * Currently this option is supported for repositories of type + * {@code FileRepository} only. See + * {@link org.eclipse.jgit.internal.storage.file.GC#setAuto(boolean)} for + * configuration details. + * + * @param monitor + * to report progress + * @since 4.6 + */ + public void autoGC(ProgressMonitor monitor) { + // default does nothing + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java 2019-09-03 12:37:49.000000000 +0000 @@ -381,7 +381,9 @@ }; /** - * @return true if changing HEAD is sane. + * Whether checkout can be done. + * + * @return whether checkout can be done. */ public abstract boolean canCheckout(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,9 @@ * Persistent configuration that can be stored and loaded from a location. */ public abstract class StoredConfig extends Config { - /** Create a configuration with no default fallback. */ + /** + * Create a configuration with no default fallback. + */ public StoredConfig() { super(); } @@ -73,9 +75,9 @@ * If the configuration does not exist, this configuration is cleared, and * thus behaves the same as though the backing store exists, but is empty. * - * @throws IOException + * @throws java.io.IOException * the configuration could not be read (but does exist). - * @throws ConfigInvalidException + * @throws org.eclipse.jgit.errors.ConfigInvalidException * the configuration is not properly formatted. */ public abstract void load() throws IOException, ConfigInvalidException; @@ -83,11 +85,12 @@ /** * Save the configuration to the persistent store. * - * @throws IOException + * @throws java.io.IOException * the configuration could not be written. */ public abstract void save() throws IOException; + /** {@inheritDoc} */ @Override public void clear() { super.clear(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017, David Pursehouse + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import org.eclipse.jgit.util.StringUtils; + +/** + * Submodule section of a Git configuration file. + * + * @since 4.7 + */ +public class SubmoduleConfig { + + /** + * Config values for submodule.[name].fetchRecurseSubmodules. + */ + public enum FetchRecurseSubmodulesMode implements Config.ConfigEnum { + /** Unconditionally recurse into all populated submodules. */ + YES("true"), //$NON-NLS-1$ + + /** + * Only recurse into a populated submodule when the superproject + * retrieves a commit that updates the submodule's reference to a commit + * that isn't already in the local submodule clone. + */ + ON_DEMAND("on-demand"), //$NON-NLS-1$ + + /** Completely disable recursion. */ + NO("false"); //$NON-NLS-1$ + + private final String configValue; + + private FetchRecurseSubmodulesMode(String configValue) { + this.configValue = configValue; + } + + @Override + public String toConfigValue() { + return configValue; + } + + @Override + public boolean matchConfigValue(String s) { + if (StringUtils.isEmptyOrNull(s)) { + return false; + } + s = s.replace('-', '_'); + return name().equalsIgnoreCase(s) + || configValue.equalsIgnoreCase(s); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,8 +43,12 @@ package org.eclipse.jgit.lib; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** - * A reference that indirectly points at another {@link Ref}. + * A reference that indirectly points at another + * {@link org.eclipse.jgit.lib.Ref}. *

    * A symbolic reference always derives its current value from the target * reference. @@ -62,19 +66,27 @@ * @param target * the ref we reference and derive our value from. */ - public SymbolicRef(String refName, Ref target) { + public SymbolicRef(@NonNull String refName, @NonNull Ref target) { this.name = refName; this.target = target; } + /** {@inheritDoc} */ + @Override + @NonNull public String getName() { return name; } + /** {@inheritDoc} */ + @Override public boolean isSymbolic() { return true; } + /** {@inheritDoc} */ + @Override + @NonNull public Ref getLeaf() { Ref dst = getTarget(); while (dst.isSymbolic()) @@ -82,26 +94,41 @@ return dst; } + /** {@inheritDoc} */ + @Override + @NonNull public Ref getTarget() { return target; } + /** {@inheritDoc} */ + @Override + @Nullable public ObjectId getObjectId() { return getLeaf().getObjectId(); } + /** {@inheritDoc} */ + @Override + @NonNull public Storage getStorage() { return Storage.LOOSE; } + /** {@inheritDoc} */ + @Override + @Nullable public ObjectId getPeeledObjectId() { return getLeaf().getPeeledObjectId(); } + /** {@inheritDoc} */ + @Override public boolean isPeeled() { return getLeaf().isPeeled(); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a symbolic link. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class SymlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public SymlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.SYMLINK; - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" S "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,12 +72,20 @@ private String message; - /** @return the type of object this tag refers to. */ + /** + * Get the type of object this tag refers to. + * + * @return the type of object this tag refers to. + */ public int getObjectType() { return type; } - /** @return the object this tag refers to. */ + /** + * Get the object this tag refers to. + * + * @return the object this tag refers to. + */ public ObjectId getObjectId() { return object; } @@ -105,7 +113,11 @@ setObjectId(obj, obj.getType()); } - /** @return short name of the tag (no {@code refs/tags/} prefix). */ + /** + * Get short name of the tag (no {@code refs/tags/} prefix). + * + * @return short name of the tag (no {@code refs/tags/} prefix). + */ public String getTag() { return tag; } @@ -122,7 +134,11 @@ this.tag = shortName; } - /** @return creator of this tag. May be null. */ + /** + * Get creator of this tag. + * + * @return creator of this tag. May be null. + */ public PersonIdent getTagger() { return tagger; } @@ -137,7 +153,11 @@ tagger = taggerIdent; } - /** @return the complete commit message. */ + /** + * Get the complete commit message. + * + * @return the complete commit message. + */ public String getMessage() { return message; } @@ -160,8 +180,8 @@ */ public byte[] build() { ByteArrayOutputStream os = new ByteArrayOutputStream(); - OutputStreamWriter w = new OutputStreamWriter(os, Constants.CHARSET); - try { + try (OutputStreamWriter w = new OutputStreamWriter(os, + Constants.CHARSET)) { w.write("object "); //$NON-NLS-1$ getObjectId().copyTo(w); w.write('\n'); @@ -183,7 +203,6 @@ w.write('\n'); if (getMessage() != null) w.write(getMessage()); - w.close(); } catch (IOException err) { // This should never occur, the only way to get it above is // for the ByteArrayOutputStream to throw, but it doesn't. @@ -203,6 +222,7 @@ return build(); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,19 +44,26 @@ package org.eclipse.jgit.lib; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; -/** A simple progress reporter printing on a stream. */ +/** + * A simple progress reporter printing on a stream. + */ public class TextProgressMonitor extends BatchingProgressMonitor { private final Writer out; private boolean write; - /** Initialize a new progress monitor. */ + /** + * Initialize a new progress monitor. + */ public TextProgressMonitor() { - this(new PrintWriter(System.err)); + this(new PrintWriter(new OutputStreamWriter(System.err, UTF_8))); } /** @@ -70,6 +77,7 @@ this.write = true; } + /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int workCurr) { StringBuilder s = new StringBuilder(); @@ -77,6 +85,7 @@ send(s); } + /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int workCurr) { StringBuilder s = new StringBuilder(); @@ -94,6 +103,7 @@ s.append(workCurr); } + /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) { StringBuilder s = new StringBuilder(); @@ -101,6 +111,7 @@ send(s); } + /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) { StringBuilder s = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,8 @@ import java.util.concurrent.locks.ReentrantLock; /** - * Wrapper around the general {@link ProgressMonitor} to make it thread safe. + * Wrapper around the general {@link org.eclipse.jgit.lib.ProgressMonitor} to + * make it thread safe. * * Updates to the underlying ProgressMonitor are made only from the thread that * allocated this wrapper. Callers are responsible for ensuring the allocating @@ -87,19 +88,25 @@ this.process = new Semaphore(0); } + /** {@inheritDoc} */ + @Override public void start(int totalTasks) { if (!isMainThread()) throw new IllegalStateException(); pm.start(totalTasks); } + /** {@inheritDoc} */ + @Override public void beginTask(String title, int totalWork) { if (!isMainThread()) throw new IllegalStateException(); pm.beginTask(title, totalWork); } - /** Notify the monitor a worker is starting. */ + /** + * Notify the monitor a worker is starting. + */ public void startWorker() { startWorkers(1); } @@ -114,7 +121,9 @@ workers.addAndGet(count); } - /** Notify the monitor a worker is finished. */ + /** + * Notify the monitor a worker is finished. + */ public void endWorker() { if (workers.decrementAndGet() == 0) process.release(); @@ -137,7 +146,7 @@ * This method can only be invoked by the same thread that allocated this * ThreadSafeProgressMonior. * - * @throws InterruptedException + * @throws java.lang.InterruptedException * if the main thread is interrupted while waiting for * completion of workers. */ @@ -156,11 +165,15 @@ pm.update(cnt); } + /** {@inheritDoc} */ + @Override public void update(int completed) { if (0 == pendingUpdates.getAndAdd(completed)) process.release(); } + /** {@inheritDoc} */ + @Override public boolean isCancelled() { lock.lock(); try { @@ -170,6 +183,8 @@ } } + /** {@inheritDoc} */ + @Override public void endTask() { if (!isMainThread()) throw new IllegalStateException(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -import org.eclipse.jgit.util.RawParseUtils; - -/** - * This class represents an entry in a tree, like a blob or another tree. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public abstract class TreeEntry implements Comparable { - private byte[] nameUTF8; - - private Tree parent; - - private ObjectId id; - - /** - * Construct a named tree entry. - * - * @param myParent - * @param myId - * @param myNameUTF8 - */ - protected TreeEntry(final Tree myParent, final ObjectId myId, - final byte[] myNameUTF8) { - nameUTF8 = myNameUTF8; - parent = myParent; - id = myId; - } - - /** - * @return parent of this tree. - */ - public Tree getParent() { - return parent; - } - - /** - * Delete this entry. - */ - public void delete() { - getParent().removeEntry(this); - detachParent(); - } - - /** - * Detach this entry from it's parent. - */ - public void detachParent() { - parent = null; - } - - void attachParent(final Tree p) { - parent = p; - } - - /** - * @return the repository owning this entry. - */ - public Repository getRepository() { - return getParent().getRepository(); - } - - /** - * @return the raw byte name of this entry. - */ - public byte[] getNameUTF8() { - return nameUTF8; - } - - /** - * @return the name of this entry. - */ - public String getName() { - if (nameUTF8 != null) - return RawParseUtils.decode(nameUTF8); - return null; - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final String n) throws IOException { - rename(Constants.encode(n)); - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final byte[] n) throws IOException { - final Tree t = getParent(); - if (t != null) { - delete(); - } - nameUTF8 = n; - if (t != null) { - t.addEntry(this); - } - } - - /** - * @return true if this entry is new or modified since being loaded. - */ - public boolean isModified() { - return getId() == null; - } - - /** - * Mark this entry as modified. - */ - public void setModified() { - setId(null); - } - - /** - * @return SHA-1 of this tree entry (null for new unhashed entries) - */ - public ObjectId getId() { - return id; - } - - /** - * Set (update) the SHA-1 of this entry. Invalidates the id's of all - * entries above this entry as they will have to be recomputed. - * - * @param n SHA-1 for this entry. - */ - public void setId(final ObjectId n) { - // If we have a parent and our id is being cleared or changed then force - // the parent's id to become unset as it depends on our id. - // - final Tree p = getParent(); - if (p != null && id != n) { - if ((id == null && n != null) || (id != null && n == null) - || !id.equals(n)) { - p.setId(null); - } - } - - id = n; - } - - /** - * @return repository relative name of this entry - */ - public String getFullName() { - final StringBuilder r = new StringBuilder(); - appendFullName(r); - return r.toString(); - } - - /** - * @return repository relative name of the entry - * FIXME better encoding - */ - public byte[] getFullNameUTF8() { - return getFullName().getBytes(); - } - - public int compareTo(final Object o) { - if (this == o) - return 0; - if (o instanceof TreeEntry) - return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o)); - return -1; - } - - /** - * Helper for accessing tree/blob methods. - * - * @param treeEntry - * @return '/' for Tree entries and NUL for non-treeish objects. - */ - final public static int lastChar(TreeEntry treeEntry) { - if (!(treeEntry instanceof Tree)) - return '\0'; - else - return '/'; - } - - /** - * @return mode (type of object) - */ - public abstract FileMode getMode(); - - private void appendFullName(final StringBuilder r) { - final TreeEntry p = getParent(); - final String n = getName(); - if (p != null) { - p.appendFullName(r); - if (r.length() > 0) { - r.append('/'); - } - } - if (n != null) { - r.append(n); - } - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -93,7 +94,9 @@ private TemporaryBuffer.Heap overflowBuffer; - /** Create an empty formatter with a default buffer size. */ + /** + * Create an empty formatter with a default buffer size. + */ public TreeFormatter() { this(8192); } @@ -111,7 +114,7 @@ } /** - * Add a link to a submodule commit, mode is {@link FileMode#GITLINK}. + * Add a link to a submodule commit, mode is {@link org.eclipse.jgit.lib.FileMode#GITLINK}. * * @param name * name of the entry. @@ -123,7 +126,7 @@ } /** - * Add a subtree, mode is {@link FileMode#TREE}. + * Add a subtree, mode is {@link org.eclipse.jgit.lib.FileMode#TREE}. * * @param name * name of the entry. @@ -135,7 +138,7 @@ } /** - * Add a regular file, mode is {@link FileMode#REGULAR_FILE}. + * Add a regular file, mode is {@link org.eclipse.jgit.lib.FileMode#REGULAR_FILE}. * * @param name * name of the entry. @@ -193,6 +196,34 @@ */ public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode, AnyObjectId id) { + append(nameBuf, namePos, nameLen, mode, id, false); + } + + /** + * Append any entry to the tree. + * + * @param nameBuf + * buffer holding the name of the entry. The name should be UTF-8 + * encoded, but file name encoding is not a well defined concept + * in Git. + * @param namePos + * first position within {@code nameBuf} of the name data. + * @param nameLen + * number of bytes from {@code nameBuf} to use as the name. + * @param mode + * mode describing the treatment of {@code id}. + * @param id + * the ObjectId to store in this entry. + * @param allowEmptyName + * allow an empty filename (creating a corrupt tree) + * @since 4.6 + */ + public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode, + AnyObjectId id, boolean allowEmptyName) { + if (nameLen == 0 && !allowEmptyName) { + throw new IllegalArgumentException( + JGitText.get().invalidTreeZeroLengthName); + } if (fmtBuf(nameBuf, namePos, nameLen, mode)) { id.copyRawTo(buf, ptr); ptr += OBJECT_ID_LENGTH; @@ -278,7 +309,7 @@ * @param ins * the inserter to store the tree. * @return computed ObjectId of the tree - * @throws IOException + * @throws java.io.IOException * the tree could not be stored. */ public ObjectId insertTo(ObjectInserter ins) throws IOException { @@ -292,7 +323,7 @@ /** * Compute the ObjectId for this tree * - * @param ins + * @param ins a {@link org.eclipse.jgit.lib.ObjectInserter} object. * @return ObjectId for this tree */ public ObjectId computeId(ObjectInserter ins) { @@ -314,7 +345,8 @@ * This method is not efficient, as it needs to create a copy of the * internal buffer in order to supply an array of the correct size to the * caller. If the buffer is just to pass to an ObjectInserter, consider - * using {@link ObjectInserter#insert(TreeFormatter)} instead. + * using {@link org.eclipse.jgit.lib.ObjectInserter#insert(TreeFormatter)} + * instead. * * @return a copy of this formatter's buffer. */ @@ -333,6 +365,7 @@ } } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,601 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.text.MessageFormat; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.EntryExistsException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * A representation of a Git tree entry. A Tree is a directory in Git. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class Tree extends TreeEntry { - private static final TreeEntry[] EMPTY_TREE = {}; - - /** - * Compare two names represented as bytes. Since git treats names of trees and - * blobs differently we have one parameter that represents a '/' for trees. For - * other objects the value should be NUL. The names are compare by their positive - * byte value (0..255). - * - * A blob and a tree with the same name will not compare equal. - * - * @param a name - * @param b name - * @param lasta '/' if a is a tree, else NUL - * @param lastb '/' if b is a tree, else NUL - * - * @return < 0 if a is sorted before b, 0 if they are the same, else b - */ - public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) { - return compareNames(a, b, 0, b.length, lasta, lastb); - } - - private static final int compareNames(final byte[] a, final byte[] nameUTF8, - final int nameStart, final int nameEnd, final int lasta, int lastb) { - int j,k; - for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) { - final int aj = a[j] & 0xff; - final int bk = nameUTF8[k] & 0xff; - if (aj < bk) - return -1; - else if (aj > bk) - return 1; - } - if (j < a.length) { - int aj = a[j]&0xff; - if (aj < lastb) - return -1; - else if (aj > lastb) - return 1; - else - if (j == a.length - 1) - return 0; - else - return -1; - } - if (k < nameEnd) { - int bk = nameUTF8[k] & 0xff; - if (lasta < bk) - return -1; - else if (lasta > bk) - return 1; - else - if (k == nameEnd - 1) - return 0; - else - return 1; - } - if (lasta < lastb) - return -1; - else if (lasta > lastb) - return 1; - - final int namelength = nameEnd - nameStart; - if (a.length == namelength) - return 0; - else if (a.length < namelength) - return -1; - else - return 1; - } - - private static final byte[] substring(final byte[] s, final int nameStart, - final int nameEnd) { - if (nameStart == 0 && nameStart == s.length) - return s; - final byte[] n = new byte[nameEnd - nameStart]; - System.arraycopy(s, nameStart, n, 0, n.length); - return n; - } - - private static final int binarySearch(final TreeEntry[] entries, - final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) { - if (entries.length == 0) - return -1; - int high = entries.length; - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8, - nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last); - if (cmp < 0) - low = mid + 1; - else if (cmp == 0) - return mid; - else - high = mid; - } while (low < high); - return -(low + 1); - } - - private final Repository db; - - private TreeEntry[] contents; - - /** - * Constructor for a new Tree - * - * @param repo The repository that owns the Tree. - */ - public Tree(final Repository repo) { - super(null, null, null); - db = repo; - contents = EMPTY_TREE; - } - - /** - * Construct a Tree object with known content and hash value - * - * @param repo - * @param myId - * @param raw - * @throws IOException - */ - public Tree(final Repository repo, final ObjectId myId, final byte[] raw) - throws IOException { - super(null, myId, null); - db = repo; - readTree(raw); - } - - /** - * Construct a new Tree under another Tree - * - * @param parent - * @param nameUTF8 - */ - public Tree(final Tree parent, final byte[] nameUTF8) { - super(parent, null, nameUTF8); - db = parent.getRepository(); - contents = EMPTY_TREE; - } - - /** - * Construct a Tree with a known SHA-1 under another tree. Data is not yet - * specified and will have to be loaded on demand. - * - * @param parent - * @param id - * @param nameUTF8 - */ - public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) { - super(parent, id, nameUTF8); - db = parent.getRepository(); - } - - public FileMode getMode() { - return FileMode.TREE; - } - - /** - * @return true if this Tree is the top level Tree. - */ - public boolean isRoot() { - return getParent() == null; - } - - public Repository getRepository() { - return db; - } - - /** - * @return true of the data of this Tree is loaded - */ - public boolean isLoaded() { - return contents != null; - } - - /** - * Forget the in-memory data for this tree. - */ - public void unload() { - if (isModified()) - throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree); - contents = null; - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final String name) throws IOException { - return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final byte[] s, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addFile(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - else if (slash < s.length) { - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return t.addFile(s, slash + 1); - } else { - final FileTreeEntry f = new FileTreeEntry(this, null, newName, - false); - insertEntry(p, f); - return f; - } - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final String name) throws IOException { - return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final byte[] s, final int offset) throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - p = binarySearch(contents, s, (byte)'/', offset, slash); - if (p >= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addTree(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return slash == s.length ? t : t.addTree(s, slash + 1); - } - - /** - * Add the specified tree entry to this tree. - * - * @param e - * @throws IOException - */ - public void addEntry(final TreeEntry e) throws IOException { - final int p; - - ensureLoaded(); - p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length); - if (p < 0) { - e.attachParent(this); - insertEntry(p, e); - } else { - throw new EntryExistsException(e.getName()); - } - } - - private void insertEntry(int p, final TreeEntry e) { - final TreeEntry[] c = contents; - final TreeEntry[] n = new TreeEntry[c.length + 1]; - p = -(p + 1); - for (int k = c.length - 1; k >= p; k--) - n[k + 1] = c[k]; - n[p] = e; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - - void removeEntry(final TreeEntry e) { - final TreeEntry[] c = contents; - final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0, - e.getNameUTF8().length); - if (p >= 0) { - final TreeEntry[] n = new TreeEntry[c.length - 1]; - for (int k = c.length - 1; k > p; k--) - n[k - 1] = c[k]; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - } - - /** - * @return number of members in this tree - * @throws IOException - */ - public int memberCount() throws IOException { - ensureLoaded(); - return contents.length; - } - - /** - * Return all members of the tree sorted in Git order. - * - * Entries are sorted by the numerical unsigned byte - * values with (sub)trees having an implicit '/'. An - * example of a tree with three entries. a:b is an - * actual file name here. - * - *

    - * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b - * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a - * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b - * - * @return all entries in this Tree, sorted. - * @throws IOException - */ - public TreeEntry[] members() throws IOException { - ensureLoaded(); - final TreeEntry[] c = contents; - if (c.length != 0) { - final TreeEntry[] r = new TreeEntry[c.length]; - for (int k = c.length - 1; k >= 0; k--) - r[k] = c[k]; - return r; - } else - return c; - } - - private boolean exists(final String s, byte slast) throws IOException { - return findMember(s, slast) != null; - } - - /** - * @param path to the tree. - * @return true if a tree with the specified path can be found under this - * tree. - * @throws IOException - */ - public boolean existsTree(String path) throws IOException { - return exists(path,(byte)'/'); - } - - /** - * @param path of the non-tree entry. - * @return true if a blob, symlink, or gitlink with the specified name - * can be found under this tree. - * @throws IOException - */ - public boolean existsBlob(String path) throws IOException { - return exists(path,(byte)0); - } - - private TreeEntry findMember(final String s, byte slast) throws IOException { - return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0); - } - - private TreeEntry findMember(final byte[] s, final byte slast, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0) { - final TreeEntry r = contents[p]; - if (slash < s.length-1) - return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1) - : null; - return r; - } - return null; - } - - /** - * @param s - * blob name - * @return a {@link TreeEntry} representing an object with the specified - * relative path. - * @throws IOException - */ - public TreeEntry findBlobMember(String s) throws IOException { - return findMember(s,(byte)0); - } - - /** - * @param s Tree Name - * @return a Tree with the name s or null - * @throws IOException - */ - public TreeEntry findTreeMember(String s) throws IOException { - return findMember(s,(byte)'/'); - } - - private void ensureLoaded() throws IOException, MissingObjectException { - if (!isLoaded()) { - ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE); - readTree(ldr.getCachedBytes()); - } - } - - private void readTree(final byte[] raw) throws IOException { - final int rawSize = raw.length; - int rawPtr = 0; - TreeEntry[] temp; - int nextIndex = 0; - - while (rawPtr < rawSize) { - while (rawPtr < rawSize && raw[rawPtr] != 0) - rawPtr++; - rawPtr++; - rawPtr += Constants.OBJECT_ID_LENGTH; - nextIndex++; - } - - temp = new TreeEntry[nextIndex]; - rawPtr = 0; - nextIndex = 0; - while (rawPtr < rawSize) { - int c = raw[rawPtr++]; - if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode); - int mode = c - '0'; - for (;;) { - c = raw[rawPtr++]; - if (' ' == c) - break; - else if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode); - mode <<= 3; - mode += c - '0'; - } - - int nameLen = 0; - while (raw[rawPtr + nameLen] != 0) - nameLen++; - final byte[] name = new byte[nameLen]; - System.arraycopy(raw, rawPtr, name, 0, nameLen); - rawPtr += nameLen + 1; - - final ObjectId id = ObjectId.fromRaw(raw, rawPtr); - rawPtr += Constants.OBJECT_ID_LENGTH; - - final TreeEntry ent; - if (FileMode.REGULAR_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, false); - else if (FileMode.EXECUTABLE_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, true); - else if (FileMode.TREE.equals(mode)) - ent = new Tree(this, id, name); - else if (FileMode.SYMLINK.equals(mode)) - ent = new SymlinkTreeEntry(this, id, name); - else if (FileMode.GITLINK.equals(mode)) - ent = new GitlinkTreeEntry(this, id, name); - else - throw new CorruptObjectException(getId(), MessageFormat.format( - JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode))); - temp[nextIndex++] = ent; - } - - contents = temp; - } - - /** - * Format this Tree in canonical format. - * - * @return canonical encoding of the tree object. - * @throws IOException - * the tree cannot be loaded, or its not in a writable state. - */ - public byte[] format() throws IOException { - TreeFormatter fmt = new TreeFormatter(); - for (TreeEntry e : members()) { - ObjectId id = e.getId(); - if (id == null) - throw new ObjectWritingException(MessageFormat.format(JGitText - .get().objectAtPathDoesNotHaveId, e.getFullName())); - - fmt.append(e.getNameUTF8(), e.getMode(), id); - } - return fmt.toByteArray(); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" T "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } - -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.transport.RefSpec; + +/** + * Something that knows how to convert plain strings from a git + * {@link org.eclipse.jgit.lib.Config} to typed values. + * + * @since 4.9 + */ +public interface TypedConfigGetter { + + /** + * Get a boolean value from a git {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return true if any value or defaultValue is true, false for missing or + * explicit false + */ + boolean getBoolean(Config config, String section, String subsection, + String name, boolean defaultValue); + + /** + * Parse an enumeration from a git {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the value from + * @param all + * all possible values in the enumeration which should be + * recognized. Typically {@code EnumType.values()}. + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return the selected enumeration value, or {@code defaultValue}. + */ + > T getEnum(Config config, T[] all, String section, + String subsection, String name, T defaultValue); + + /** + * Obtain an integer value from a git {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return an integer value from the configuration, or defaultValue. + */ + int getInt(Config config, String section, String subsection, String name, + int defaultValue); + + /** + * Obtain a long value from a git {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return a long value from the configuration, or defaultValue. + */ + long getLong(Config config, String section, String subsection, String name, + long defaultValue); + + /** + * Parse a numerical time unit, such as "1 minute", from a git + * {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the value from + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @param defaultValue + * default value to return if no value was present. + * @param wantUnit + * the units of {@code defaultValue} and the return value, as + * well as the units to assume if the value does not contain an + * indication of the units. + * @return the value, or {@code defaultValue} if not set, expressed in + * {@code units}. + */ + long getTimeUnit(Config config, String section, String subsection, + String name, long defaultValue, TimeUnit wantUnit); + + + /** + * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from a git + * {@link org.eclipse.jgit.lib.Config}. + * + * @param config + * to get the list from + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @return a possibly empty list of + * {@link org.eclipse.jgit.transport.RefSpec}s + */ + @NonNull + List getRefSpecs(Config config, String section, String subsection, + String name); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,14 +48,12 @@ import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.util.SystemReader; -/** The standard "user" configuration parameters. */ +/** + * The standard "user" configuration parameters. + */ public class UserConfig { /** Key for {@link Config#get(SectionParser)}. */ - public static final Config.SectionParser KEY = new SectionParser() { - public UserConfig parse(final Config cfg) { - return new UserConfig(cfg); - } - }; + public static final Config.SectionParser KEY = UserConfig::new; private String authorName; @@ -98,6 +96,8 @@ } /** + * Get the author name as defined in the git variables and configurations. + * * @return the author name as defined in the git variables and * configurations. If no name could be found, try to use the system * user name instead. @@ -107,6 +107,9 @@ } /** + * Get the committer name as defined in the git variables and + * configurations. + * * @return the committer name as defined in the git variables and * configurations. If no name could be found, try to use the system * user name instead. @@ -116,26 +119,31 @@ } /** - * @return the author email as defined in git variables and - * configurations. If no email could be found, try to - * propose one default with the user name and the - * host name. + * Get the author email as defined in git variables and configurations. + * + * @return the author email as defined in git variables and configurations. + * If no email could be found, try to propose one default with the + * user name and the host name. */ public String getAuthorEmail() { return authorEmail; } /** + * Get the committer email as defined in git variables and configurations. + * * @return the committer email as defined in git variables and - * configurations. If no email could be found, try to - * propose one default with the user name and the - * host name. + * configurations. If no email could be found, try to propose one + * default with the user name and the host name. */ public String getCommitterEmail() { return committerEmail; } /** + * Whether the author name was not explicitly configured but constructed + * from information the system has about the logged on user + * * @return true if the author name was not explicitly configured but * constructed from information the system has about the logged on * user @@ -145,6 +153,9 @@ } /** + * Whether the author email was not explicitly configured but constructed + * from information the system has about the logged on user + * * @return true if the author email was not explicitly configured but * constructed from information the system has about the logged on * user @@ -154,6 +165,9 @@ } /** + * Whether the committer name was not explicitly configured but constructed + * from information the system has about the logged on user + * * @return true if the committer name was not explicitly configured but * constructed from information the system has about the logged on * user @@ -163,6 +177,9 @@ } /** + * Whether the author email was not explicitly configured but constructed + * from information the system has about the logged on user + * * @return true if the author email was not explicitly configured but * constructed from information the system has about the logged on * user diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -79,12 +79,14 @@ return bol; } + /** {@inheritDoc} */ @Override public void write(int val) throws IOException { out.write(val); bol = (val == '\n'); } + /** {@inheritDoc} */ @Override public void write(byte[] buf, int pos, int cnt) throws IOException { if (cnt > 0) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,14 +57,15 @@ /** * Provides the merge algorithm which does a three-way merge on content provided - * as RawText. By default {@link HistogramDiff} is used as diff algorithm. + * as RawText. By default {@link org.eclipse.jgit.diff.HistogramDiff} is used as + * diff algorithm. */ public final class MergeAlgorithm { private final DiffAlgorithm diffAlg; /** - * Creates a new MergeAlgorithm which uses {@link HistogramDiff} as diff - * algorithm + * Creates a new MergeAlgorithm which uses + * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm */ public MergeAlgorithm() { this(new HistogramDiff()); @@ -88,8 +89,6 @@ /** * Does the three way merge between a common base and two sequences. * - * @param - * type of sequence. * @param cmp comparison method for this execution. * @param base the common base sequence * @param ours the first sequence to be merged @@ -98,11 +97,11 @@ */ public MergeResult merge( SequenceComparator cmp, S base, S ours, S theirs) { - List sequences = new ArrayList(3); + List sequences = new ArrayList<>(3); sequences.add(base); sequences.add(ours); sequences.add(theirs); - MergeResult result = new MergeResult(sequences); + MergeResult result = new MergeResult<>(sequences); if (ours.size() == 0) { if (theirs.size() != 0) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java 2019-09-03 12:37:49.000000000 +0000 @@ -97,7 +97,8 @@ * merge result. All elements between begin (including begin) and * this element are added. * @param conflictState - * the state of this chunk. See {@link ConflictState} + * the state of this chunk. See + * {@link org.eclipse.jgit.merge.MergeChunk.ConflictState} */ protected MergeChunk(int sequenceIndex, int begin, int end, ConflictState conflictState) { @@ -108,7 +109,9 @@ } /** - * @return the index of the sequence to which sequence this chunks belongs + * Get the index of the sequence to which this sequence chunks belongs to. + * + * @return the index of the sequence to which this sequence chunks belongs * to. Same as in {@link org.eclipse.jgit.merge.MergeResult#add} */ public int getSequenceIndex() { @@ -116,6 +119,9 @@ } /** + * Get the first element from the specified sequence which should be + * included in the merge result. + * * @return the first element from the specified sequence which should be * included in the merge result. Indexes start with 0. */ @@ -124,17 +130,22 @@ } /** - * @return the end of the range of this chunk. The element this index - * points to is the first element which not added to the merge - * result. All elements between begin (including begin) and this - * element are added. + * Get the end of the range of this chunk. + * + * @return the end of the range of this chunk. The element this index points + * to is the first element which not added to the merge result. All + * elements between begin (including begin) and this element are + * added. */ public int getEnd() { return end; } /** - * @return the state of this chunk. See {@link ConflictState} + * Get the state of this chunk. + * + * @return the state of this chunk. See + * {@link org.eclipse.jgit.merge.MergeChunk.ConflictState} */ public ConflictState getConflictState() { return conflictState; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,8 +46,8 @@ import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Repository; /** @@ -58,7 +58,10 @@ public class MergeConfig { /** + * Get merge configuration for the current branch of the repository + * * @param repo + * a {@link org.eclipse.jgit.lib.Repository} object. * @return merge configuration for the current branch of the repository */ public static MergeConfig getConfigForCurrentBranch(Repository repo) { @@ -74,10 +77,14 @@ } /** + * Get a parser for use with + * {@link org.eclipse.jgit.lib.Config#get(SectionParser)} + * * @param branch * short branch name to get the configuration for, as returned - * e.g. by {@link Repository#getBranch()} - * @return a parser for use with {@link Config#get(SectionParser)} + * e.g. by {@link org.eclipse.jgit.lib.Repository#getBranch()} + * @return a parser for use with + * {@link org.eclipse.jgit.lib.Config#get(SectionParser)} */ public static final SectionParser getParser( final String branch) { @@ -104,6 +111,8 @@ } /** + * Get the fast forward mode configured for this branch + * * @return the fast forward mode configured for this branch */ public FastForwardMode getFastForwardMode() { @@ -111,6 +120,9 @@ } /** + * Whether merges into this branch are configured to be squash merges, false + * otherwise + * * @return true if merges into this branch are configured to be squash * merges, false otherwise */ @@ -119,8 +131,10 @@ } /** - * @return false if --no-commit is configured for this branch, true - * otherwise (event if --squash is configured) + * Whether {@code --no-commit} option is not set. + * + * @return {@code false} if --no-commit is configured for this branch, + * {@code true} otherwise (even if --squash is configured) */ public boolean isCommit() { return commit; @@ -167,6 +181,7 @@ this.branch = branch; } + @Override public MergeConfig parse(Config cfg) { return new MergeConfig(branch, cfg); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,11 +55,12 @@ */ public class MergeFormatter { /** - * Formats the results of a merge of {@link RawText} objects in a Git - * conformant way. This method also assumes that the {@link RawText} objects - * being merged are line oriented files which use LF as delimiter. This - * method will also use LF to separate chunks and conflict metadata, - * therefore it fits only to texts that are LF-separated lines. + * Formats the results of a merge of {@link org.eclipse.jgit.diff.RawText} + * objects in a Git conformant way. This method also assumes that the + * {@link org.eclipse.jgit.diff.RawText} objects being merged are line + * oriented files which use LF as delimiter. This method will also use LF to + * separate chunks and conflict metadata, therefore it fits only to texts + * that are LF-separated lines. * * @param out * the outputstream where to write the textual presentation @@ -67,13 +68,13 @@ * the merge result which should be presented * @param seqName * When a conflict is reported each conflicting range will get a - * name. This name is following the "<<<<<<< " or ">>>>>>> " - * conflict markers. The names for the sequences are given in - * this list + * name. This name is following the "<<<<<<< + * " or ">>>>>>> " conflict markers. The + * names for the sequences are given in this list * @param charsetName * the name of the characterSet used when writing conflict * metadata - * @throws IOException + * @throws java.io.IOException */ public void formatMerge(OutputStream out, MergeResult res, List seqName, String charsetName) throws IOException { @@ -81,13 +82,14 @@ } /** - * Formats the results of a merge of exactly two {@link RawText} objects in - * a Git conformant way. This convenience method accepts the names for the - * three sequences (base and the two merged sequences) as explicit - * parameters and doesn't require the caller to specify a List + * Formats the results of a merge of exactly two + * {@link org.eclipse.jgit.diff.RawText} objects in a Git conformant way. + * This convenience method accepts the names for the three sequences (base + * and the two merged sequences) as explicit parameters and doesn't require + * the caller to specify a List * * @param out - * the {@link OutputStream} where to write the textual + * the {@link java.io.OutputStream} where to write the textual * presentation * @param res * the merge result which should be presented @@ -100,11 +102,12 @@ * @param charsetName * the name of the characterSet used when writing conflict * metadata - * @throws IOException + * @throws java.io.IOException */ + @SuppressWarnings("unchecked") public void formatMerge(OutputStream out, MergeResult res, String baseName, String oursName, String theirsName, String charsetName) throws IOException { - List names = new ArrayList(3); + List names = new ArrayList<>(3); names.add(baseName); names.add(oursName); names.add(theirsName); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java 2019-09-03 12:37:49.000000000 +0000 @@ -143,4 +143,4 @@ if (out.isBeginln()) out.write('\n'); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ import java.util.List; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.ChangeIdUtil; @@ -70,31 +71,31 @@ StringBuilder sb = new StringBuilder(); sb.append("Merge "); //$NON-NLS-1$ - List branches = new ArrayList(); - List remoteBranches = new ArrayList(); - List tags = new ArrayList(); - List commits = new ArrayList(); - List others = new ArrayList(); + List branches = new ArrayList<>(); + List remoteBranches = new ArrayList<>(); + List tags = new ArrayList<>(); + List commits = new ArrayList<>(); + List others = new ArrayList<>(); for (Ref ref : refsToMerge) { - if (ref.getName().startsWith(Constants.R_HEADS)) + if (ref.getName().startsWith(Constants.R_HEADS)) { branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$ + "'"); //$NON-NLS-1$ - - else if (ref.getName().startsWith(Constants.R_REMOTES)) + } else if (ref.getName().startsWith(Constants.R_REMOTES)) { remoteBranches.add("'" //$NON-NLS-1$ + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ - - else if (ref.getName().startsWith(Constants.R_TAGS)) + } else if (ref.getName().startsWith(Constants.R_TAGS)) { tags.add("'" + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - - else if (ref.getName().equals(ref.getObjectId().getName())) - commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - - else - others.add(ref.getName()); + } else { + ObjectId objectId = ref.getObjectId(); + if (objectId != null && ref.getName().equals(objectId.getName())) { + commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + others.add(ref.getName()); + } + } } - List listings = new ArrayList(); + List listings = new ArrayList<>(); if (!branches.isEmpty()) listings.add(joinNames(branches, "branch", "branches")); //$NON-NLS-1$//$NON-NLS-2$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,14 +51,14 @@ import org.eclipse.jgit.util.IntList; /** - * The result of merging a number of {@link Sequence} objects. These sequences - * have one common predecessor sequence. The result of a merge is a list of - * MergeChunks. Each MergeChunk contains either a range (a subsequence) from - * one of the merged sequences, a range from the common predecessor or a - * conflicting range from one of the merged sequences. A conflict will be - * reported as multiple chunks, one for each conflicting range. The first chunk - * for a conflict is marked specially to distinguish the border between two - * consecutive conflicts. + * The result of merging a number of {@link org.eclipse.jgit.diff.Sequence} + * objects. These sequences have one common predecessor sequence. The result of + * a merge is a list of MergeChunks. Each MergeChunk contains either a range (a + * subsequence) from one of the merged sequences, a range from the common + * predecessor or a conflicting range from one of the merged sequences. A + * conflict will be reported as multiple chunks, one for each conflicting range. + * The first chunk for a conflict is marked specially to distinguish the border + * between two consecutive conflicts. *

    * This class does not know anything about how to present the merge result to * the end-user. MergeFormatters have to be used to construct something human @@ -70,7 +70,7 @@ public class MergeResult implements Iterable { private final List sequences; - private final IntList chunks = new IntList(); + final IntList chunks = new IntList(); private boolean containsConflicts = false; @@ -80,7 +80,8 @@ * @param sequences * contains the common predecessor sequence at position 0 * followed by the merged sequences. This list should not be - * modified anymore during the lifetime of this {@link MergeResult}. + * modified anymore during the lifetime of this + * {@link org.eclipse.jgit.merge.MergeResult}. */ public MergeResult(List sequences) { this.sequences = sequences; @@ -127,20 +128,20 @@ return sequences; } - private static final ConflictState[] states = ConflictState.values(); + static final ConflictState[] states = ConflictState.values(); - /** - * @return an iterator over the MergeChunks. The iterator does not support - * the remove operation - */ + /** {@inheritDoc} */ + @Override public Iterator iterator() { return new Iterator() { int idx; + @Override public boolean hasNext() { return (idx < chunks.size()); } + @Override public MergeChunk next() { ConflictState state = states[chunks.get(idx++)]; int srcIdx = chunks.get(idx++); @@ -149,6 +150,7 @@ return new MergeChunk(srcIdx, begin, end, state); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -156,6 +158,8 @@ } /** + * Whether this merge result contains conflicts + * * @return true if this merge result contains conflicts */ public boolean containsConflicts() { @@ -169,6 +173,8 @@ * markers!) as new conflict-free content * * @param containsConflicts + * whether this merge should be seen as containing a conflict or + * not. * @since 3.5 */ protected void setContainsConflicts(boolean containsConflicts) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2013, Google Inc. + * Copyright (C) 2016, Laurent Delaigue * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -46,14 +47,17 @@ import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; @@ -64,10 +68,19 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser; /** - * Instance of a specific {@link MergeStrategy} for a single {@link Repository}. + * Instance of a specific {@link org.eclipse.jgit.merge.MergeStrategy} for a + * single {@link org.eclipse.jgit.lib.Repository}. */ public abstract class Merger { - /** The repository this merger operates on. */ + /** + * The repository this merger operates on. + *

    + * Null if and only if the merger was constructed with {@link + * #Merger(ObjectInserter)}. Callers that want to assume the repo is not null + * (e.g. because of a previous check that the merger is not in-core) may use + * {@link #nonNullRepo()}. + */ + @Nullable protected final Repository db; /** Reader to support {@link #walk} and other object loading. */ @@ -88,26 +101,75 @@ protected RevTree[] sourceTrees; /** + * A progress monitor. + * + * @since 4.2 + */ + protected ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + + /** * Create a new merge instance for a repository. * * @param local * the repository this merger will read and write data on. */ protected Merger(final Repository local) { + if (local == null) { + throw new NullPointerException(JGitText.get().repositoryIsRequired); + } db = local; - inserter = db.newObjectInserter(); + inserter = local.newObjectInserter(); reader = inserter.newReader(); walk = new RevWalk(reader); } /** + * Create a new in-core merge instance from an inserter. + * + * @param oi + * the inserter to write objects to. Will be closed at the + * conclusion of {@code merge}, unless {@code flush} is false. + * @since 4.8 + */ + protected Merger(ObjectInserter oi) { + db = null; + inserter = oi; + reader = oi.newReader(); + walk = new RevWalk(reader); + } + + /** + * Get the repository this merger operates on. + * * @return the repository this merger operates on. */ + @Nullable public Repository getRepository() { return db; } - /** @return an object writer to create objects in {@link #getRepository()}. */ + /** + * Get non-null repository instance + * + * @return non-null repository instance + * @throws java.lang.NullPointerException + * if the merger was constructed without a repository. + * @since 4.8 + */ + protected Repository nonNullRepo() { + if (db == null) { + throw new NullPointerException(JGitText.get().repositoryIsRequired); + } + return db; + } + + /** + * Get an object writer to create objects, writing objects to + * {@link #getRepository()} + * + * @return an object writer to create objects, writing objects to + * {@link #getRepository()} (if a repository was provided). + */ public ObjectInserter getObjectInserter() { return inserter; } @@ -121,7 +183,9 @@ * * @param oi * the inserter instance to use. Must be associated with the - * repository instance returned by {@link #getRepository()}. + * repository instance returned by {@link #getRepository()} (if a + * repository was provided). Will be closed at the conclusion of + * {@code merge}, unless {@code flush} is false. */ public void setObjectInserter(ObjectInserter oi) { walk.close(); @@ -147,7 +211,7 @@ * @throws IncorrectObjectTypeException * one of the input objects is not a commit, but the strategy * requires it to be a commit. - * @throws IOException + * @throws java.io.IOException * one or more sources could not be read, or outputs could not * be written to the Repository. */ @@ -163,9 +227,9 @@ * * @since 3.5 * @param flush - * whether to flush the underlying object inserter when finished to - * store any content-merged blobs and virtual merged bases; if - * false, callers are responsible for flushing. + * whether to flush and close the underlying object inserter when + * finished to store any content-merged blobs and virtual merged + * bases; if false, callers are responsible for flushing. * @param tips * source trees to be combined together. The merge base is not * included in this set. @@ -175,7 +239,7 @@ * @throws IncorrectObjectTypeException * one of the input objects is not a commit, but the strategy * requires it to be a commit. - * @throws IOException + * @throws java.io.IOException * one or more sources could not be read, or outputs could not * be written to the Repository. */ @@ -211,6 +275,8 @@ } /** + * Get the ID of the commit that was used as merge base for merging + * * @return the ID of the commit that was used as merge base for merging, or * null if no merge base was used or it was set manually * @since 3.2 @@ -225,9 +291,9 @@ * @param b * the second commit in {@link #sourceObjects}. * @return the merge base of two commits - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * one of the input objects is not a commit. - * @throws IOException + * @throws java.io.IOException * objects are missing or multiple merge bases were found. * @since 3.0 */ @@ -257,9 +323,9 @@ * @param treeId * the tree to scan; must be a tree (not a treeish). * @return an iterator for the tree. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the input object is not a tree. - * @throws IOException + * @throws java.io.IOException * the tree object is not found or cannot be read. */ protected AbstractTreeIterator openTree(final AnyObjectId treeId) @@ -280,14 +346,32 @@ * @throws IncorrectObjectTypeException * one of the input objects is not a commit, but the strategy * requires it to be a commit. - * @throws IOException + * @throws java.io.IOException * one or more sources could not be read, or outputs could not * be written to the Repository. */ protected abstract boolean mergeImpl() throws IOException; /** + * Get resulting tree. + * * @return resulting tree, if {@link #merge(AnyObjectId[])} returned true. */ public abstract ObjectId getResultTreeId(); + + /** + * Set a progress monitor. + * + * @param monitor + * Monitor to use, can be null to indicate no progress reporting + * is desired. + * @since 4.2 + */ + public void setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + this.monitor = NullProgressMonitor.INSTANCE; + } else { + this.monitor = monitor; + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,6 +49,8 @@ import java.util.HashMap; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -80,7 +82,7 @@ */ public static final ThreeWayMergeStrategy RECURSIVE = new StrategyRecursive(); - private static final HashMap STRATEGIES = new HashMap(); + private static final HashMap STRATEGIES = new HashMap<>(); static { register(OURS); @@ -95,7 +97,7 @@ * * @param imp * the strategy to register. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * a strategy by the same name has already been registered. */ public static void register(final MergeStrategy imp) { @@ -109,7 +111,7 @@ * name the strategy can be looked up under. * @param imp * the strategy to register. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * a strategy by the same name has already been registered. */ public static synchronized void register(final String name, @@ -144,7 +146,11 @@ return r; } - /** @return default name of this strategy implementation. */ + /** + * Get default name of this strategy implementation. + * + * @return default name of this strategy implementation. + */ public abstract String getName(); /** @@ -170,4 +176,20 @@ * @return the new merge instance which implements this strategy. */ public abstract Merger newMerger(Repository db, boolean inCore); + + /** + * Create a new merge instance. + *

    + * The merge will happen in memory, working folder will not be modified, in + * case of a non-trivial merge that requires manual resolution, the merger + * will fail. + * + * @param inserter + * inserter to write results back to. + * @param config + * repo config for reading diff algorithm settings. + * @return the new merge instance which implements this strategy. + * @since 4.8 + */ + public abstract Merger newMerger(ObjectInserter inserter, Config config); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,20 +57,19 @@ import java.util.TimeZone; import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheBuilder; -import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; /** @@ -84,6 +83,7 @@ * - uses "Lists" instead of Arrays for chained types * * - recursively merges the merge bases together to compute a usable base + * * @since 3.0 */ public class RecursiveMerger extends ResolveMerger { @@ -98,7 +98,9 @@ * inCore * * @param local + * a {@link org.eclipse.jgit.lib.Repository} object. * @param inCore + * a boolean. */ protected RecursiveMerger(Repository local, boolean inCore) { super(local, inCore); @@ -107,19 +109,31 @@ /** * Normal recursive merge, implies not inCore * - * @param local + * @param local a {@link org.eclipse.jgit.lib.Repository} object. */ protected RecursiveMerger(Repository local) { this(local, false); } /** + * Normal recursive merge, implies inCore. + * + * @param inserter + * an {@link org.eclipse.jgit.lib.ObjectInserter} object. + * @param config + * the repository configuration + * @since 4.8 + */ + protected RecursiveMerger(ObjectInserter inserter, Config config) { + super(inserter, config); + } + + /** + * {@inheritDoc} + *

    * Get a single base commit for two given commits. If the two source commits * have more than one base commit recursively merge the base commits * together until you end up with a single base commit. - * - * @throws IOException - * @throws IncorrectObjectTypeException */ @Override protected RevCommit getBaseCommit(RevCommit a, RevCommit b) @@ -141,7 +155,7 @@ * @return the merge base of two commits. If a criss-cross merge required a * synthetic merge base this commit is visible only the merger's * RevWalk and will not be in the repository. - * @throws IOException + * @throws java.io.IOException * @throws IncorrectObjectTypeException * one of the input objects is not a commit. * @throws NoMergeBaseException @@ -150,7 +164,7 @@ */ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth) throws IOException { - ArrayList baseCommits = new ArrayList(); + ArrayList baseCommits = new ArrayList<>(); walk.reset(); walk.setRevFilter(RevFilter.MERGE_BASE); walk.markStart(a); @@ -181,10 +195,10 @@ WorkingTreeIterator oldWTreeIt = workingTreeIterator; workingTreeIterator = null; try { - dircache = dircacheFromTree(currentBase.getTree()); + dircache = DirCache.read(reader, currentBase.getTree()); inCore = true; - List parents = new ArrayList(); + List parents = new ArrayList<>(); parents.add(currentBase); for (int commitIdx = 1; commitIdx < baseCommits.size(); commitIdx++) { RevCommit nextBase = baseCommits.get(commitIdx); @@ -256,30 +270,4 @@ new Date((time + 1) * 1000L), TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$ } - - /** - * Create a new in memory dircache which has the same content as a given - * tree. - * - * @param treeId - * the tree which should be used to fill the dircache - * @return a new in memory dircache - * @throws IOException - */ - private DirCache dircacheFromTree(ObjectId treeId) throws IOException { - DirCache ret = DirCache.newInCore(); - DirCacheBuilder aBuilder = ret.builder(); - try (TreeWalk atw = new TreeWalk(reader)) { - atw.addTree(treeId); - atw.setRecursive(true); - while (atw.next()) { - DirCacheEntry e = new DirCacheEntry(atw.getRawPath()); - e.setFileMode(atw.getFileMode(0)); - e.setObjectId(atw.getObjectId(0)); - aBuilder.add(e); - } - } - aBuilder.finish(); - return ret; - } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -2,6 +2,8 @@ * Copyright (C) 2010, Christian Halstrick , * Copyright (C) 2010-2012, Matthias Sohn * Copyright (C) 2012, Research In Motion Limited + * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr) + * Copyright (C) 2018, Thomas Wolf * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -44,12 +46,14 @@ */ package org.eclipse.jgit.merge; +import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM; import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -64,6 +68,7 @@ import java.util.List; import java.util.Map; +import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.diff.DiffAlgorithm; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; import org.eclipse.jgit.diff.RawText; @@ -74,25 +79,37 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IndexWriteException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.submodule.SubmoduleConflict; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.NameConflictTreeWalk; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.LfsFactory; +import org.eclipse.jgit.util.LfsFactory.LfsInputStream; import org.eclipse.jgit.util.TemporaryBuffer; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; /** * A three-way merger performing a content-merge if necessary @@ -180,14 +197,14 @@ * * @since 3.4 */ - protected List unmergedPaths = new ArrayList(); + protected List unmergedPaths = new ArrayList<>(); /** * Files modified during this merge operation. * * @since 3.4 */ - protected List modifiedFiles = new LinkedList(); + protected List modifiedFiles = new LinkedList<>(); /** * If the merger has nothing to do for a file but check it out at the end of @@ -195,7 +212,7 @@ * * @since 3.4 */ - protected Map toBeCheckedOut = new HashMap(); + protected Map toBeCheckedOut = new HashMap<>(); /** * Paths in this list will be deleted from the local copy at the end of the @@ -203,7 +220,7 @@ * * @since 3.4 */ - protected List toBeDeleted = new ArrayList(); + protected List toBeDeleted = new ArrayList<>(); /** * Low-level textual merge results. Will be passed on to the callers in case @@ -211,14 +228,14 @@ * * @since 3.4 */ - protected Map> mergeResults = new HashMap>(); + protected Map> mergeResults = new HashMap<>(); /** * Paths for which the merge failed altogether. * * @since 3.4 */ - protected Map failingPaths = new HashMap(); + protected Map failingPaths = new HashMap<>(); /** * Updated as we merge entries of the tree walk. Tells us whether we should @@ -267,17 +284,49 @@ protected MergeAlgorithm mergeAlgorithm; /** + * The {@link WorkingTreeOptions} are needed to determine line endings for + * merged files. + * + * @since 4.11 + */ + protected WorkingTreeOptions workingTreeOptions; + + /** + * The size limit (bytes) which controls a file to be stored in {@code Heap} + * or {@code LocalFile} during the merge. + */ + private int inCoreLimit; + + private static MergeAlgorithm getMergeAlgorithm(Config config) { + SupportedAlgorithm diffAlg = config.getEnum( + CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM, + HISTOGRAM); + return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg)); + } + + private static int getInCoreLimit(Config config) { + return config.getInt( + ConfigConstants.CONFIG_MERGE_SECTION, ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20); + } + + private static String[] defaultCommitNames() { + return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Constructor for ResolveMerger. + * * @param local + * the {@link org.eclipse.jgit.lib.Repository}. * @param inCore + * a boolean. */ protected ResolveMerger(Repository local, boolean inCore) { super(local); - SupportedAlgorithm diffAlg = local.getConfig().getEnum( - ConfigConstants.CONFIG_DIFF_SECTION, null, - ConfigConstants.CONFIG_KEY_ALGORITHM, - SupportedAlgorithm.HISTOGRAM); - mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg)); - commitNames = new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Config config = local.getConfig(); + mergeAlgorithm = getMergeAlgorithm(config); + inCoreLimit = getInCoreLimit(config); + commitNames = defaultCommitNames(); this.inCore = inCore; if (inCore) { @@ -285,20 +334,43 @@ dircache = DirCache.newInCore(); } else { implicitDirCache = true; + workingTreeOptions = local.getConfig().get(WorkingTreeOptions.KEY); } } /** + * Constructor for ResolveMerger. + * * @param local + * the {@link org.eclipse.jgit.lib.Repository}. */ protected ResolveMerger(Repository local) { this(local, false); } + /** + * Constructor for ResolveMerger. + * + * @param inserter + * an {@link org.eclipse.jgit.lib.ObjectInserter} object. + * @param config + * the repository configuration + * @since 4.8 + */ + protected ResolveMerger(ObjectInserter inserter, Config config) { + super(inserter); + mergeAlgorithm = getMergeAlgorithm(config); + commitNames = defaultCommitNames(); + inCore = true; + implicitDirCache = false; + dircache = DirCache.newInCore(); + } + + /** {@inheritDoc} */ @Override protected boolean mergeImpl() throws IOException { if (implicitDirCache) - dircache = getRepository().lockDirCache(); + dircache = nonNullRepo().lockDirCache(); try { return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1], @@ -315,7 +387,7 @@ // of a non-empty directory, for which delete() would fail. for (int i = toBeDeleted.size() - 1; i >= 0; i--) { String fileName = toBeDeleted.get(i); - File f = new File(db.getWorkTree(), fileName); + File f = new File(nonNullRepo().getWorkTree(), fileName); if (!f.delete()) if (!f.isDirectory()) failingPaths.put(fileName, @@ -324,8 +396,13 @@ } for (Map.Entry entry : toBeCheckedOut .entrySet()) { - DirCacheCheckout.checkoutEntry(db, entry.getValue(), reader); - modifiedFiles.add(entry.getKey()); + DirCacheEntry cacheEntry = entry.getValue(); + if (cacheEntry.getFileMode() == FileMode.GITLINK) { + new File(nonNullRepo().getWorkTree(), entry.getKey()).mkdirs(); + } else { + DirCacheCheckout.checkoutEntry(db, cacheEntry, reader); + modifiedFiles.add(entry.getKey()); + } } } @@ -335,9 +412,9 @@ * contained only stage 0. In case if inCore operation just clear the * history of modified files. * - * @throws IOException - * @throws CorruptObjectException - * @throws NoWorkTreeException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @throws org.eclipse.jgit.errors.NoWorkTreeException * @since 3.4 */ protected void cleanUp() throws NoWorkTreeException, @@ -348,7 +425,7 @@ return; } - DirCache dc = db.readDirCache(); + DirCache dc = nonNullRepo().readDirCache(); Iterator mpathsIt=modifiedFiles.iterator(); while(mpathsIt.hasNext()) { String mpath=mpathsIt.next(); @@ -393,7 +470,7 @@ * @return the entry which was added to the index */ private DirCacheEntry keep(DirCacheEntry e) { - DirCacheEntry newEntry = new DirCacheEntry(e.getPathString(), + DirCacheEntry newEntry = new DirCacheEntry(e.getRawPath(), e.getStage()); newEntry.setFileMode(e.getFileMode()); newEntry.setObjectId(e.getObjectId()); @@ -404,9 +481,10 @@ } /** - * Processes one path and tries to merge. This method will do all do all - * trivial (not content) merges and will also detect if a merge will fail. - * The merge will fail when one of the following is true + * Processes one path and tries to merge taking git attributes in account. + * This method will do all trivial (not content) merges and will also detect + * if a merge will fail. The merge will fail when one of the following is + * true *

      *
    • the index entry does not match the entry in ours. When merging one * branch into the current HEAD, ours will point to HEAD and theirs will @@ -437,20 +515,77 @@ * the file in the working tree * @param ignoreConflicts * see - * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} + * {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} * @return false if the merge will fail because the index entry * didn't match ours or the working-dir file was dirty and a * conflict occurred - * @throws MissingObjectException - * @throws IncorrectObjectTypeException - * @throws CorruptObjectException - * @throws IOException + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @throws java.io.IOException * @since 3.5 */ + @Deprecated protected boolean processEntry(CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, DirCacheBuildIterator index, WorkingTreeIterator work, - boolean ignoreConflicts) + boolean ignoreConflicts) throws MissingObjectException, + IncorrectObjectTypeException, CorruptObjectException, IOException { + return processEntry(base, ours, theirs, index, work, ignoreConflicts, + null); + } + + /** + * Processes one path and tries to merge taking git attributes in account. + * This method will do all trivial (not content) merges and will also detect + * if a merge will fail. The merge will fail when one of the following is + * true + *
        + *
      • the index entry does not match the entry in ours. When merging one + * branch into the current HEAD, ours will point to HEAD and theirs will + * point to the other branch. It is assumed that the index matches the HEAD + * because it will only not match HEAD if it was populated before the merge + * operation. But the merge commit should not accidentally contain + * modifications done before the merge. Check the git read-tree documentation for further explanations.
      • + *
      • A conflict was detected and the working-tree file is dirty. When a + * conflict is detected the content-merge algorithm will try to write a + * merged version into the working-tree. If the file is dirty we would + * override unsaved data.
      • + *
      + * + * @param base + * the common base for ours and theirs + * @param ours + * the ours side of the merge. When merging a branch into the + * HEAD ours will point to HEAD + * @param theirs + * the theirs side of the merge. When merging a branch into the + * current HEAD theirs will point to the branch which is merged + * into HEAD. + * @param index + * the index entry + * @param work + * the file in the working tree + * @param ignoreConflicts + * see + * {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} + * @param attributes + * the attributes defined for this entry + * @return false if the merge will fail because the index entry + * didn't match ours or the working-dir file was dirty and a + * conflict occurred + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @throws java.io.IOException + * @since 4.9 + */ + protected boolean processEntry(CanonicalTreeParser base, + CanonicalTreeParser ours, CanonicalTreeParser theirs, + DirCacheBuildIterator index, WorkingTreeIterator work, + boolean ignoreConflicts, Attributes attributes) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { enterSubtree = true; @@ -518,7 +653,7 @@ unmergedPaths.add(tw.getPathString()); mergeResults.put( tw.getPathString(), - new MergeResult(Collections + new MergeResult<>(Collections . emptyList())); } return true; @@ -598,22 +733,50 @@ if (nonTree(modeO) && nonTree(modeT)) { // Check worktree before modifying files - if (isWorktreeDirty(work, ourDce)) + boolean worktreeDirty = isWorktreeDirty(work, ourDce); + if (!attributes.canBeContentMerged() && worktreeDirty) { return false; + } + boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT); // Don't attempt to resolve submodule link conflicts - if (isGitLink(modeO) || isGitLink(modeT)) { + if (gitlinkConflict || !attributes.canBeContentMerged()) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); - unmergedPaths.add(tw.getPathString()); + + if (gitlinkConflict) { + MergeResult result = new MergeResult<>( + Arrays.asList( + new SubmoduleConflict(base == null ? null + : base.getEntryObjectId()), + new SubmoduleConflict(ours == null ? null + : ours.getEntryObjectId()), + new SubmoduleConflict(theirs == null ? null + : theirs.getEntryObjectId()))); + result.setContainsConflicts(true); + mergeResults.put(tw.getPathString(), result); + if (!ignoreConflicts) { + unmergedPaths.add(tw.getPathString()); + } + } else { + // attribute merge issues are conflicts but not failures + unmergedPaths.add(tw.getPathString()); + } return true; } - MergeResult result = contentMerge(base, ours, theirs); - if (ignoreConflicts) + // Check worktree before modifying files + if (worktreeDirty) { + return false; + } + + MergeResult result = contentMerge(base, ours, theirs, + attributes); + if (ignoreConflicts) { result.setContainsConflicts(false); - updateIndex(base, ours, theirs, result); + } + updateIndex(base, ours, theirs, result, attributes); if (result.containsConflicts() && !ignoreConflicts) unmergedPaths.add(tw.getPathString()); modifiedFiles.add(tw.getPathString()); @@ -621,6 +784,8 @@ // OURS or THEIRS has been deleted if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw .idEqual(T_BASE, T_THEIRS)))) { + MergeResult result = contentMerge(base, ours, theirs, + attributes); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); @@ -641,8 +806,7 @@ unmergedPaths.add(tw.getPathString()); // generate a MergeResult for the deleted file - mergeResults.put(tw.getPathString(), - contentMerge(base, ours, theirs)); + mergeResults.put(tw.getPathString(), result); } } return true; @@ -656,19 +820,31 @@ * @param base * @param ours * @param theirs + * @param attributes * * @return the result of the content merge * @throws IOException */ private MergeResult contentMerge(CanonicalTreeParser base, - CanonicalTreeParser ours, CanonicalTreeParser theirs) + CanonicalTreeParser ours, CanonicalTreeParser theirs, + Attributes attributes) throws IOException { - RawText baseText = base == null ? RawText.EMPTY_TEXT : getRawText( - base.getEntryObjectId(), reader); - RawText ourText = ours == null ? RawText.EMPTY_TEXT : getRawText( - ours.getEntryObjectId(), reader); - RawText theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText( - theirs.getEntryObjectId(), reader); + RawText baseText; + RawText ourText; + RawText theirsText; + + try { + baseText = base == null ? RawText.EMPTY_TEXT : getRawText( + base.getEntryObjectId(), attributes); + ourText = ours == null ? RawText.EMPTY_TEXT : getRawText( + ours.getEntryObjectId(), attributes); + theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText( + theirs.getEntryObjectId(), attributes); + } catch (BinaryBlobException e) { + MergeResult r = new MergeResult<>(Collections.emptyList()); + r.setContainsConflicts(true); + return r; + } return (mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText, ourText, theirsText)); } @@ -727,96 +903,109 @@ * @param ours * @param theirs * @param result + * @param attributes * @throws FileNotFoundException * @throws IOException */ private void updateIndex(CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, - MergeResult result) throws FileNotFoundException, + MergeResult result, Attributes attributes) + throws FileNotFoundException, IOException { - File mergedFile = !inCore ? writeMergedFile(result) : null; - if (result.containsConflicts()) { - // A conflict occurred, the file will contain conflict markers - // the index will be populated with the three stages and the - // workdir (if used) contains the halfway merged content. - add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); - add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); - add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); - mergeResults.put(tw.getPathString(), result); - return; - } + TemporaryBuffer rawMerged = null; + try { + rawMerged = doMerge(result); + File mergedFile = inCore ? null + : writeMergedFile(rawMerged, attributes); + if (result.containsConflicts()) { + // A conflict occurred, the file will contain conflict markers + // the index will be populated with the three stages and the + // workdir (if used) contains the halfway merged content. + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); + mergeResults.put(tw.getPathString(), result); + return; + } - // No conflict occurred, the file will contain fully merged content. - // The index will be populated with the new merged version. - DirCacheEntry dce = new DirCacheEntry(tw.getPathString()); - - // Set the mode for the new content. Fall back to REGULAR_FILE if - // we can't merge modes of OURS and THEIRS. - int newMode = mergeFileModes( - tw.getRawMode(0), - tw.getRawMode(1), - tw.getRawMode(2)); - dce.setFileMode(newMode == FileMode.MISSING.getBits() - ? FileMode.REGULAR_FILE - : FileMode.fromBits(newMode)); - if (mergedFile != null) { - long len = mergedFile.length(); - dce.setLastModified(mergedFile.lastModified()); - dce.setLength((int) len); - InputStream is = new FileInputStream(mergedFile); - try { - dce.setObjectId(getObjectInserter().insert(OBJ_BLOB, len, is)); - } finally { - is.close(); + // No conflict occurred, the file will contain fully merged content. + // The index will be populated with the new merged version. + DirCacheEntry dce = new DirCacheEntry(tw.getPathString()); + + // Set the mode for the new content. Fall back to REGULAR_FILE if + // we can't merge modes of OURS and THEIRS. + int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1), + tw.getRawMode(2)); + dce.setFileMode(newMode == FileMode.MISSING.getBits() + ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode)); + if (mergedFile != null) { + dce.setLastModified( + nonNullRepo().getFS().lastModified(mergedFile)); + dce.setLength((int) mergedFile.length()); } - } else - dce.setObjectId(insertMergeResult(result)); - builder.add(dce); + dce.setObjectId(insertMergeResult(rawMerged, attributes)); + builder.add(dce); + } finally { + if (rawMerged != null) { + rawMerged.destroy(); + } + } } /** * Writes merged file content to the working tree. * - * @param result - * the result of the content merge + * @param rawMerged + * the raw merged content + * @param attributes + * the files .gitattributes entries * @return the working tree file to which the merged content was written. * @throws FileNotFoundException * @throws IOException */ - private File writeMergedFile(MergeResult result) + private File writeMergedFile(TemporaryBuffer rawMerged, + Attributes attributes) throws FileNotFoundException, IOException { - File workTree = db.getWorkTree(); - if (workTree == null) - // TODO: This should be handled by WorkingTreeIterators which - // support write operations - throw new UnsupportedOperationException(); - - FS fs = db.getFS(); + File workTree = nonNullRepo().getWorkTree(); + FS fs = nonNullRepo().getFS(); File of = new File(workTree, tw.getPathString()); File parentFolder = of.getParentFile(); - if (!fs.exists(parentFolder)) + if (!fs.exists(parentFolder)) { parentFolder.mkdirs(); - try (OutputStream os = new BufferedOutputStream( - new FileOutputStream(of))) { - new MergeFormatter().formatMerge(os, result, - Arrays.asList(commitNames), CHARACTER_ENCODING); + } + EolStreamType streamType = EolStreamTypeUtil.detectStreamType( + OperationType.CHECKOUT_OP, workingTreeOptions, + attributes); + try (OutputStream os = EolStreamTypeUtil.wrapOutputStream( + new BufferedOutputStream(new FileOutputStream(of)), + streamType)) { + rawMerged.writeTo(os, null); } return of; } - private ObjectId insertMergeResult(MergeResult result) + private TemporaryBuffer doMerge(MergeResult result) throws IOException { TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile( - db.getDirectory(), 10 << 20); + db != null ? nonNullRepo().getDirectory() : null, inCoreLimit); try { new MergeFormatter().formatMerge(buf, result, Arrays.asList(commitNames), CHARACTER_ENCODING); buf.close(); - try (InputStream in = buf.openInputStream()) { - return getObjectInserter().insert(OBJ_BLOB, buf.length(), in); - } - } finally { + } catch (IOException e) { buf.destroy(); + throw e; + } + return buf; + } + + private ObjectId insertMergeResult(TemporaryBuffer buf, + Attributes attributes) throws IOException { + InputStream in = buf.openInputStream(); + try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter( + getRepository(), in, + buf.length(), attributes.get(Constants.ATTR_MERGE))) { + return getObjectInserter().insert(OBJ_BLOB, is.getLength(), is); } } @@ -848,11 +1037,17 @@ return FileMode.MISSING.getBits(); } - private static RawText getRawText(ObjectId id, ObjectReader reader) - throws IOException { + private RawText getRawText(ObjectId id, + Attributes attributes) + throws IOException, BinaryBlobException { if (id.equals(ObjectId.zeroId())) return new RawText(new byte[] {}); - return new RawText(reader.open(id, OBJ_BLOB).getCachedBytes()); + + ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter( + getRepository(), reader.open(id, OBJ_BLOB), + attributes.get(Constants.ATTR_MERGE)); + int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD; + return RawText.load(loader, threshold); } private static boolean nonTree(final int mode) { @@ -863,12 +1058,15 @@ return FileMode.GITLINK.equals(mode); } + /** {@inheritDoc} */ @Override public ObjectId getResultTreeId() { return (resultTree == null) ? null : resultTree.toObjectId(); } /** + * Set the names of the commits as they would appear in conflict markers + * * @param commitNames * the names of the commits as they would appear in conflict * markers @@ -878,6 +1076,8 @@ } /** + * Get the names of the commits as they would appear in conflict markers. + * * @return the names of the commits as they would appear in conflict * markers. */ @@ -886,17 +1086,22 @@ } /** - * @return the paths with conflicts. This is a subset of the files listed - * by {@link #getModifiedFiles()} + * Get the paths with conflicts. This is a subset of the files listed by + * {@link #getModifiedFiles()} + * + * @return the paths with conflicts. This is a subset of the files listed by + * {@link #getModifiedFiles()} */ public List getUnmergedPaths() { return unmergedPaths; } /** - * @return the paths of files which have been modified by this merge. A - * file will be modified if a content-merge works on this path or if - * the merge algorithm decides to take the theirs-version. This is a + * Get the paths of files which have been modified by this merge. + * + * @return the paths of files which have been modified by this merge. A file + * will be modified if a content-merge works on this path or if the + * merge algorithm decides to take the theirs-version. This is a * superset of the files listed by {@link #getUnmergedPaths()}. */ public List getModifiedFiles() { @@ -904,6 +1109,10 @@ } /** + * Get a map which maps the paths of files which have to be checked out + * because the merge created new fully-merged content for this file into the + * index. + * * @return a map which maps the paths of files which have to be checked out * because the merge created new fully-merged content for this file * into the index. This means: the merge wrote a new stage 0 entry @@ -914,6 +1123,8 @@ } /** + * Get the mergeResults + * * @return the mergeResults */ public Map> getMergeResults() { @@ -921,6 +1132,9 @@ } /** + * Get list of paths causing this merge to fail (not stopped because of a + * conflict). + * * @return lists paths causing this merge to fail (not stopped because of a * conflict). null is returned if this merge didn't * fail. @@ -945,10 +1159,10 @@ * not set explicitly and if this merger doesn't work in-core, this merger * will implicitly get and lock a default DirCache. If the DirCache is * explicitly set the caller is responsible to lock it in advance. Finally - * the merger will call {@link DirCache#commit()} which requires that the - * DirCache is locked. If the {@link #mergeImpl()} returns without throwing - * an exception the lock will be released. In case of exceptions the caller - * is responsible to release the lock. + * the merger will call {@link org.eclipse.jgit.dircache.DirCache#commit()} + * which requires that the DirCache is locked. If the {@link #mergeImpl()} + * returns without throwing an exception the lock will be released. In case + * of exceptions the caller is responsible to release the lock. * * @param dc * the DirCache to set @@ -978,8 +1192,12 @@ * The resolve conflict way of three way merging * * @param baseTree + * a {@link org.eclipse.jgit.treewalk.AbstractTreeIterator} + * object. * @param headTree + * a {@link org.eclipse.jgit.revwalk.RevTree} object. * @param mergeTree + * a {@link org.eclipse.jgit.revwalk.RevTree} object. * @param ignoreConflicts * Controls what to do in case a content-merge is done and a * conflict is detected. The default setting for this should be @@ -996,11 +1214,11 @@ * other stages are filled. Means: there is no conflict on that * path but the new content (including conflict markers) is * stored as successful merge result. This is needed in the - * context of {@link RecursiveMerger} where when determining - * merge bases we don't want to deal with content-merge - * conflicts. + * context of {@link org.eclipse.jgit.merge.RecursiveMerger} + * where when determining merge bases we don't want to deal with + * content-merge conflicts. * @return whether the trees merged cleanly - * @throws IOException + * @throws java.io.IOException * @since 3.5 */ protected boolean mergeTrees(AbstractTreeIterator baseTree, @@ -1010,13 +1228,14 @@ builder = dircache.builder(); DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder); - tw = new NameConflictTreeWalk(reader); + tw = new NameConflictTreeWalk(db, reader); tw.addTree(baseTree); tw.addTree(headTree); tw.addTree(mergeTree); - tw.addTree(buildIt); + int dciPos = tw.addTree(buildIt); if (workingTreeIterator != null) { tw.addTree(workingTreeIterator); + workingTreeIterator.setDirCacheIterator(tw, dciPos); } else { tw.setFilter(TreeFilter.ANY_DIFF); } @@ -1062,14 +1281,16 @@ * The walk to iterate over. * @param ignoreConflicts * see - * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} + * {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} * @return Whether the trees merged cleanly. - * @throws IOException + * @throws java.io.IOException * @since 3.5 */ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts) throws IOException { boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE; + boolean hasAttributeNodeProvider = treeWalk + .getAttributesNodeProvider() != null; while (treeWalk.next()) { if (!processEntry( treeWalk.getTree(T_BASE, CanonicalTreeParser.class), @@ -1077,7 +1298,9 @@ treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class), treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class), hasWorkingTreeIterator ? treeWalk.getTree(T_FILE, - WorkingTreeIterator.class) : null, ignoreConflicts)) { + WorkingTreeIterator.class) : null, + ignoreConflicts, hasAttributeNodeProvider + ? treeWalk.getAttributes() : new Attributes())) { cleanUp(); return false; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import java.io.IOException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -74,21 +76,30 @@ treeIndex = index; } + /** {@inheritDoc} */ @Override public String getName() { return strategyName; } + /** {@inheritDoc} */ @Override public Merger newMerger(final Repository db) { return new OneSide(db, treeIndex); } + /** {@inheritDoc} */ @Override public Merger newMerger(final Repository db, boolean inCore) { return new OneSide(db, treeIndex); } + /** {@inheritDoc} */ + @Override + public Merger newMerger(final ObjectInserter inserter, final Config config) { + return new OneSide(inserter, treeIndex); + } + static class OneSide extends Merger { private final int treeIndex; @@ -97,6 +108,11 @@ treeIndex = index; } + protected OneSide(final ObjectInserter inserter, final int index) { + super(inserter); + treeIndex = index; + } + @Override protected boolean mergeImpl() throws IOException { return treeIndex < sourceTrees.length; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.merge; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -52,16 +54,25 @@ */ public class StrategyRecursive extends StrategyResolve { + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(Repository db) { return new RecursiveMerger(db, false); } + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(Repository db, boolean inCore) { return new RecursiveMerger(db, inCore); } + /** {@inheritDoc} */ + @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new RecursiveMerger(inserter, config); + } + + /** {@inheritDoc} */ @Override public String getName() { return "recursive"; //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.merge; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -50,18 +52,27 @@ */ public class StrategyResolve extends ThreeWayMergeStrategy { + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(Repository db) { return new ResolveMerger(db, false); } + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(Repository db, boolean inCore) { return new ResolveMerger(db, inCore); } + /** {@inheritDoc} */ + @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new ResolveMerger(inserter, config); + } + + /** {@inheritDoc} */ @Override public String getName() { return "resolve"; //$NON-NLS-1$ } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,6 +49,7 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -68,27 +69,38 @@ * file contents. */ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { - /** Create a new instance of the strategy. */ + /** + * Create a new instance of the strategy. + */ protected StrategySimpleTwoWayInCore() { // } + /** {@inheritDoc} */ @Override public String getName() { return "simple-two-way-in-core"; //$NON-NLS-1$ } + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(final Repository db) { return new InCoreMerger(db); } + /** {@inheritDoc} */ @Override public ThreeWayMerger newMerger(Repository db, boolean inCore) { // This class is always inCore, so ignore the parameter return newMerger(db); } + /** {@inheritDoc} */ + @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new InCoreMerger(inserter); + } + private static class InCoreMerger extends ThreeWayMerger { private static final int T_BASE = 0; @@ -106,7 +118,13 @@ InCoreMerger(final Repository local) { super(local); - tw = new NameConflictTreeWalk(reader); + tw = new NameConflictTreeWalk(local, reader); + cache = DirCache.newInCore(); + } + + InCoreMerger(final ObjectInserter inserter) { + super(inserter); + tw = new NameConflictTreeWalk(null, reader); cache = DirCache.newInCore(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,13 +50,16 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; -/** A merge of 2 trees, using a common base ancestor tree. */ +/** + * A merge of 2 trees, using a common base ancestor tree. + */ public abstract class ThreeWayMerger extends Merger { private RevTree baseTree; @@ -85,17 +88,28 @@ } /** + * Create a new in-core merge instance from an inserter. + * + * @param inserter + * the inserter to write objects to. + * @since 4.8 + */ + protected ThreeWayMerger(ObjectInserter inserter) { + super(inserter); + } + + /** * Set the common ancestor tree. * * @param id * common base treeish; null to automatically compute the common * base from the input commits during * {@link #merge(AnyObjectId...)}. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object is not a treeish. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. - * @throws IOException + * @throws java.io.IOException * the object could not be read. */ public void setBase(final AnyObjectId id) throws MissingObjectException, @@ -107,6 +121,7 @@ } } + /** {@inheritDoc} */ @Override public boolean merge(final AnyObjectId... tips) throws IOException { if (tips.length != 2) @@ -114,6 +129,7 @@ return super.merge(tips); } + /** {@inheritDoc} */ @Override public ObjectId getBaseCommitId() { return baseCommitId; @@ -124,7 +140,7 @@ * * @return an iterator over the caller-specified merge base, or the natural * merge base of the two input commits. - * @throws IOException + * @throws java.io.IOException */ protected AbstractTreeIterator mergeBase() throws IOException { if (baseTree != null) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,11 +45,15 @@ import org.eclipse.jgit.lib.Repository; -/** A merge strategy to merge 2 trees, using a common base ancestor tree. */ +/** + * A merge strategy to merge 2 trees, using a common base ancestor tree. + */ public abstract class ThreeWayMergeStrategy extends MergeStrategy { + /** {@inheritDoc} */ @Override public abstract ThreeWayMerger newMerger(Repository db); + /** {@inheritDoc} */ @Override public abstract ThreeWayMerger newMerger(Repository db, boolean inCore); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,7 +65,7 @@ */ class GlobalBundleCache { private static final Map> cachedBundles - = new HashMap>(); + = new HashMap<>(); /** * Looks up for a translation bundle in the global cache. If found returns @@ -87,7 +87,7 @@ try { Map bundles = cachedBundles.get(locale); if (bundles == null) { - bundles = new HashMap(); + bundles = new HashMap<>(); cachedBundles.put(locale, bundles); } TranslationBundle bundle = bundles.get(type); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,14 +68,13 @@ * */ public class NLS { - /** The root locale constant. It is defined here because the Locale.ROOT is not defined in Java 5 */ + /** + * The root locale constant. It is defined here because the Locale.ROOT is + * not defined in Java 5 + */ public static final Locale ROOT_LOCALE = new Locale("", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - private static final InheritableThreadLocal local = new InheritableThreadLocal() { - protected NLS initialValue() { - return new NLS(Locale.getDefault()); - } - }; + private static final InheritableThreadLocal local = new InheritableThreadLocal<>(); /** * Sets the locale for the calling thread. @@ -95,31 +94,47 @@ /** * Sets the JVM default locale as the locale for the calling thread. *

      - * Semantically this is equivalent to NLS.setLocale(Locale.getDefault()). + * Semantically this is equivalent to + * NLS.setLocale(Locale.getDefault()). */ public static void useJVMDefaultLocale() { - local.set(new NLS(Locale.getDefault())); + useJVMDefaultInternal(); + } + + // TODO(ms): change signature of public useJVMDefaultLocale() in 5.0 to get + // rid of this internal method + private static NLS useJVMDefaultInternal() { + NLS b = new NLS(Locale.getDefault()); + local.set(b); + return b; } /** * Returns an instance of the translation bundle of the required type. All * public String fields of the bundle instance will get their values - * injected as described in the {@link TranslationBundle}. + * injected as described in the + * {@link org.eclipse.jgit.nls.TranslationBundle}. * - * @param - * required bundle type * @param type * required bundle type * @return an instance of the required bundle type - * @exception TranslationBundleLoadingException see {@link TranslationBundleLoadingException} - * @exception TranslationStringMissingException see {@link TranslationStringMissingException} + * @exception TranslationBundleLoadingException + * see + * {@link org.eclipse.jgit.errors.TranslationBundleLoadingException} + * @exception TranslationStringMissingException + * see + * {@link org.eclipse.jgit.errors.TranslationStringMissingException} */ public static T getBundleFor(Class type) { - return local.get().get(type); + NLS b = local.get(); + if (b == null) { + b = useJVMDefaultInternal(); + } + return b.get(type); } final private Locale locale; - final private ConcurrentHashMap map = new ConcurrentHashMap(); + final private ConcurrentHashMap map = new ConcurrentHashMap<>(); private NLS(Locale locale) { this.locale = locale; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java 2019-09-03 12:37:49.000000000 +0000 @@ -92,21 +92,22 @@ * * * The translated text is automatically injected into the public String fields - * according to the locale set with {@link NLS#setLocale(Locale)}. However, the - * {@link NLS#setLocale(Locale)} method defines only prefered locale which will - * be honored only if it is supported by the provided resource bundle property - * files. Basically, this class will use - * {@link ResourceBundle#getBundle(String, Locale)} method to load a resource - * bundle. See the documentation of this method for a detailed explanation of - * resource bundle loading strategy. After a bundle is created the - * {@link #effectiveLocale()} method can be used to determine whether the bundle - * really corresponds to the requested locale or is a fallback. + * according to the locale set with + * {@link org.eclipse.jgit.nls.NLS#setLocale(Locale)}. However, the + * {@link org.eclipse.jgit.nls.NLS#setLocale(Locale)} method defines only + * prefered locale which will be honored only if it is supported by the provided + * resource bundle property files. Basically, this class will use + * {@link java.util.ResourceBundle#getBundle(String, Locale)} method to load a + * resource bundle. See the documentation of this method for a detailed + * explanation of resource bundle loading strategy. After a bundle is created + * the {@link #effectiveLocale()} method can be used to determine whether the + * bundle really corresponds to the requested locale or is a fallback. * *

      * To load a String from a resource bundle property file this class uses the - * {@link ResourceBundle#getString(String)}. This method can throw the - * {@link MissingResourceException} and this class is not making any effort to - * catch and/or translate this exception. + * {@link java.util.ResourceBundle#getString(String)}. This method can throw the + * {@link java.util.MissingResourceException} and this class is not making any + * effort to catch and/or translate this exception. * *

      * To define a concrete translation bundle one has to: @@ -125,15 +126,20 @@ private ResourceBundle resourceBundle; /** - * @return the locale locale used for loading the resource bundle from which - * the field values were taken + * Get the locale used for loading the resource bundle from which the field + * values were taken. + * + * @return the locale used for loading the resource bundle from which the + * field values were taken. */ public Locale effectiveLocale() { return effectiveLocale; } /** - * @return the resource bundle on which this translation bundle is based + * Get the resource bundle on which this translation bundle is based. + * + * @return the resource bundle on which this translation bundle is based. */ public ResourceBundle resourceBundle() { return resourceBundle; @@ -184,4 +190,4 @@ } } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,7 @@ import org.eclipse.jgit.util.io.UnionInputStream; /** - * Default implementation of the {@link NoteMerger}. + * Default implementation of the {@link org.eclipse.jgit.notes.NoteMerger}. *

      * If ours and theirs are both non-null, which means they are either both edits * or both adds, then this merger will simply join the content of ours and @@ -67,6 +67,8 @@ */ public class DefaultNoteMerger implements NoteMerger { + /** {@inheritDoc} */ + @Override public Note merge(Note base, Note ours, Note theirs, ObjectReader reader, ObjectInserter inserter) throws IOException { if (ours == null) @@ -80,14 +82,11 @@ ObjectLoader lo = reader.open(ours.getData()); ObjectLoader lt = reader.open(theirs.getData()); - UnionInputStream union = new UnionInputStream(lo.openStream(), - lt.openStream()); - try { + try (UnionInputStream union = new UnionInputStream(lo.openStream(), + lt.openStream())) { ObjectId noteData = inserter.insert(Constants.OBJ_BLOB, lo.getSize() + lt.getSize(), union); return new Note(ours, noteData); - } finally { - union.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java 2019-09-03 12:37:49.000000000 +0000 @@ -140,6 +140,7 @@ private Iterator itr; + @Override public boolean hasNext() { if (itr != null && itr.hasNext()) return true; @@ -164,6 +165,7 @@ return false; } + @Override public Note next() { if (hasNext()) return itr.next(); @@ -171,6 +173,7 @@ throw new NoSuchElementException(); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -259,6 +262,7 @@ return inserter.insert(build(true, inserter)); } + @Override ObjectId getTreeId() { try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) { return f.idFor(build(false, null)); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java 2019-09-03 12:37:49.000000000 +0000 @@ -122,10 +122,12 @@ return new Iterator() { private int idx; + @Override public boolean hasNext() { return idx < cnt; } + @Override public Note next() { if (hasNext()) return notes[idx++]; @@ -133,6 +135,7 @@ throw new NoSuchElementException(); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -144,6 +147,7 @@ return cnt; } + @Override InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData, ObjectReader or) throws IOException { int p = search(noteOn); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.util.Paths; /** A tree entry found in a note branch that isn't a valid note. */ class NonNoteEntry extends ObjectId { @@ -74,27 +75,8 @@ } int pathCompare(byte[] bBuf, int bPos, int bLen, FileMode bMode) { - return pathCompare(name, 0, name.length, mode, // - bBuf, bPos, bLen, bMode); - } - - private static int pathCompare(final byte[] aBuf, int aPos, final int aEnd, - final FileMode aMode, final byte[] bBuf, int bPos, final int bEnd, - final FileMode bMode) { - while (aPos < aEnd && bPos < bEnd) { - int cmp = (aBuf[aPos++] & 0xff) - (bBuf[bPos++] & 0xff); - if (cmp != 0) - return cmp; - } - - if (aPos < aEnd) - return (aBuf[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(aMode) - (bBuf[bPos] & 0xff); - return 0; - } - - private static int lastPathChar(final FileMode mode) { - return FileMode.TREE.equals(mode.getBits()) ? '/' : '\0'; + return Paths.compare( + name, 0, name.length, mode.getBits(), + bBuf, bPos, bLen, bMode.getBits()); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; -/** In-memory representation of a single note attached to one object. */ +/** + * In-memory representation of a single note attached to one object. + */ public class Note extends ObjectId { private ObjectId data; @@ -63,7 +65,11 @@ data = noteData; } - /** @return the note content */ + /** + * Get the note content. + * + * @return the note content. + */ public ObjectId getData() { return data; } @@ -72,6 +78,7 @@ data = newData; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,10 +63,11 @@ /** * Index of notes from a note branch. * - * This class is not thread-safe, and relies on an {@link ObjectReader} that it - * borrows/shares with the caller. The reader can be used during any call, and - * is not released by this class. The caller should arrange for releasing the - * shared {@code ObjectReader} at the proper times. + * This class is not thread-safe, and relies on an + * {@link org.eclipse.jgit.lib.ObjectReader} that it borrows/shares with the + * caller. The reader can be used during any call, and is not released by this + * class. The caller should arrange for releasing the shared + * {@code ObjectReader} at the proper times. */ public class NoteMap implements Iterable { /** @@ -81,10 +82,11 @@ } /** - * Shorten the note ref name by trimming off the {@link Constants#R_NOTES} - * prefix if it exists. + * Shorten the note ref name by trimming off the + * {@link org.eclipse.jgit.lib.Constants#R_NOTES} prefix if it exists. * * @param noteRefName + * a {@link java.lang.String} object. * @return a more user friendly note name */ public static String shortenRefName(String noteRefName) { @@ -103,13 +105,13 @@ * @param commit * the revision of the note branch to read. * @return the note map read from the commit. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed through the reader. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * a tree object is corrupt and cannot be read. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * a tree object wasn't actually a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a reference tree object doesn't exist. */ public static NoteMap read(ObjectReader reader, RevCommit commit) @@ -128,13 +130,13 @@ * @param tree * the note tree to read. * @return the note map read from the tree. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed through the reader. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * a tree object is corrupt and cannot be read. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * a tree object wasn't actually a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a reference tree object doesn't exist. */ public static NoteMap read(ObjectReader reader, RevTree tree) @@ -153,13 +155,13 @@ * @param treeId * the note tree to read. * @return the note map read from the tree. - * @throws IOException + * @throws java.io.IOException * the repository cannot be accessed through the reader. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * a tree object is corrupt and cannot be read. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * a tree object wasn't actually a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a reference tree object doesn't exist. */ public static NoteMap readTree(ObjectReader reader, ObjectId treeId) @@ -197,10 +199,8 @@ this.reader = reader; } - /** - * @return an iterator that iterates over notes of this NoteMap. Non note - * entries are ignored by this iterator. - */ + /** {@inheritDoc} */ + @Override public Iterator iterator() { try { return root.iterator(new MutableObjectId(), reader); @@ -215,7 +215,7 @@ * @param id * the object to look for. * @return the note's blob ObjectId, or null if no note exists. - * @throws IOException + * @throws java.io.IOException * a portion of the note space is not accessible. */ public ObjectId get(AnyObjectId id) throws IOException { @@ -229,7 +229,7 @@ * @param id * the object to look for. * @return the note for the given object id, or null if no note exists. - * @throws IOException + * @throws java.io.IOException * a portion of the note space is not accessible. */ public Note getNote(AnyObjectId id) throws IOException { @@ -242,7 +242,7 @@ * @param id * the object to look for. * @return true if a note exists; false if there is no note. - * @throws IOException + * @throws java.io.IOException * a portion of the note space is not accessible. */ public boolean contains(AnyObjectId id) throws IOException { @@ -268,11 +268,11 @@ * larger than this limit, LargeObjectException will be thrown. * @return if a note is defined for {@code id}, the note content. If no note * is defined, null. - * @throws LargeObjectException + * @throws org.eclipse.jgit.errors.LargeObjectException * the note data is larger than {@code sizeLimit}. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the note's blob does not exist in the repository. - * @throws IOException + * @throws java.io.IOException * the note's blob cannot be read from the repository */ public byte[] getCachedBytes(AnyObjectId id, int sizeLimit) @@ -305,7 +305,7 @@ * data to associate with the note. This must be the ObjectId of * a blob that already exists in the repository. If null the note * will be deleted, if present. - * @throws IOException + * @throws java.io.IOException * a portion of the note space is not accessible. */ public void set(AnyObjectId noteOn, ObjectId noteData) throws IOException { @@ -336,7 +336,7 @@ * inserter to write the encoded {@code noteData} out as a blob. * The caller must ensure the inserter is flushed before the * updated note map is made available for reading. - * @throws IOException + * @throws java.io.IOException * the note data could not be stored in the repository. */ public void set(AnyObjectId noteOn, String noteData, ObjectInserter ins) @@ -360,7 +360,7 @@ * * @param noteOn * the object to remove the note from. - * @throws IOException + * @throws java.io.IOException * a portion of the note space is not accessible. */ public void remove(AnyObjectId noteOn) throws IOException { @@ -375,7 +375,7 @@ * Caller is responsible for flushing the inserter before trying * to read the objects, or exposing them through a reference. * @return the top level tree. - * @throws IOException + * @throws java.io.IOException * a tree could not be written. */ public ObjectId writeTree(ObjectInserter inserter) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,14 +56,13 @@ import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ThreeWayMerger; -import org.eclipse.jgit.treewalk.AbstractTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; /** * Three-way note tree merge. *

      - * Direct implementation of NoteMap merger without using {@link TreeWalk} and - * {@link AbstractTreeIterator} + * Direct implementation of NoteMap merger without using + * {@link org.eclipse.jgit.treewalk.TreeWalk} and + * {@link org.eclipse.jgit.treewalk.AbstractTreeIterator} */ public class NoteMapMerger { private static final FanoutBucket EMPTY_FANOUT = new FanoutBucket(0); @@ -83,8 +82,9 @@ private final MutableObjectId objectIdPrefix; /** - * Constructs a NoteMapMerger with custom {@link NoteMerger} and custom - * {@link MergeStrategy}. + * Constructs a NoteMapMerger with custom + * {@link org.eclipse.jgit.notes.NoteMerger} and custom + * {@link org.eclipse.jgit.merge.MergeStrategy}. * * @param db * Git repository @@ -104,9 +104,10 @@ } /** - * Constructs a NoteMapMerger with {@link DefaultNoteMerger} as the merger - * for notes and the {@link MergeStrategy#RESOLVE} as the strategy for - * resolving conflicts on non-notes + * Constructs a NoteMapMerger with + * {@link org.eclipse.jgit.notes.DefaultNoteMerger} as the merger for notes + * and the {@link org.eclipse.jgit.merge.MergeStrategy#RESOLVE} as the + * strategy for resolving conflicts on non-notes * * @param db * Git repository @@ -125,7 +126,7 @@ * @param theirs * theirs version of the note tree * @return merge result as a new NoteMap - * @throws IOException + * @throws java.io.IOException */ public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMerger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMerger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMerger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMerger.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,13 +72,13 @@ * @param inserter * the object inserter that must be used to insert Git objects * @return the merge result - * @throws NotesMergeConflictException + * @throws org.eclipse.jgit.notes.NotesMergeConflictException * in case there was a merge conflict which this note merger * couldn't resolve - * @throws IOException - * in case the reader or the inserter would throw an IOException - * the implementor will most likely want to propagate it as it - * can't do much to recover from it + * @throws java.io.IOException + * in case the reader or the inserter would throw an + * java.io.IOException the implementor will most likely want to + * propagate it as it can't do much to recover from it */ Note merge(Note base, Note ours, Note their, ObjectReader reader, ObjectInserter inserter) throws NotesMergeConflictException, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NotesMergeConflictException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NotesMergeConflictException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/notes/NotesMergeConflictException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/notes/NotesMergeConflictException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,9 @@ import org.eclipse.jgit.internal.JGitText; /** - * This exception will be thrown from the {@link NoteMerger} when a conflict on - * Notes content is found during merge. + * This exception will be thrown from the + * {@link org.eclipse.jgit.notes.NoteMerger} when a conflict on Notes content is + * found during merge. */ public class NotesMergeConflictException extends IOException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,9 @@ import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; -/** Part of a "GIT binary patch" to describe the pre-image or post-image */ +/** + * Part of a "GIT binary patch" to describe the pre-image or post-image + */ public class BinaryHunk { private static final byte[] LITERAL = encodeASCII("literal "); //$NON-NLS-1$ @@ -83,32 +85,56 @@ startOffset = offset; } - /** @return header for the file this hunk applies to */ + /** + * Get header for the file this hunk applies to. + * + * @return header for the file this hunk applies to. + */ public FileHeader getFileHeader() { return file; } - /** @return the byte array holding this hunk's patch script. */ + /** + * Get the byte array holding this hunk's patch script. + * + * @return the byte array holding this hunk's patch script. + */ public byte[] getBuffer() { return file.buf; } - /** @return offset the start of this hunk in {@link #getBuffer()}. */ + /** + * Get offset the start of this hunk in {@link #getBuffer()}. + * + * @return offset the start of this hunk in {@link #getBuffer()}. + */ public int getStartOffset() { return startOffset; } - /** @return offset one past the end of the hunk in {@link #getBuffer()}. */ + /** + * Get offset one past the end of the hunk in {@link #getBuffer()}. + * + * @return offset one past the end of the hunk in {@link #getBuffer()}. + */ public int getEndOffset() { return endOffset; } - /** @return type of this binary hunk */ + /** + * Get type of this binary hunk. + * + * @return type of this binary hunk. + */ public Type getType() { return type; } - /** @return inflated size of this hunk's data */ + /** + * Get inflated size of this hunk's data. + * + * @return inflated size of this hunk's data. + */ public int getSize() { return length; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,19 +73,29 @@ super(b, offset); } + /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public List getHunks() { return (List) super.getHunks(); } - /** @return number of ancestor revisions mentioned in this diff. */ + /** + * {@inheritDoc} + *

      + * + * @return number of ancestor revisions mentioned in this diff. + */ @Override public int getParentCount() { return oldIds.length; } - /** @return get the file mode of the first parent. */ + /** + * {@inheritDoc} + *

      + * @return get the file mode of the first parent. + */ @Override public FileMode getOldMode() { return getOldMode(0); @@ -102,7 +112,12 @@ return oldModes[nthParent]; } - /** @return get the object id of the first parent. */ + /** + * {@inheritDoc} + *

      + * + * @return get the object id of the first parent. + */ @Override public AbbreviatedObjectId getOldId() { return getOldId(0); @@ -119,6 +134,7 @@ return oldIds[nthParent]; } + /** {@inheritDoc} */ @Override public String getScriptText(final Charset ocs, final Charset ncs) { final Charset[] cs = new Charset[getParentCount() + 1]; @@ -128,15 +144,9 @@ } /** + * {@inheritDoc} + *

      * Convert the patch script for this file into a string. - * - * @param charsetGuess - * optional array to suggest the character set to use when - * decoding each file's line. If supplied the array must have a - * length of {@link #getParentCount()} + 1 - * representing the old revision character sets and the new - * revision character set. - * @return the patch script, as a Unicode string. */ @Override public String getScriptText(final Charset[] charsetGuess) { @@ -179,11 +189,12 @@ return ptr; } + /** {@inheritDoc} */ @Override protected void parseIndexLine(int ptr, final int eol) { // "index $asha1,$bsha1..$csha1" // - final List ids = new ArrayList(); + final List ids = new ArrayList<>(); while (ptr < eol) { final int comma = nextLF(buf, ptr, ','); if (eol <= comma) @@ -200,6 +211,7 @@ oldModes = new FileMode[oldIds.length]; } + /** {@inheritDoc} */ @Override protected void parseNewFileMode(final int ptr, final int eol) { for (int i = 0; i < oldModes.length; i++) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,7 +54,9 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.util.MutableInteger; -/** Hunk header for a hunk appearing in a "diff --cc" style patch. */ +/** + * Hunk header for a hunk appearing in a "diff --cc" style patch. + */ public class CombinedHunkHeader extends HunkHeader { private static abstract class CombinedOldImage extends OldImage { int nContext; @@ -76,11 +78,13 @@ } } + /** {@inheritDoc} */ @Override public CombinedFileHeader getFileHeader() { return (CombinedFileHeader) super.getFileHeader(); } + /** {@inheritDoc} */ @Override public OldImage getOldImage() { return getOldImage(0); @@ -266,6 +270,7 @@ } } + @Override void extractFileLines(final StringBuilder sb, final String[] text, final int[] offsets) { final byte[] buf = file.buf; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,7 +69,9 @@ import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.TemporaryBuffer; -/** Patch header describing an action for a single file path. */ +/** + * Patch header describing an action for a single file path. + */ public class FileHeader extends DiffEntry { private static final byte[] OLD_MODE = encodeASCII("old mode "); //$NON-NLS-1$ @@ -164,17 +166,30 @@ return 1; } - /** @return the byte array holding this file's patch script. */ + /** + * Get the byte array holding this file's patch script. + * + * @return the byte array holding this file's patch script. + */ public byte[] getBuffer() { return buf; } - /** @return offset the start of this file's script in {@link #getBuffer()}. */ + /** + * Get offset of the start of this file's script in {@link #getBuffer()}. + * + * @return offset of the start of this file's script in + * {@link #getBuffer()}. + */ public int getStartOffset() { return startOffset; } - /** @return offset one past the end of the file script. */ + /** + * Get offset one past the end of the file script. + * + * @return offset one past the end of the file script. + */ public int getEndOffset() { return endOffset; } @@ -182,8 +197,9 @@ /** * Convert the patch script for this file into a string. *

      - * The default character encoding ({@link Constants#CHARSET}) is assumed for - * both the old and new files. + * The default character encoding + * ({@link org.eclipse.jgit.lib.Constants#CHARSET}) is assumed for both the + * old and new files. * * @return the patch script, as a Unicode string. */ @@ -284,17 +300,29 @@ } } - /** @return style of patch used to modify this file */ + /** + * Get style of patch used to modify this file. + * + * @return style of patch used to modify this file. + */ public PatchType getPatchType() { return patchType; } - /** @return true if this patch modifies metadata about a file */ + /** + * Whether this patch modifies metadata about a file + * + * @return {@code true} if this patch modifies metadata about a file . + */ public boolean hasMetaDataChanges() { return changeType != ChangeType.MODIFY || newMode != oldMode; } - /** @return hunks altering this file; in order of appearance in patch */ + /** + * Get hunks altering this file; in order of appearance in patch + * + * @return hunks altering this file; in order of appearance in patch. + */ public List getHunks() { if (hunks == null) return Collections.emptyList(); @@ -305,7 +333,7 @@ if (h.getFileHeader() != this) throw new IllegalArgumentException(JGitText.get().hunkBelongsToAnotherFile); if (hunks == null) - hunks = new ArrayList(); + hunks = new ArrayList<>(); hunks.add(h); } @@ -313,17 +341,33 @@ return new HunkHeader(this, offset); } - /** @return if a {@link PatchType#GIT_BINARY}, the new-image delta/literal */ + /** + * Get the new-image delta/literal if this is a + * {@link PatchType#GIT_BINARY}. + * + * @return the new-image delta/literal if this is a + * {@link PatchType#GIT_BINARY}. + */ public BinaryHunk getForwardBinaryHunk() { return forwardBinaryHunk; } - /** @return if a {@link PatchType#GIT_BINARY}, the old-image delta/literal */ + /** + * Get the old-image delta/literal if this is a + * {@link PatchType#GIT_BINARY}. + * + * @return the old-image delta/literal if this is a + * {@link PatchType#GIT_BINARY}. + */ public BinaryHunk getReverseBinaryHunk() { return reverseBinaryHunk; } - /** @return a list describing the content edits performed on this file. */ + /** + * Convert to a list describing the content edits performed on this file. + * + * @return a list describing the content edits performed on this file. + */ public EditList toEditList() { final EditList r = new EditList(); for (final HunkHeader hunk : hunks) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,10 +43,14 @@ package org.eclipse.jgit.patch; +import java.util.Locale; + import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.RawParseUtils; -/** An error in a patch script */ +/** + * An error in a patch script + */ public class FormatError { /** Classification of an error. */ public static enum Severity { @@ -73,36 +77,57 @@ message = msg; } - /** @return the severity of the error. */ + /** + * Get the severity of the error. + * + * @return the severity of the error. + */ public Severity getSeverity() { return severity; } - /** @return a message describing the error. */ + /** + * Get a message describing the error. + * + * @return a message describing the error. + */ public String getMessage() { return message; } - /** @return the byte buffer holding the patch script. */ + /** + * Get the byte buffer holding the patch script. + * + * @return the byte buffer holding the patch script. + */ public byte[] getBuffer() { return buf; } - /** @return byte offset within {@link #getBuffer()} where the error is */ + /** + * Get byte offset within {@link #getBuffer()} where the error is. + * + * @return byte offset within {@link #getBuffer()} where the error is. + */ public int getOffset() { return offset; } - /** @return line of the patch script the error appears on. */ + /** + * Get line of the patch script the error appears on. + * + * @return line of the patch script the error appears on. + */ public String getLineText() { final int eol = RawParseUtils.nextLF(buf, offset); return RawParseUtils.decode(Constants.CHARSET, buf, offset, eol); } + /** {@inheritDoc} */ @Override public String toString() { final StringBuilder r = new StringBuilder(); - r.append(getSeverity().name().toLowerCase()); + r.append(getSeverity().name().toLowerCase(Locale.ROOT)); r.append(": at offset "); //$NON-NLS-1$ r.append(getOffset()); r.append(": "); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,9 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.util.MutableInteger; -/** Hunk header describing the layout of a single block of lines */ +/** + * Hunk header describing the layout of a single block of lines + */ public class HunkHeader { /** Details about an old image of the file. */ public abstract static class OldImage { @@ -148,47 +150,83 @@ } } - /** @return header for the file this hunk applies to */ + /** + * Get header for the file this hunk applies to. + * + * @return header for the file this hunk applies to. + */ public FileHeader getFileHeader() { return file; } - /** @return the byte array holding this hunk's patch script. */ + /** + * Get the byte array holding this hunk's patch script. + * + * @return the byte array holding this hunk's patch script. + */ public byte[] getBuffer() { return file.buf; } - /** @return offset the start of this hunk in {@link #getBuffer()}. */ + /** + * Get offset of the start of this hunk in {@link #getBuffer()}. + * + * @return offset of the start of this hunk in {@link #getBuffer()}. + */ public int getStartOffset() { return startOffset; } - /** @return offset one past the end of the hunk in {@link #getBuffer()}. */ + /** + * Get offset one past the end of the hunk in {@link #getBuffer()}. + * + * @return offset one past the end of the hunk in {@link #getBuffer()}. + */ public int getEndOffset() { return endOffset; } - /** @return information about the old image mentioned in this hunk. */ + /** + * Get information about the old image mentioned in this hunk. + * + * @return information about the old image mentioned in this hunk. + */ public OldImage getOldImage() { return old; } - /** @return first line number in the post-image file where the hunk starts */ + /** + * Get first line number in the post-image file where the hunk starts. + * + * @return first line number in the post-image file where the hunk starts. + */ public int getNewStartLine() { return newStartLine; } - /** @return Total number of post-image lines this hunk covers */ + /** + * Get total number of post-image lines this hunk covers. + * + * @return total number of post-image lines this hunk covers. + */ public int getNewLineCount() { return newLineCount; } - /** @return total number of lines of context appearing in this hunk */ + /** + * Get total number of lines of context appearing in this hunk. + * + * @return total number of lines of context appearing in this hunk. + */ public int getLinesContext() { return nContext; } - /** @return a list describing the content edits performed within the hunk. */ + /** + * Convert to a list describing the content edits performed within the hunk. + * + * @return a list describing the content edits performed within the hunk. + */ public EditList toEditList() { if (editList == null) { editList = new EditList(); @@ -404,6 +442,7 @@ offsets[fileIdx] = end < 0 ? s.length() : end + 1; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,9 +44,9 @@ package org.eclipse.jgit.patch; import static org.eclipse.jgit.lib.Constants.encodeASCII; -import static org.eclipse.jgit.patch.FileHeader.isHunkHdr; import static org.eclipse.jgit.patch.FileHeader.NEW_NAME; import static org.eclipse.jgit.patch.FileHeader.OLD_NAME; +import static org.eclipse.jgit.patch.FileHeader.isHunkHdr; import static org.eclipse.jgit.util.RawParseUtils.match; import static org.eclipse.jgit.util.RawParseUtils.nextLF; @@ -58,7 +58,10 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.TemporaryBuffer; -/** A parsed collection of {@link FileHeader}s from a unified diff patch file */ +/** + * A parsed collection of {@link org.eclipse.jgit.patch.FileHeader}s from a + * unified diff patch file + */ public class Patch { static final byte[] DIFF_GIT = encodeASCII("diff --git "); //$NON-NLS-1$ @@ -81,10 +84,12 @@ /** Formatting errors, if any were identified. */ private final List errors; - /** Create an empty patch. */ + /** + * Create an empty patch. + */ public Patch() { - files = new ArrayList(); - errors = new ArrayList(0); + files = new ArrayList<>(); + errors = new ArrayList<>(0); } /** @@ -100,7 +105,11 @@ files.add(fh); } - /** @return list of files described in the patch, in occurrence order. */ + /** + * Get list of files described in the patch, in occurrence order. + * + * @return list of files described in the patch, in occurrence order. + */ public List getFiles() { return files; } @@ -115,7 +124,11 @@ errors.add(err); } - /** @return collection of formatting errors, if any. */ + /** + * Get collection of formatting errors. + * + * @return collection of formatting errors, if any. + */ public List getErrors() { return errors; } @@ -130,7 +143,7 @@ * @param is * the stream to read the patch data from. The stream is read * until EOF is reached. - * @throws IOException + * @throws java.io.IOException * there was an error reading from the input stream. */ public void parse(final InputStream is) throws IOException { @@ -139,10 +152,10 @@ } private static byte[] readFully(final InputStream is) throws IOException { - TemporaryBuffer b = new TemporaryBuffer.Heap(Integer.MAX_VALUE); - b.copy(is); - b.close(); - return b.toByteArray(); + try (TemporaryBuffer b = new TemporaryBuffer.Heap(Integer.MAX_VALUE)) { + b.copy(is); + return b.toByteArray(); + } } /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,8 +44,8 @@ package org.eclipse.jgit.revplot; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevCommit; /** * A commit reference to a commit in the DAG. @@ -149,7 +149,7 @@ * child index to obtain. Must be in the range 0 through * {@link #getChildCount()}-1. * @return the specified child. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * an invalid child index was specified. */ public final PlotCommit getChild(final int nth) { @@ -186,7 +186,7 @@ * ref index to obtain. Must be in the range 0 through * {@link #getRefCount()}-1. * @return the specified ref. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * an invalid ref index was specified. */ public final Ref getRef(final int nth) { @@ -203,6 +203,7 @@ return (L) lane; } + /** {@inheritDoc} */ @Override public void reset() { forkingOffLanes = NO_LANES; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,13 +57,13 @@ import org.eclipse.jgit.revwalk.RevWalk; /** - * An ordered list of {@link PlotCommit} subclasses. + * An ordered list of {@link org.eclipse.jgit.revplot.PlotCommit} subclasses. *

      * Commits are allocated into lanes as they enter the list, based upon their * connections between descendant (child) commits and ancestor (parent) commits. *

      - * The source of the list must be a {@link PlotWalk} and {@link #fillTo(int)} - * must be used to populate the list. + * The source of the list must be a {@link org.eclipse.jgit.revplot.PlotWalk} + * and {@link #fillTo(int)} must be used to populate the list. * * @param * type of lane used by the application. @@ -74,14 +74,15 @@ private int positionsAllocated; - private final TreeSet freePositions = new TreeSet(); + private final TreeSet freePositions = new TreeSet<>(); - private final HashSet activeLanes = new HashSet(32); + private final HashSet activeLanes = new HashSet<>(32); /** number of (child) commits on a lane */ - private final HashMap laneLength = new HashMap( + private final HashMap laneLength = new HashMap<>( 32); + /** {@inheritDoc} */ @Override public void clear() { super.clear(); @@ -91,6 +92,7 @@ laneLength.clear(); } + /** {@inheritDoc} */ @Override public void source(final RevWalk w) { if (!(w instanceof PlotWalk)) @@ -122,6 +124,7 @@ result.add((L) p); } + /** {@inheritDoc} */ @Override protected void enter(final int index, final PlotCommit currCommit) { setupChildren(currCommit); @@ -395,7 +398,11 @@ } /** - * @return a new Lane appropriate for this particular PlotList. + * Create a new {@link PlotLane} appropriate for this particular + * {@link PlotCommitList}. + * + * @return a new {@link PlotLane} appropriate for this particular + * {@link PlotCommitList}. */ @SuppressWarnings("unchecked") protected L createLane() { @@ -407,6 +414,7 @@ * is no longer needed. * * @param lane + * a lane */ protected void recycleLane(final L lane) { // Nothing. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,11 +70,14 @@ import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; -/** Specialized RevWalk for visualization of a commit graph. */ +/** + * Specialized RevWalk for visualization of a commit graph. + */ public class PlotWalk extends RevWalk { private Map> reverseRefMap; + /** {@inheritDoc} */ @Override public void dispose() { super.dispose(); @@ -98,8 +101,7 @@ * * @param refs * additional refs - * - * @throws IOException + * @throws java.io.IOException */ public void addAdditionalRefs(Iterable refs) throws IOException { for (Ref ref : refs) { @@ -107,13 +109,14 @@ if (set == null) set = Collections.singleton(ref); else { - set = new HashSet(set); + set = new HashSet<>(set); set.add(ref); } reverseRefMap.put(ref.getObjectId(), set); } } + /** {@inheritDoc} */ @Override public void sort(final RevSort s, final boolean use) { if (s == RevSort.TOPO && !use) @@ -121,11 +124,13 @@ super.sort(s, use); } + /** {@inheritDoc} */ @Override protected RevCommit createCommit(final AnyObjectId id) { return new PlotCommit(id); } + /** {@inheritDoc} */ @Override public RevCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -147,6 +152,7 @@ } class PlotRefComparator implements Comparator { + @Override public int compare(Ref o1, Ref o2) { try { RevObject obj1 = parseAny(o1.getObjectId()); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -103,13 +103,16 @@ } /** + * {@inheritDoc} + *

      * Remove the first commit from the queue. - * - * @return the first commit of this queue. */ + @Override public abstract RevCommit next(); - /** Remove all entries from this queue. */ + /** + * Remove all entries from this queue. + */ public abstract void clear(); abstract boolean everbodyHasFlag(int f); @@ -121,6 +124,14 @@ return outputType; } + /** + * Describe this queue + * + * @param s + * a StringBuilder + * @param c + * a {@link org.eclipse.jgit.revwalk.RevCommit} + */ protected static void describe(final StringBuilder s, final RevCommit c) { s.append(c.toString()); s.append('\n'); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,11 +59,11 @@ * Obtain the next object. * * @return the object; null if there are no more objects remaining. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object does not exist. There may be more objects * remaining in the iteration, the application should call * {@link #next()} again. - * @throws IOException + * @throws java.io.IOException * the object store cannot be accessed. */ public RevObject next() throws MissingObjectException, IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2012, 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.revwalk; + +import java.io.IOException; +import java.util.Arrays; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.revwalk.AddToBitmapFilter; +import org.eclipse.jgit.internal.revwalk.AddUnseenToBitmapFilter; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BitmapIndex; +import org.eclipse.jgit.lib.BitmapIndex.Bitmap; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.revwalk.filter.ObjectFilter; + +/** + * Helper class to do ObjectWalks with pack index bitmaps. + * + * @since 4.10 + */ +public final class BitmapWalker { + + private final ObjectWalk walker; + + private final BitmapIndex bitmapIndex; + + private final ProgressMonitor pm; + + private long countOfBitmapIndexMisses; + + /** + * Create a BitmapWalker. + * + * @param walker walker to use when traversing the object graph. + * @param bitmapIndex index to obtain bitmaps from. + * @param pm progress monitor to report progress on. + */ + public BitmapWalker( + ObjectWalk walker, BitmapIndex bitmapIndex, ProgressMonitor pm) { + this.walker = walker; + this.bitmapIndex = bitmapIndex; + this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm; + } + + /** + * Return the number of objects that had to be walked because they were not covered by a + * bitmap. + * + * @return the number of objects that had to be walked because they were not covered by a + * bitmap. + */ + public long getCountOfBitmapIndexMisses() { + return countOfBitmapIndexMisses; + } + + /** + * Return, as a bitmap, the objects reachable from the objects in start. + * + * @param start + * the objects to start the object traversal from. + * @param seen + * the objects to skip if encountered during traversal. + * @param ignoreMissing + * true to ignore missing objects, false otherwise. + * @return as a bitmap, the objects reachable from the objects in start. + * @throws org.eclipse.jgit.errors.MissingObjectException + * the object supplied is not available from the object + * database. This usually indicates the supplied object is + * invalid, but the reference was constructed during an earlier + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * the object was not parsed yet and it was discovered during + * parsing that it is not actually the type of the instance + * passed in. This usually indicates the caller used the wrong + * type in a + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)} + * call. + * @throws java.io.IOException + * a pack file or loose object could not be read. + */ + public BitmapBuilder findObjects(Iterable start, BitmapBuilder seen, + boolean ignoreMissing) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + if (!ignoreMissing) { + return findObjectsWalk(start, seen, false); + } + + try { + return findObjectsWalk(start, seen, true); + } catch (MissingObjectException ignore) { + // An object reachable from one of the "start"s is missing. + // Walk from the "start"s one at a time so it can be excluded. + } + + final BitmapBuilder result = bitmapIndex.newBitmapBuilder(); + for (ObjectId obj : start) { + Bitmap bitmap = bitmapIndex.getBitmap(obj); + if (bitmap != null) { + result.or(bitmap); + } + } + + for (ObjectId obj : start) { + if (result.contains(obj)) { + continue; + } + try { + result.or(findObjectsWalk(Arrays.asList(obj), result, false)); + } catch (MissingObjectException ignore) { + // An object reachable from this "start" is missing. + // + // This can happen when the client specified a "have" line + // pointing to an object that is present but unreachable: + // "git prune" and "git fsck" only guarantee that the object + // database will continue to contain all objects reachable + // from a ref and does not guarantee connectivity for other + // objects in the object database. + // + // In this situation, skip the relevant "start" and move on + // to the next one. + // + // TODO(czhen): Make findObjectsWalk resume the walk instead + // once RevWalk and ObjectWalk support that. + } + } + return result; + } + + private BitmapBuilder findObjectsWalk(Iterable start, BitmapBuilder seen, + boolean ignoreMissingStart) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + walker.reset(); + final BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder(); + + for (ObjectId obj : start) { + Bitmap bitmap = bitmapIndex.getBitmap(obj); + if (bitmap != null) + bitmapResult.or(bitmap); + } + + boolean marked = false; + for (ObjectId obj : start) { + try { + if (!bitmapResult.contains(obj)) { + walker.markStart(walker.parseAny(obj)); + marked = true; + } + } catch (MissingObjectException e) { + if (ignoreMissingStart) + continue; + throw e; + } + } + + if (marked) { + if (seen == null) { + walker.setRevFilter(new AddToBitmapFilter(bitmapResult)); + } else { + walker.setRevFilter( + new AddUnseenToBitmapFilter(seen, bitmapResult)); + } + walker.setObjectFilter(new BitmapObjectFilter(bitmapResult)); + + while (walker.next() != null) { + // Iterate through all of the commits. The BitmapRevFilter does + // the work. + // + // filter.include returns true for commits that do not have + // a bitmap in bitmapIndex and are not reachable from a + // bitmap in bitmapIndex encountered earlier in the walk. + // Thus the number of commits returned by next() measures how + // much history was traversed without being able to make use + // of bitmaps. + pm.update(1); + countOfBitmapIndexMisses++; + } + + RevObject ro; + while ((ro = walker.nextObject()) != null) { + bitmapResult.addObject(ro, ro.getType()); + pm.update(1); + } + } + + return bitmapResult; + } + + /** + * Filter that excludes objects already in the given bitmap. + */ + static class BitmapObjectFilter extends ObjectFilter { + private final BitmapBuilder bitmap; + + BitmapObjectFilter(BitmapBuilder bitmap) { + this.bitmap = bitmap; + } + + @Override + public final boolean include(ObjectWalk walker, AnyObjectId objid) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return !bitmap.contains(objid); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,9 @@ abstract class BlockRevQueue extends AbstractRevQueue { protected BlockFreeList free; - /** Create an empty revision queue. */ + /** + * Create an empty revision queue. + */ protected BlockRevQueue() { free = new BlockFreeList(); } @@ -70,6 +72,8 @@ } /** + * {@inheritDoc} + *

      * Reconfigure this queue to share the same free list as another. *

      * Multiple revision queues can be connected to the same free list, making @@ -79,10 +83,8 @@ *

      * Free lists are not thread-safe. Applications must ensure that all queues * sharing the same free list are doing so from only a single thread. - * - * @param q - * the other queue we will steal entries from. */ + @Override public void shareFreeList(final BlockRevQueue q) { free = q.free; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,9 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; -/** A queue of commits sorted by commit time order. */ +/** + * A queue of commits sorted by commit time order. + */ public class DateRevQueue extends AbstractRevQueue { private static final int REBUILD_INDEX_COUNT = 1000; @@ -67,7 +69,9 @@ private int last = -1; - /** Create an empty date queue. */ + /** + * Create an empty date queue. + */ public DateRevQueue() { super(); } @@ -82,6 +86,8 @@ } } + /** {@inheritDoc} */ + @Override public void add(final RevCommit c) { sinceLastIndex++; if (++inQueue > REBUILD_INDEX_COUNT @@ -126,6 +132,8 @@ } } + /** {@inheritDoc} */ + @Override public RevCommit next() { final Entry q = head; if (q == null) @@ -161,6 +169,8 @@ return head != null ? head.commit : null; } + /** {@inheritDoc} */ + @Override public void clear() { head = null; free = null; @@ -170,6 +180,7 @@ last = -1; } + @Override boolean everbodyHasFlag(final int f) { for (Entry q = head; q != null; q = q.next) { if ((q.commit.flags & f) == 0) @@ -178,6 +189,7 @@ return true; } + @Override boolean anybodyHasFlag(final int f) { for (Entry q = head; q != null; q = q.next) { if ((q.commit.flags & f) != 0) @@ -191,6 +203,8 @@ return outputType | SORT_COMMIT_TIME_DESC; } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder s = new StringBuilder(); for (Entry q = head; q != null; q = q.next) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,15 +52,30 @@ import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; -/** Interface for revision walkers that perform depth filtering. */ +/** + * Interface for revision walkers that perform depth filtering. + */ public interface DepthWalk { - /** @return Depth to filter to. */ + /** + * Get depth to filter to. + * + * @return Depth to filter to. + */ public int getDepth(); /** @return flag marking commits that should become unshallow. */ + /** + * Get flag marking commits that should become unshallow. + * + * @return flag marking commits that should become unshallow. + */ public RevFlag getUnshallowFlag(); - /** @return flag marking commits that are interesting again. */ + /** + * Get flag marking commits that are interesting again. + * + * @return flag marking commits that are interesting again. + */ public RevFlag getReinterestingFlag(); /** RevCommit with a depth (in commits) from a root. */ @@ -138,17 +153,31 @@ return new Commit(id); } + @Override public int getDepth() { return depth; } + @Override public RevFlag getUnshallowFlag() { return UNSHALLOW; } + @Override public RevFlag getReinterestingFlag() { return REINTERESTING; } + + /** + * @since 4.5 + */ + @Override + public ObjectWalk toObjectWalkWithSameObjects() { + ObjectWalk ow = new ObjectWalk(reader, depth); + ow.objects = objects; + ow.freeFlags = freeFlags; + return ow; + } } /** Subclass of ObjectWalk that performs depth filtering. */ @@ -228,14 +257,17 @@ return new Commit(id); } + @Override public int getDepth() { return depth; } + @Override public RevFlag getUnshallowFlag() { return UNSHALLOW; } + @Override public RevFlag getReinterestingFlag() { return REINTERESTING; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,13 +48,17 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; -/** A queue of commits in FIFO order. */ +/** + * A queue of commits in FIFO order. + */ public class FIFORevQueue extends BlockRevQueue { private Block head; private Block tail; - /** Create an empty FIFO queue. */ + /** + * Create an empty FIFO queue. + */ public FIFORevQueue() { super(); } @@ -64,6 +68,8 @@ super(s); } + /** {@inheritDoc} */ + @Override public void add(final RevCommit c) { Block b = tail; if (b == null) { @@ -107,6 +113,8 @@ head = b; } + /** {@inheritDoc} */ + @Override public RevCommit next() { final Block b = head; if (b == null) @@ -122,12 +130,15 @@ return c; } + /** {@inheritDoc} */ + @Override public void clear() { head = null; tail = null; free.clear(); } + @Override boolean everbodyHasFlag(final int f) { for (Block b = head; b != null; b = b.next) { for (int i = b.headIndex; i < b.tailIndex; i++) @@ -137,6 +148,7 @@ return true; } + @Override boolean anybodyHasFlag(final int f) { for (Block b = head; b != null; b = b.next) { for (int i = b.headIndex; i < b.tailIndex; i++) @@ -154,6 +166,8 @@ } } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder s = new StringBuilder(); for (Block q = head; q != null; q = q.next) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,9 +57,10 @@ * Includes a commit only if all subfilters include the same commit. *

      * Classic shortcut behavior is used, so evaluation of the - * {@link RevFilter#include(RevWalk, RevCommit)} method stops as soon as a false - * result is obtained. Applications can improve filtering performance by placing - * faster filters that are more likely to reject a result earlier in the list. + * {@link org.eclipse.jgit.revwalk.filter.RevFilter#include(RevWalk, RevCommit)} + * method stops as soon as a false result is obtained. Applications can improve + * filtering performance by placing faster filters that are more likely to + * reject a result earlier in the list. */ public abstract class AndRevFilter extends RevFilter { /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.util.RawCharSequence; import org.eclipse.jgit.util.RawParseUtils; -/** Matches only commits whose author name matches the pattern. */ +/** + * Matches only commits whose author name matches the pattern. + */ public class AuthorRevFilter { /** * Create a new author filter. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.util.RawCharSequence; import org.eclipse.jgit.util.RawParseUtils; -/** Matches only commits whose committer name matches the pattern. */ +/** + * Matches only commits whose committer name matches the pattern. + */ public class CommitterRevFilter { /** * Create a new committer filter. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,9 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -/** Selects commits based upon the commit time field. */ +/** + * Selects commits based upon the commit time field. + */ public abstract class CommitTimeRevFilter extends RevFilter { /** * Create a new filter to select commits before a given date/time. @@ -129,11 +131,13 @@ when = (int) (ts / 1000); } + /** {@inheritDoc} */ @Override public RevFilter clone() { return this; } + /** {@inheritDoc} */ @Override public boolean requiresCommitBody() { return false; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -79,6 +79,7 @@ this.maxCount = maxCount; } + /** {@inheritDoc} */ @Override public boolean include(RevWalk walker, RevCommit cmit) throws StopWalkException, MissingObjectException, @@ -89,6 +90,7 @@ return true; } + /** {@inheritDoc} */ @Override public RevFilter clone() { return new MaxCountRevFilter(maxCount); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.util.RawCharSequence; import org.eclipse.jgit.util.RawParseUtils; -/** Matches only commits whose message matches the pattern. */ +/** + * Matches only commits whose message matches the pattern. + */ public class MessageRevFilter { /** * Create a message filter. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -/** Includes a commit only if the subfilter does not include the commit. */ +/** + * Includes a commit only if the subfilter does not include the commit. + */ public class NotRevFilter extends RevFilter { /** * Create a filter that negates the result of another filter. @@ -69,11 +71,13 @@ a = one; } + /** {@inheritDoc} */ @Override public RevFilter negate() { return a; } + /** {@inheritDoc} */ @Override public boolean include(final RevWalk walker, final RevCommit c) throws MissingObjectException, IncorrectObjectTypeException, @@ -81,16 +85,19 @@ return !a.include(walker, c); } + /** {@inheritDoc} */ @Override public boolean requiresCommitBody() { return a.requiresCommitBody(); } + /** {@inheritDoc} */ @Override public RevFilter clone() { return new NotRevFilter(a.clone()); } + /** {@inheritDoc} */ @Override public String toString() { return "NOT " + a.toString(); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/ObjectFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/ObjectFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/ObjectFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/ObjectFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,7 +54,8 @@ * Selects interesting objects when walking. *

      * Applications should install the filter on an ObjectWalk by - * {@link ObjectWalk#setObjectFilter(ObjectFilter)} prior to starting traversal. + * {@link org.eclipse.jgit.revwalk.ObjectWalk#setObjectFilter(ObjectFilter)} + * prior to starting traversal. * * @since 4.0 */ @@ -77,13 +78,13 @@ * @param objid * the object currently being tested. * @return {@code true} if the named object should be included in the walk. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * an object the filter needed to consult to determine its * answer was missing - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object the filter needed to consult to determine its * answer was of the wrong type - * @throws IOException + * @throws java.io.IOException * an object the filter needed to consult to determine its * answer could not be read. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,9 +57,10 @@ * Includes a commit if any subfilters include the same commit. *

      * Classic shortcut behavior is used, so evaluation of the - * {@link RevFilter#include(RevWalk, RevCommit)} method stops as soon as a true - * result is obtained. Applications can improve filtering performance by placing - * faster filters that are more likely to accept a result earlier in the list. + * {@link org.eclipse.jgit.revwalk.filter.RevFilter#include(RevWalk, RevCommit)} + * method stops as soon as a true result is obtained. Applications can improve + * filtering performance by placing faster filters that are more likely to + * accept a result earlier in the list. */ public abstract class OrRevFilter extends RevFilter { /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,9 +54,10 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.util.RawCharSequence; -/** Abstract filter that searches text using extended regular expressions. */ +/** + * Abstract filter that searches text using extended regular expressions. + */ public abstract class PatternMatchRevFilter extends RevFilter { /** * Encode a string pattern for faster matching on byte arrays. @@ -69,7 +70,7 @@ * original pattern string supplied by the user or the * application. * @return same pattern, but re-encoded to match our funny raw UTF-8 - * character sequence {@link RawCharSequence}. + * character sequence {@link org.eclipse.jgit.util.RawCharSequence}. */ protected static final String forceToRaw(final String patternText) { final byte[] b = Constants.encode(patternText); @@ -97,7 +98,8 @@ * should {@link #forceToRaw(String)} be applied to the pattern * before compiling it? * @param flags - * flags from {@link Pattern} to control how matching performs. + * flags from {@link java.util.regex.Pattern} to control how + * matching performs. */ protected PatternMatchRevFilter(String pattern, final boolean innerString, final boolean rawEncoding, final int flags) { @@ -124,6 +126,7 @@ return patternText; } + /** {@inheritDoc} */ @Override public boolean include(final RevWalk walker, final RevCommit cmit) throws MissingObjectException, IncorrectObjectTypeException, @@ -131,6 +134,7 @@ return compiledPattern.reset(text(cmit)).matches(); } + /** {@inheritDoc} */ @Override public boolean requiresCommitBody() { return true; @@ -145,6 +149,7 @@ */ protected abstract CharSequence text(RevCommit cmit); + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,7 +61,8 @@ * OrRevFilter to create complex boolean expressions. *

      * Applications should install the filter on a RevWalk by - * {@link RevWalk#setRevFilter(RevFilter)} prior to starting traversal. + * {@link org.eclipse.jgit.revwalk.RevWalk#setRevFilter(RevFilter)} prior to + * starting traversal. *

      * Unless specifically noted otherwise a RevFilter implementation is not thread * safe and may not be shared by different RevWalk instances at the same time. @@ -73,23 +74,27 @@ *

      * Message filters: *

        - *
      • Author name/email: {@link AuthorRevFilter}
      • - *
      • Committer name/email: {@link CommitterRevFilter}
      • - *
      • Message body: {@link MessageRevFilter}
      • + *
      • Author name/email: + * {@link org.eclipse.jgit.revwalk.filter.AuthorRevFilter}
      • + *
      • Committer name/email: + * {@link org.eclipse.jgit.revwalk.filter.CommitterRevFilter}
      • + *
      • Message body: + * {@link org.eclipse.jgit.revwalk.filter.MessageRevFilter}
      • *
      * *

      * Merge filters: *

        *
      • Skip all merges: {@link #NO_MERGES}.
      • + *
      • Skip all non-merges: {@link #ONLY_MERGES}
      • *
      * *

      * Boolean modifiers: *

        - *
      • AND: {@link AndRevFilter}
      • - *
      • OR: {@link OrRevFilter}
      • - *
      • NOT: {@link NotRevFilter}
      • + *
      • AND: {@link org.eclipse.jgit.revwalk.filter.AndRevFilter}
      • + *
      • OR: {@link org.eclipse.jgit.revwalk.filter.OrRevFilter}
      • + *
      • NOT: {@link org.eclipse.jgit.revwalk.filter.NotRevFilter}
      • *
      */ public abstract class RevFilter { @@ -143,6 +148,37 @@ } } + /** + * Filter including only merge commits, excluding all commits with less than + * two parents (thread safe). + * + * @since 4.4 + */ + public static final RevFilter ONLY_MERGES = new OnlyMergesFilter(); + + private static final class OnlyMergesFilter extends RevFilter { + + @Override + public boolean include(RevWalk walker, RevCommit c) { + return c.getParentCount() >= 2; + } + + @Override + public RevFilter clone() { + return this; + } + + @Override + public boolean requiresCommitBody() { + return false; + } + + @Override + public String toString() { + return "ONLY_MERGES"; //$NON-NLS-1$ + } + } + /** Excludes commits with more than one parent (thread safe). */ public static final RevFilter NO_MERGES = new NoMergesFilter(); @@ -209,7 +245,11 @@ return NotRevFilter.create(this); } - /** @return true if the filter needs the commit body to be parsed. */ + /** + * Whether the filter needs the commit body to be parsed. + * + * @return true if the filter needs the commit body to be parsed. + */ public boolean requiresCommitBody() { // Assume true to be backward compatible with prior behavior. return true; @@ -226,19 +266,19 @@ * returns true from {@link #requiresCommitBody()}. * @return true to include this commit in the results; false to have this * commit be omitted entirely from the results. - * @throws StopWalkException + * @throws org.eclipse.jgit.errors.StopWalkException * the filter knows for certain that no additional commits can * ever match, and the current commit doesn't match either. The * walk is halted and no more results are provided. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * an object the filter needs to consult to determine its answer * does not exist in the Git repository the walker is operating * on. Filtering this commit is impossible without the object. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object the filter needed to consult was not of the * expected object type. This usually indicates a corrupt * repository, as an object link is referencing the wrong type. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read to obtain data * necessary for the filter to make its decision. */ @@ -247,15 +287,17 @@ IncorrectObjectTypeException, IOException; /** + * {@inheritDoc} + *

      * Clone this revision filter, including its parameters. *

      * This is a deep clone. If this filter embeds objects or other filters it * must also clone those, to ensure the instances do not share mutable data. - * - * @return another copy of this filter, suitable for another thread. */ + @Override public abstract RevFilter clone(); + /** {@inheritDoc} */ @Override public String toString() { String n = getClass().getName(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.revwalk.RevFlagSet; import org.eclipse.jgit.revwalk.RevWalk; -/** Matches only commits with some/all RevFlags already set. */ +/** + * Matches only commits with some/all RevFlags already set. + */ public abstract class RevFlagFilter extends RevFilter { /** * Create a new filter that tests for a single flag. @@ -123,11 +125,13 @@ flags = m; } + /** {@inheritDoc} */ @Override public RevFilter clone() { return this; } + /** {@inheritDoc} */ @Override public String toString() { return super.toString() + flags; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -78,6 +78,7 @@ this.skip = skip; } + /** {@inheritDoc} */ @Override public boolean include(RevWalk walker, RevCommit cmit) throws StopWalkException, MissingObjectException, @@ -87,8 +88,9 @@ return true; } + /** {@inheritDoc} */ @Override public RevFilter clone() { return new SkipRevFilter(skip); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.util.RawCharSequence; import org.eclipse.jgit.util.RawSubStringPattern; -/** Abstract filter that searches text using only substring search. */ +/** + * Abstract filter that searches text using only substring search. + */ public abstract class SubStringRevFilter extends RevFilter { /** * Can this string be safely handled by a substring filter? @@ -60,7 +62,8 @@ * @param pattern * the pattern text proposed by the user. * @return true if a substring filter can perform this pattern match; false - * if {@link PatternMatchRevFilter} must be used instead. + * if {@link org.eclipse.jgit.revwalk.filter.PatternMatchRevFilter} + * must be used instead. */ public static boolean safe(final String pattern) { for (int i = 0; i < pattern.length(); i++) { @@ -97,6 +100,7 @@ pattern = new RawSubStringPattern(patternText); } + /** {@inheritDoc} */ @Override public boolean include(final RevWalk walker, final RevCommit cmit) throws MissingObjectException, IncorrectObjectTypeException, @@ -104,6 +108,7 @@ return pattern.match(text(cmit)) >= 0; } + /** {@inheritDoc} */ @Override public boolean requiresCommitBody() { return true; @@ -118,11 +123,13 @@ */ protected abstract RawCharSequence text(RevCommit cmit); + /** {@inheritDoc} */ @Override public RevFilter clone() { return this; // Typically we are actually thread-safe. } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,7 +59,8 @@ * triggers rename detection so that the path node is updated to include a prior * file name as the RevWalk traverses history. * - * The renames found will be reported to a {@link RenameCallback} if one is set. + * The renames found will be reported to a + * {@link org.eclipse.jgit.revwalk.RenameCallback} if one is set. *

      * Results with this filter are unpredictable if the path being followed is a * subdirectory. @@ -81,7 +82,7 @@ * @param cfg * diff config specifying rename detection options. * @return a new filter for the requested path. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the path supplied was the empty string. * @since 3.0 */ @@ -100,10 +101,16 @@ } /** @return the path this filter matches. */ + /** + * Get the path this filter matches. + * + * @return the path this filter matches. + */ public String getPath() { return path.getPath(); } + /** {@inheritDoc} */ @Override public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, @@ -111,16 +118,19 @@ return path.include(walker) && ANY_DIFF.include(walker); } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return path.shouldBeRecursive() || ANY_DIFF.shouldBeRecursive(); } + /** {@inheritDoc} */ @Override public TreeFilter clone() { return new FollowFilter(path.clone(), cfg); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -130,6 +140,8 @@ } /** + * Get the callback to which renames are reported. + * * @return the callback to which renames are reported, or null * if none */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,9 +43,13 @@ package org.eclipse.jgit.revwalk; +import java.util.Locale; + import org.eclipse.jgit.lib.Constants; -/** Case insensitive key for a {@link FooterLine}. */ +/** + * Case insensitive key for a {@link org.eclipse.jgit.revwalk.FooterLine}. + */ public final class FooterKey { /** Standard {@code Signed-off-by} */ public static final FooterKey SIGNED_OFF_BY = new FooterKey("Signed-off-by"); //$NON-NLS-1$ @@ -68,14 +72,19 @@ */ public FooterKey(final String keyName) { name = keyName; - raw = Constants.encode(keyName.toLowerCase()); + raw = Constants.encode(keyName.toLowerCase(Locale.ROOT)); } - /** @return name of this footer line. */ + /** + * Get name of this footer line. + * + * @return name of this footer line. + */ public String getName() { return name; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java 2019-09-03 12:37:49.000000000 +0000 @@ -80,6 +80,8 @@ } /** + * Whether keys match + * * @param key * key to test this line's key name against. * @return true if {@code key.getName().equalsIgnorecase(getKey())}. @@ -101,6 +103,8 @@ } /** + * Get key name of this footer. + * * @return key name of this footer; that is the text before the ":" on the * line footer's line. The text is decoded according to the commit's * specified (or assumed) character encoding. @@ -110,6 +114,8 @@ } /** + * Get value of this footer. + * * @return value of this footer; that is the text after the ":" and any * leading whitespace has been skipped. May be the empty string if * the footer has no value (line ended with ":"). The text is @@ -144,6 +150,7 @@ return RawParseUtils.decode(enc, buffer, lt, gt - 1); } + /** {@inheritDoc} */ @Override public String toString() { return getKey() + ": " + getValue(); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,11 +49,15 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; -/** A queue of commits in LIFO order. */ +/** + * A queue of commits in LIFO order. + */ public class LIFORevQueue extends BlockRevQueue { private Block head; - /** Create an empty LIFO queue. */ + /** + * Create an empty LIFO queue. + */ public LIFORevQueue() { super(); } @@ -63,6 +67,8 @@ super(s); } + /** {@inheritDoc} */ + @Override public void add(final RevCommit c) { Block b = head; if (b == null || !b.canUnpop()) { @@ -74,6 +80,8 @@ b.unpop(c); } + /** {@inheritDoc} */ + @Override public RevCommit next() { final Block b = head; if (b == null) @@ -87,11 +95,14 @@ return c; } + /** {@inheritDoc} */ + @Override public void clear() { head = null; free.clear(); } + @Override boolean everbodyHasFlag(final int f) { for (Block b = head; b != null; b = b.next) { for (int i = b.headIndex; i < b.tailIndex; i++) @@ -101,6 +112,7 @@ return true; } + @Override boolean anybodyHasFlag(final int f) { for (Block b = head; b != null; b = b.next) { for (int i = b.headIndex; i < b.tailIndex; i++) @@ -110,6 +122,8 @@ return false; } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder s = new StringBuilder(); for (Block q = head; q != null; q = q.next) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,7 @@ import java.io.IOException; import java.text.MessageFormat; +import java.util.LinkedList; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -68,29 +69,27 @@ */ class MergeBaseGenerator extends Generator { private static final int PARSED = RevWalk.PARSED; - private static final int IN_PENDING = RevWalk.SEEN; - private static final int POPPED = RevWalk.TEMP_MARK; - private static final int MERGE_BASE = RevWalk.REWRITE; private final RevWalk walker; - private final DateRevQueue pending; private int branchMask; - private int recarryTest; - private int recarryMask; + private int mergeBaseAncestor = -1; + private LinkedList ret = new LinkedList<>(); + + private CarryStack stack; MergeBaseGenerator(final RevWalk w) { walker = w; pending = new DateRevQueue(); } - void init(final AbstractRevQueue p) { + void init(final AbstractRevQueue p) throws IOException { try { for (;;) { final RevCommit c = p.next(); @@ -98,17 +97,25 @@ break; add(c); } - } finally { - // Always free the flags immediately. This ensures the flags - // will be available for reuse when the walk resets. - // - walker.freeFlag(branchMask); - // Setup the condition used by carryOntoOne to detect a late // merge base and produce it on the next round. // recarryTest = branchMask | POPPED; recarryMask = branchMask | POPPED | MERGE_BASE; + mergeBaseAncestor = walker.allocFlag(); + + for (;;) { + RevCommit c = _next(); + if (c == null) { + break; + } + ret.add(c); + } + } finally { + // Always free the flags immediately. This ensures the flags + // will be available for reuse when the walk resets. + // + walker.freeFlag(branchMask | mergeBaseAncestor); } } @@ -131,8 +138,7 @@ return 0; } - @Override - RevCommit next() throws MissingObjectException, + private RevCommit _next() throws MissingObjectException, IncorrectObjectTypeException, IOException { for (;;) { final RevCommit c = pending.next(); @@ -156,7 +162,7 @@ // also flagged as being popped, so that they do not // generate to the caller. // - carry |= MERGE_BASE; + carry |= MERGE_BASE | mergeBaseAncestor; } carryOntoHistory(c, carry); @@ -179,29 +185,68 @@ } } - private void carryOntoHistory(RevCommit c, final int carry) { + @Override + RevCommit next() throws MissingObjectException, + IncorrectObjectTypeException, IOException { + while (!ret.isEmpty()) { + RevCommit commit = ret.remove(); + if ((commit.flags & mergeBaseAncestor) == 0) { + return commit; + } + } + return null; + } + + private void carryOntoHistory(RevCommit c, int carry) { + stack = null; + for (;;) { + carryOntoHistoryInnerLoop(c, carry); + if (stack == null) { + break; + } + c = stack.c; + carry = stack.carry; + stack = stack.prev; + } + } + + private void carryOntoHistoryInnerLoop(RevCommit c, int carry) { for (;;) { - final RevCommit[] pList = c.parents; - if (pList == null) - return; - final int n = pList.length; - if (n == 0) - return; - - for (int i = 1; i < n; i++) { - final RevCommit p = pList[i]; - if (!carryOntoOne(p, carry)) - carryOntoHistory(p, carry); + RevCommit[] parents = c.parents; + if (parents == null || parents.length == 0) { + break; + } + + int e = parents.length - 1; + for (int i = 0; i < e; i++) { + RevCommit p = parents[i]; + if (carryOntoOne(p, carry) == CONTINUE) { + // Walking p will be required, buffer p on stack. + stack = new CarryStack(stack, p, carry); + } + // For other results from carryOntoOne: + // HAVE_ALL: p has all bits, do nothing to skip that path. + // CONTINUE_ON_STACK: callee pushed StackElement for p. } - c = pList[0]; - if (carryOntoOne(c, carry)) + c = parents[e]; + if (carryOntoOne(c, carry) != CONTINUE) { break; + } } } - private boolean carryOntoOne(final RevCommit p, final int carry) { - final boolean haveAll = (p.flags & carry) == carry; + private static final int CONTINUE = 0; + private static final int HAVE_ALL = 1; + private static final int CONTINUE_ON_STACK = 2; + + private int carryOntoOne(RevCommit p, int carry) { + // If we already had all carried flags, our parents do too. + // Return HAVE_ALL to stop caller from running down this leg + // of the revision graph any further. + // + // Otherwise return CONTINUE to ask the caller to walk history. + int rc = (p.flags & carry) == carry ? HAVE_ALL : CONTINUE; p.flags |= carry; if ((p.flags & recarryMask) == recarryTest) { @@ -209,17 +254,23 @@ // voted to be one. Inject ourselves back at the front of the // pending queue and tell all of our ancestors they are within // the merge base now. - // p.flags &= ~POPPED; pending.add(p); - carryOntoHistory(p, branchMask | MERGE_BASE); - return true; + stack = new CarryStack(stack, p, branchMask | MERGE_BASE); + return CONTINUE_ON_STACK; } + return rc; + } - // If we already had all carried flags, our parents do too. - // Return true to stop the caller from running down this leg - // of the revision graph any further. - // - return haveAll; + private static class CarryStack { + final CarryStack prev; + final RevCommit c; + final int carry; + + CarryStack(CarryStack prev, RevCommit c, int carry) { + this.prev = prev; + this.c = c; + this.carry = carry; + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -76,8 +76,9 @@ * scheduled for inclusion in the results of {@link #nextObject()}, returning * each object exactly once. Objects are sorted and returned according to the * the commits that reference them and the order they appear within a tree. - * Ordering can be affected by changing the {@link RevSort} used to order the - * commits that are returned first. + * Ordering can be affected by changing the + * {@link org.eclipse.jgit.revwalk.RevSort} used to order the commits that are + * returned first. */ public class ObjectWalk extends RevWalk { private static final int ID_SZ = 20; @@ -133,7 +134,7 @@ public ObjectWalk(ObjectReader or) { super(or); setRetainBody(false); - rootObjects = new ArrayList(); + rootObjects = new ArrayList<>(); pendingObjects = new BlockObjQueue(); objectFilter = ObjectFilter.ALL; pathBuf = new byte[256]; @@ -142,34 +143,39 @@ /** * Mark an object or commit to start graph traversal from. *

      - * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)} - * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method - * requires the object to be parsed before it can be added as a root for the - * traversal. + * Callers are encouraged to use + * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as + * this method requires the object to be parsed before it can be added as a + * root for the traversal. *

      * The method will automatically parse an unparsed object, but error * handling may be more difficult for the application to explain why a * RevObject is not actually valid. The object pool of this walker would * also be 'poisoned' by the invalid RevObject. *

      - * This method will automatically call {@link RevWalk#markStart(RevCommit)} - * if passed RevCommit instance, or a RevTag that directly (or indirectly) - * references a RevCommit. + * This method will automatically call + * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed + * RevCommit instance, or a RevTag that directly (or indirectly) references + * a RevCommit. * * @param o * the object to start traversing from. The object passed must be * from this same revision walker. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. This usually indicates the supplied object is * invalid, but the reference was constructed during an earlier - * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}. - * @throws IncorrectObjectTypeException + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually the type of the instance * passed in. This usually indicates the caller used the wrong - * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call. - * @throws IOException + * type in a + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)} + * call. + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void markStart(RevObject o) throws MissingObjectException, @@ -193,33 +199,38 @@ * reachable chain, back until the merge base of an uninteresting commit and * an otherwise interesting commit. *

      - * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)} - * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method - * requires the object to be parsed before it can be added as a root for the - * traversal. + * Callers are encouraged to use + * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as + * this method requires the object to be parsed before it can be added as a + * root for the traversal. *

      * The method will automatically parse an unparsed object, but error * handling may be more difficult for the application to explain why a * RevObject is not actually valid. The object pool of this walker would * also be 'poisoned' by the invalid RevObject. *

      - * This method will automatically call {@link RevWalk#markStart(RevCommit)} - * if passed RevCommit instance, or a RevTag that directly (or indirectly) - * references a RevCommit. + * This method will automatically call + * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed + * RevCommit instance, or a RevTag that directly (or indirectly) references + * a RevCommit. * * @param o * the object to start traversing from. The object passed must be - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. This usually indicates the supplied object is * invalid, but the reference was constructed during an earlier - * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}. - * @throws IncorrectObjectTypeException + * invocation to + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually the type of the instance * passed in. This usually indicates the caller used the wrong - * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call. - * @throws IOException + * type in a + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)} + * call. + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void markUninteresting(RevObject o) throws MissingObjectException, @@ -243,11 +254,14 @@ addObject(o); } + /** {@inheritDoc} */ + @Override public void sort(RevSort s) { super.sort(s); boundary = hasRevSort(RevSort.BOUNDARY); } + /** {@inheritDoc} */ @Override public void sort(RevSort s, boolean use) { super.sort(s, use); @@ -258,7 +272,6 @@ * Get the currently configured object filter. * * @return the current filter. Never null as a filter is always needed. - * * @since 4.0 */ public ObjectFilter getObjectFilter() { @@ -266,9 +279,9 @@ } /** - * Set the object filter for this walker. This filter affects the objects - * visited by {@link #nextObject()}. It does not affect the commits - * listed by {@link #next()}. + * Set the object filter for this walker. This filter affects the objects + * visited by {@link #nextObject()}. It does not affect the commits listed + * by {@link #next()}. *

      * If the filter returns false for an object, then that object is skipped * and objects reachable from it are not enqueued to be walked recursively. @@ -276,9 +289,9 @@ * are known to be uninteresting. * * @param newFilter - * the new filter. If null the special {@link ObjectFilter#ALL} + * the new filter. If null the special + * {@link org.eclipse.jgit.revwalk.filter.ObjectFilter#ALL} * filter will be used instead, as it matches every object. - * * @since 4.0 */ public void setObjectFilter(ObjectFilter newFilter) { @@ -286,6 +299,7 @@ objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL; } + /** {@inheritDoc} */ @Override public RevCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -315,14 +329,14 @@ * Pop the next most recent object. * * @return next most recent object; null if traversal is over. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one or or more of the next objects are not available from the * object database, but were thought to be candidates for * traversal. This usually indicates a broken link. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * one or or more of the objects in a tree do not match the type * indicated. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public RevObject nextObject() throws MissingObjectException, @@ -519,14 +533,14 @@ * exception if there is a connectivity problem. The exception message * provides some detail about the connectivity failure. * - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one or or more of the next objects are not available from the * object database, but were thought to be candidates for * traversal. This usually indicates a broken link. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * one or or more of the objects in a tree do not match the type * indicated. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void checkConnectivity() throws MissingObjectException, @@ -617,14 +631,22 @@ return hash; } - /** @return the internal buffer holding the current path. */ + /** + * Get the internal buffer holding the current path. + * + * @return the internal buffer holding the current path. + */ public byte[] getPathBuffer() { if (pathLen == 0) pathLen = updatePathBuf(currVisit); return pathBuf; } - /** @return length of the path in {@link #getPathBuffer()}. */ + /** + * Get length of the path in {@link #getPathBuffer()}. + * + * @return length of the path in {@link #getPathBuffer()}. + */ public int getPathLength() { if (pathLen == 0) pathLen = updatePathBuf(currVisit); @@ -666,6 +688,7 @@ pathBuf = newBuf; } + /** {@inheritDoc} */ @Override public void dispose() { super.dispose(); @@ -674,6 +697,7 @@ freeVisit = null; } + /** {@inheritDoc} */ @Override protected void reset(final int retainFlags) { super.reset(retainFlags); @@ -681,7 +705,7 @@ for (RevObject obj : rootObjects) obj.flags &= ~IN_PENDING; - rootObjects = new ArrayList(); + rootObjects = new ArrayList<>(); pendingObjects = new BlockObjQueue(); currVisit = null; freeVisit = null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,8 +46,9 @@ /** * An instance of this class can be used in conjunction with a - * {@link FollowFilter}. Whenever a rename has been detected during a revision - * walk, it will be reported here. + * {@link org.eclipse.jgit.revwalk.FollowFilter}. Whenever a rename has been + * detected during a revision walk, it will be reported here. + * * @see FollowFilter#setRenameCallback(RenameCallback) */ public abstract class RenameCallback { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -/** A binary file, or a symbolic link. */ +/** + * A binary file, or a symbolic link. + */ public class RevBlob extends RevObject { /** * Create a new blob reference. @@ -64,6 +66,7 @@ super(id); } + /** {@inheritDoc} */ @Override public final int getType() { return Constants.OBJ_BLOB; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,12 +44,17 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; @@ -61,7 +66,9 @@ import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; -/** A commit reference to a commit in the DAG. */ +/** + * A commit reference to a commit in the DAG. + */ public class RevCommit extends RevObject { private static final int STACK_DEPTH = 500; @@ -74,7 +81,8 @@ * will not have their headers loaded. * * Applications are discouraged from using this API. Callers usually need - * more than one commit. Use {@link RevWalk#parseCommit(AnyObjectId)} to + * more than one commit. Use + * {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)} to * obtain a RevCommit from an existing repository. * * @param raw @@ -110,7 +118,7 @@ * modified by the caller. * @return the parsed commit, in an isolated revision pool that is not * available to the caller. - * @throws IOException + * @throws java.io.IOException * in case of RevWalk initialization fails */ public static RevCommit parse(RevWalk rw, byte[] raw) throws IOException { @@ -215,6 +223,7 @@ flags |= PARSED; } + /** {@inheritDoc} */ @Override public final int getType() { return Constants.OBJ_COMMIT; @@ -239,7 +248,7 @@ if ((p.flags & carry) == carry) continue; p.flags |= carry; - FIFORevQueue q = carryFlags1(c, carry, depth + 1); + FIFORevQueue q = carryFlags1(p, carry, depth + 1); if (q != null) return defer(q, carry, pList, i + 1); } @@ -310,7 +319,7 @@ /** * Time from the "committer " line of the buffer. * - * @return time, expressed as seconds since the epoch. + * @return commit time */ public final int getCommitTime() { return commitTime; @@ -341,7 +350,7 @@ * parent index to obtain. Must be in the range 0 through * {@link #getParentCount()}-1. * @return the specified parent. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * an invalid parent index was specified. */ public final RevCommit getParent(final int nth) { @@ -389,9 +398,10 @@ * should cache the return value for as long as necessary to use all * information from it. *

      - * RevFilter implementations should try to use {@link RawParseUtils} to scan - * the {@link #getRawBuffer()} instead, as this will allow faster evaluation - * of commits. + * RevFilter implementations should try to use + * {@link org.eclipse.jgit.util.RawParseUtils} to scan the + * {@link #getRawBuffer()} instead, as this will allow faster evaluation of + * commits. * * @return identity of the author (name, email) and the time the commit was * made by the author; null if no author line was found. @@ -415,9 +425,10 @@ * should cache the return value for as long as necessary to use all * information from it. *

      - * RevFilter implementations should try to use {@link RawParseUtils} to scan - * the {@link #getRawBuffer()} instead, as this will allow faster evaluation - * of commits. + * RevFilter implementations should try to use + * {@link org.eclipse.jgit.util.RawParseUtils} to scan the + * {@link #getRawBuffer()} instead, as this will allow faster evaluation of + * commits. * * @return identity of the committer (name, email) and the time the commit * was made by the committer; null if no committer line was found. @@ -441,12 +452,12 @@ * @return decoded commit message as a string. Never null. */ public final String getFullMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.commitMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.commitMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ - final Charset enc = RawParseUtils.parseEncoding(raw); - return RawParseUtils.decode(enc, raw, msgB, raw.length); + } + return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); } /** @@ -465,16 +476,17 @@ * spanned multiple lines. Embedded LFs are converted to spaces. */ public final String getShortMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.commitMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.commitMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ + } - final Charset enc = RawParseUtils.parseEncoding(raw); - final int msgE = RawParseUtils.endOfParagraph(raw, msgB); - String str = RawParseUtils.decode(enc, raw, msgB, msgE); - if (hasLF(raw, msgB, msgE)) + int msgE = RawParseUtils.endOfParagraph(raw, msgB); + String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); + if (hasLF(raw, msgB, msgE)) { str = StringUtils.replaceLineBreaksWithSpace(str); + } return str; } @@ -488,18 +500,49 @@ /** * Determine the encoding of the commit message buffer. *

      + * Locates the "encoding" header (if present) and returns its value. Due to + * corruption in the wild this may be an invalid encoding name that is not + * recognized by any character encoding library. + *

      + * If no encoding header is present, null. + * + * @return the preferred encoding of {@link #getRawBuffer()}; or null. + * @since 4.2 + */ + @Nullable + public final String getEncodingName() { + return RawParseUtils.parseEncodingName(buffer); + } + + /** + * Determine the encoding of the commit message buffer. + *

      * Locates the "encoding" header (if present) and then returns the proper * character set to apply to this buffer to evaluate its contents as * character data. *

      - * If no encoding header is present, {@link Constants#CHARSET} is assumed. + * If no encoding header is present {@code UTF-8} is assumed. * * @return the preferred encoding of {@link #getRawBuffer()}. + * @throws IllegalCharsetNameException + * if the character set requested by the encoding header is + * malformed and unsupportable. + * @throws UnsupportedCharsetException + * if the JRE does not support the character set requested by + * the encoding header. */ public final Charset getEncoding() { return RawParseUtils.parseEncoding(buffer); } + private Charset guessEncoding() { + try { + return getEncoding(); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + return UTF_8; + } + } + /** * Parse the footer lines (e.g. "Signed-off-by") for machine processing. *

      @@ -528,8 +571,8 @@ ptr--; final int msgB = RawParseUtils.commitMessage(raw, 0); - final ArrayList r = new ArrayList(4); - final Charset enc = getEncoding(); + final ArrayList r = new ArrayList<>(4); + final Charset enc = guessEncoding(); for (;;) { ptr = RawParseUtils.prevLF(raw, ptr); if (ptr <= msgB) @@ -591,7 +634,7 @@ final List src = getFooterLines(); if (src.isEmpty()) return Collections.emptyList(); - final ArrayList r = new ArrayList(src.size()); + final ArrayList r = new ArrayList<>(src.size()); for (final FooterLine f : src) { if (f.matches(keyName)) r.add(f.getValue()); @@ -617,7 +660,7 @@ * time in {@link #getCommitTime()}. Accessing other properties such as * {@link #getAuthorIdent()}, {@link #getCommitterIdent()} or either message * function requires reloading the buffer by invoking - * {@link RevWalk#parseBody(RevObject)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. * * @since 4.0 */ @@ -625,6 +668,7 @@ buffer = null; } + /** {@inheritDoc} */ @Override public String toString() { final StringBuilder s = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ import org.eclipse.jgit.revwalk.filter.RevFilter; /** - * An ordered list of {@link RevCommit} subclasses. + * An ordered list of {@link org.eclipse.jgit.revwalk.RevCommit} subclasses. * * @param * type of subclass of RevCommit the list is storing. @@ -58,6 +58,7 @@ public class RevCommitList extends RevObjectList { private RevWalk walker; + /** {@inheritDoc} */ @Override public void clear() { super.clear(); @@ -77,15 +78,15 @@ * @param flag * the flag to apply (or remove). Applications are responsible * for allocating this flag from the source RevWalk. - * @throws IOException + * @throws java.io.IOException * revision filter needed to read additional objects, but an * error occurred while reading the pack files or loose objects * of the repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * revision filter needed to read additional objects, but an * object was not of the correct type. Repository corruption may * have occurred. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * revision filter needed to read additional objects, but an * object that should be present was not found. Repository * corruption may have occurred. @@ -117,15 +118,15 @@ * last commit within the list to end testing at, exclusive. If * smaller than or equal to rangeBegin then no * commits will be tested. - * @throws IOException + * @throws java.io.IOException * revision filter needed to read additional objects, but an * error occurred while reading the pack files or loose objects * of the repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * revision filter needed to read additional objects, but an * object was not of the correct type. Repository corruption may * have occurred. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * revision filter needed to read additional objects, but an * object that should be present was not found. Repository * corruption may have occurred. @@ -290,12 +291,12 @@ * @param highMark * number of commits the caller wants this list to contain when * the fill operation is complete. - * @throws IOException - * see {@link RevWalk#next()} - * @throws IncorrectObjectTypeException - * see {@link RevWalk#next()} - * @throws MissingObjectException - * see {@link RevWalk#next()} + * @throws java.io.IOException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} + * @throws org.eclipse.jgit.errors.MissingObjectException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} */ @SuppressWarnings("unchecked") public void fillTo(final int highMark) throws MissingObjectException, @@ -355,12 +356,12 @@ * contain when the fill operation is complete. If highMark is 0 * the walk is pumped until the specified commit or the end of * the walk is reached. - * @throws IOException - * see {@link RevWalk#next()} - * @throws IncorrectObjectTypeException - * see {@link RevWalk#next()} - * @throws MissingObjectException - * see {@link RevWalk#next()} + * @throws java.io.IOException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} + * @throws org.eclipse.jgit.errors.MissingObjectException + * see {@link org.eclipse.jgit.revwalk.RevWalk#next()} */ @SuppressWarnings("unchecked") public void fillTo(final RevCommit commitToLoad, int highMark) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,9 +48,10 @@ import org.eclipse.jgit.internal.JGitText; /** - * Application level mark bit for {@link RevObject}s. + * Application level mark bit for {@link org.eclipse.jgit.revwalk.RevObject}s. *

      - * To create a flag use {@link RevWalk#newFlag(String)}. + * To create a flag use + * {@link org.eclipse.jgit.revwalk.RevWalk#newFlag(String)}. */ public class RevFlag { /** @@ -99,6 +100,8 @@ return walker; } + /** {@inheritDoc} */ + @Override public String toString() { return name; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,8 @@ import java.util.List; /** - * Multiple application level mark bits for {@link RevObject}s. + * Multiple application level mark bits for + * {@link org.eclipse.jgit.revwalk.RevObject}s. * * @see RevFlag */ @@ -59,9 +60,11 @@ private final List active; - /** Create an empty set of flags. */ + /** + * Create an empty set of flags. + */ public RevFlagSet() { - active = new ArrayList(); + active = new ArrayList<>(); } /** @@ -72,7 +75,7 @@ */ public RevFlagSet(final RevFlagSet s) { mask = s.mask; - active = new ArrayList(s.active); + active = new ArrayList<>(s.active); } /** @@ -86,6 +89,7 @@ addAll(s); } + /** {@inheritDoc} */ @Override public boolean contains(final Object o) { if (o instanceof RevFlag) @@ -93,6 +97,7 @@ return false; } + /** {@inheritDoc} */ @Override public boolean containsAll(final Collection c) { if (c instanceof RevFlagSet) { @@ -102,6 +107,7 @@ return super.containsAll(c); } + /** {@inheritDoc} */ @Override public boolean add(final RevFlag flag) { if ((mask & flag.mask) != 0) @@ -114,6 +120,7 @@ return true; } + /** {@inheritDoc} */ @Override public boolean remove(final Object o) { final RevFlag flag = (RevFlag) o; @@ -126,20 +133,24 @@ return true; } + /** {@inheritDoc} */ @Override public Iterator iterator() { final Iterator i = active.iterator(); return new Iterator() { private RevFlag current; + @Override public boolean hasNext() { return i.hasNext(); } + @Override public RevFlag next() { return current = i.next(); } + @Override public void remove() { mask &= ~current.mask; i.remove(); @@ -147,6 +158,7 @@ }; } + /** {@inheritDoc} */ @Override public int size() { return active.size(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; -/** Base object type accessed during revision walking. */ +/** + * Base object type accessed during revision walking. + */ public abstract class RevObject extends ObjectIdOwnerMap.Entry { static final int PARSED = 1; @@ -69,7 +71,7 @@ IncorrectObjectTypeException, IOException; /** - * Get Git object type. See {@link Constants}. + * Get Git object type. See {@link org.eclipse.jgit.lib.Constants}. * * @return object type */ @@ -163,6 +165,7 @@ flags &= ~set.mask; } + /** {@inheritDoc} */ @Override public String toString() { final StringBuilder s = new StringBuilder(); @@ -175,6 +178,8 @@ } /** + * Append a debug description of core RevFlags to a buffer. + * * @param s * buffer to append a debug description of core RevFlags onto. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,7 @@ import org.eclipse.jgit.internal.JGitText; /** - * An ordered list of {@link RevObject} subclasses. + * An ordered list of {@link org.eclipse.jgit.revwalk.RevObject} subclasses. * * @param * type of subclass of RevObject the list is storing. @@ -73,11 +73,15 @@ /** Current number of elements in the list. */ protected int size = 0; - /** Create an empty object list. */ + /** + * Create an empty object list. + */ public RevObjectList() { // Initialized above. } + /** {@inheritDoc} */ + @Override public void add(final int index, final E element) { if (index != size) throw new UnsupportedOperationException(MessageFormat.format( @@ -87,6 +91,8 @@ size++; } + /** {@inheritDoc} */ + @Override @SuppressWarnings("unchecked") public E set(int index, E element) { Block s = contents; @@ -107,6 +113,8 @@ return (E) old; } + /** {@inheritDoc} */ + @Override @SuppressWarnings("unchecked") public E get(int index) { Block s = contents; @@ -120,10 +128,13 @@ return s != null ? (E) s.contents[index] : null; } + /** {@inheritDoc} */ + @Override public int size() { return size; } + /** {@inheritDoc} */ @Override public void clear() { contents = new Block(0); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,10 @@ package org.eclipse.jgit.revwalk; -/** Sorting strategies supported by {@link RevWalk} and {@link ObjectWalk}. */ +/** + * Sorting strategies supported by {@link org.eclipse.jgit.revwalk.RevWalk} and + * {@link org.eclipse.jgit.revwalk.ObjectWalk}. + */ public enum RevSort { /** * No specific sorting is requested. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,8 +45,12 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -60,7 +64,9 @@ import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; -/** An annotated tag. */ +/** + * An annotated tag. + */ public class RevTag extends RevObject { /** * Parse an annotated tag from its canonical format. @@ -71,14 +77,15 @@ * not have its headers loaded. * * Applications are discouraged from using this API. Callers usually need - * more than one object. Use {@link RevWalk#parseTag(AnyObjectId)} to obtain + * more than one object. Use + * {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)} to obtain * a RevTag from an existing repository. * * @param raw * the canonical formatted tag to be parsed. * @return the parsed tag, in an isolated revision pool that is not * available to the caller. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tag contains a malformed header that cannot be handled. */ public static RevTag parse(byte[] raw) throws CorruptObjectException { @@ -105,7 +112,7 @@ * modified by the caller. * @return the parsed tag, in an isolated revision pool that is not * available to the caller. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tag contains a malformed header that cannot be handled. */ public static RevTag parse(RevWalk rw, byte[] raw) @@ -162,13 +169,14 @@ int p = pos.value += 4; // "tag " final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1; - tagName = RawParseUtils.decode(Constants.CHARSET, rawTag, p, nameEnd); + tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd); if (walk.isRetainBody()) buffer = rawTag; flags |= PARSED; } + /** {@inheritDoc} */ @Override public final int getType() { return Constants.OBJ_TAG; @@ -207,12 +215,12 @@ * @return decoded tag message as a string. Never null. */ public final String getFullMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.tagMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.tagMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ - final Charset enc = RawParseUtils.parseEncoding(raw); - return RawParseUtils.decode(enc, raw, msgB, raw.length); + } + return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); } /** @@ -231,29 +239,41 @@ * multiple lines. Embedded LFs are converted to spaces. */ public final String getShortMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.tagMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.tagMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ + } - final Charset enc = RawParseUtils.parseEncoding(raw); - final int msgE = RawParseUtils.endOfParagraph(raw, msgB); - String str = RawParseUtils.decode(enc, raw, msgB, msgE); - if (RevCommit.hasLF(raw, msgB, msgE)) + int msgE = RawParseUtils.endOfParagraph(raw, msgB); + String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); + if (RevCommit.hasLF(raw, msgB, msgE)) { str = StringUtils.replaceLineBreaksWithSpace(str); + } return str; } + private Charset guessEncoding() { + try { + return RawParseUtils.parseEncoding(buffer); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + return UTF_8; + } + } + /** * Get a reference to the object this tag was placed on. *

      * Note that the returned object has only been looked up (see - * {@link RevWalk#lookupAny(AnyObjectId, int)}. To access the contents it - * needs to be parsed, see {@link RevWalk#parseHeaders(RevObject)} and - * {@link RevWalk#parseBody(RevObject)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. To + * access the contents it needs to be parsed, see + * {@link org.eclipse.jgit.revwalk.RevWalk#parseHeaders(RevObject)} and + * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. *

      - * As an alternative, use {@link RevWalk#peel(RevObject)} and pass this - * {@link RevTag} to peel it until the first non-tag object. + * As an alternative, use + * {@link org.eclipse.jgit.revwalk.RevWalk#peel(RevObject)} and pass this + * {@link org.eclipse.jgit.revwalk.RevTag} to peel it until the first + * non-tag object. * * @return object this tag refers to (only looked up, not parsed) */ @@ -277,7 +297,7 @@ * only the {@link #getObject()} pointer and {@link #getTagName()}. * Accessing other properties such as {@link #getTaggerIdent()} or either * message function requires reloading the buffer by invoking - * {@link RevWalk#parseBody(RevObject)}. + * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. * * @since 4.0 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,9 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -/** A reference to a tree of subtrees/files. */ +/** + * A reference to a tree of subtrees/files. + */ public class RevTree extends RevObject { /** * Create a new tree reference. @@ -64,6 +66,7 @@ super(id); } + /** {@inheritDoc} */ @Override public final int getType() { return Constants.OBJ_TREE; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import java.util.Iterator; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.LargeObjectException; @@ -84,10 +85,12 @@ * usage of a RevWalk instance to a single thread, or implement their own * synchronization at a higher level. *

      - * Multiple simultaneous RevWalk instances per {@link Repository} are permitted, - * even from concurrent threads. Equality of {@link RevCommit}s from two - * different RevWalk instances is never true, even if their {@link ObjectId}s - * are equal (and thus they describe the same commit). + * Multiple simultaneous RevWalk instances per + * {@link org.eclipse.jgit.lib.Repository} are permitted, even from concurrent + * threads. Equality of {@link org.eclipse.jgit.revwalk.RevCommit}s from two + * different RevWalk instances is never true, even if their + * {@link org.eclipse.jgit.lib.ObjectId}s are equal (and thus they describe the + * same commit). *

      * The offered iterator is over the list of RevCommits described by the * configuration of this instance. Applications should restrict themselves to @@ -172,7 +175,7 @@ ObjectIdOwnerMap objects; - private int freeFlags = APP_FLAGS; + int freeFlags = APP_FLAGS; private int delayFreeFlags; @@ -226,8 +229,8 @@ private RevWalk(ObjectReader or, boolean closeReader) { reader = or; idBuffer = new MutableObjectId(); - objects = new ObjectIdOwnerMap(); - roots = new ArrayList(); + objects = new ObjectIdOwnerMap<>(); + roots = new ArrayList<>(); queue = new DateRevQueue(); pending = new StartGenerator(this); sorting = EnumSet.of(RevSort.NONE); @@ -236,12 +239,18 @@ this.closeReader = closeReader; } - /** @return the reader this walker is using to load objects. */ + /** + * Get the reader this walker is using to load objects. + * + * @return the reader this walker is using to load objects. + */ public ObjectReader getObjectReader() { return reader; } /** + * {@inheritDoc} + *

      * Release any resources used by this walker's reader. *

      * A walker that has been released can be used again, but may need to be @@ -272,17 +281,17 @@ * @param c * the commit to start traversing from. The commit passed must be * from this same revision walker. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to {@link #lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link #lookupCommit(AnyObjectId)}. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void markStart(final RevCommit c) throws MissingObjectException, @@ -302,17 +311,17 @@ * @param list * commits to start traversing from. The commits passed must be * from this same revision walker. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one of the commits supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to {@link #lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link #lookupCommit(AnyObjectId)}. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void markStart(final Collection list) @@ -342,17 +351,17 @@ * @param c * the commit to start traversing from. The commit passed must be * from this same revision walker. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to {@link #lookupCommit(AnyObjectId)}. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link #lookupCommit(AnyObjectId)}. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void markUninteresting(final RevCommit c) @@ -383,14 +392,14 @@ * @return true if there is a path directly from tip to * base (and thus base is fully merged * into tip); false otherwise. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one or or more of the next commit's parents are not available * from the object database, but were thought to be candidates * for traversal. This usually indicates a broken link. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * one or or more of the next commit's parents are not actually * commit objects. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public boolean isMergedInto(final RevCommit base, final RevCommit tip) @@ -420,14 +429,14 @@ * Pop the next most recent commit. * * @return next most recent commit; null if traversal is over. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * one or or more of the next commit's parents are not available * from the object database, but were thought to be candidates * for traversal. This usually indicates a broken link. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * one or or more of the next commit's parents are not actually * commit objects. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public RevCommit next() throws MissingObjectException, @@ -439,7 +448,8 @@ * Obtain the sort types applied to the commits returned. * * @return the sorting strategies employed. At least one strategy is always - * used, but that strategy may be {@link RevSort#NONE}. + * used, but that strategy may be + * {@link org.eclipse.jgit.revwalk.RevSort#NONE}. */ public EnumSet getRevSort() { return sorting.clone(); @@ -475,8 +485,9 @@ * Add or remove a sorting strategy for the returned commits. *

      * Multiple strategies can be applied at once, in which case some strategies - * may take precedence over others. As an example, {@link RevSort#TOPO} must - * take precedence over {@link RevSort#COMMIT_TIME_DESC}, otherwise it + * may take precedence over others. As an example, + * {@link org.eclipse.jgit.revwalk.RevSort#TOPO} must take precedence over + * {@link org.eclipse.jgit.revwalk.RevSort#COMMIT_TIME_DESC}, otherwise it * cannot enforce its ordering. * * @param s @@ -503,6 +514,7 @@ * * @return the current filter. Never null as a filter is always needed. */ + @NonNull public RevFilter getRevFilter() { return filter; } @@ -518,12 +530,14 @@ * Note that filters are not thread-safe and may not be shared by concurrent * RevWalk instances. Every RevWalk must be supplied its own unique filter, * unless the filter implementation specifically states it is (and always - * will be) thread-safe. Callers may use {@link RevFilter#clone()} to create - * a unique filter tree for this RevWalk instance. + * will be) thread-safe. Callers may use + * {@link org.eclipse.jgit.revwalk.filter.RevFilter#clone()} to create a + * unique filter tree for this RevWalk instance. * * @param newFilter - * the new filter. If null the special {@link RevFilter#ALL} - * filter will be used instead, as it matches every commit. + * the new filter. If null the special + * {@link org.eclipse.jgit.revwalk.filter.RevFilter#ALL} filter + * will be used instead, as it matches every commit. * @see org.eclipse.jgit.revwalk.filter.AndRevFilter * @see org.eclipse.jgit.revwalk.filter.OrRevFilter */ @@ -536,8 +550,11 @@ * Get the tree filter used to simplify commits by modified paths. * * @return the current filter. Never null as a filter is always needed. If - * no filter is being applied {@link TreeFilter#ALL} is returned. + * no filter is being applied + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} is + * returned. */ + @NonNull public TreeFilter getTreeFilter() { return treeFilter; } @@ -545,20 +562,23 @@ /** * Set the tree filter used to simplify commits by modified paths. *

      - * If null or {@link TreeFilter#ALL} the path limiter is removed. Commits - * will not be simplified. + * If null or {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} the + * path limiter is removed. Commits will not be simplified. *

      - * If non-null and not {@link TreeFilter#ALL} then the tree filter will be - * installed. Commits will have their ancestry simplified to hide commits that - * do not contain tree entries matched by the filter, unless - * {@code setRewriteParents(false)} is called. + * If non-null and not + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} then the tree + * filter will be installed. Commits will have their ancestry simplified to + * hide commits that do not contain tree entries matched by the filter, + * unless {@code setRewriteParents(false)} is called. *

      * Usually callers should be inserting a filter graph including - * {@link TreeFilter#ANY_DIFF} along with one or more - * {@link org.eclipse.jgit.treewalk.filter.PathFilter} instances. + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} along with + * one or more {@link org.eclipse.jgit.treewalk.filter.PathFilter} + * instances. * * @param newFilter - * new filter. If null the special {@link TreeFilter#ALL} filter + * new filter. If null the special + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} filter * will be used instead, as it matches everything. * @see org.eclipse.jgit.treewalk.filter.PathFilter */ @@ -571,9 +591,9 @@ * Set whether to rewrite parent pointers when filtering by modified paths. *

      * By default, when {@link #setTreeFilter(TreeFilter)} is called with non- - * null and non-{@link TreeFilter#ALL} filter, commits will have their - * ancestry simplified and parents rewritten to hide commits that do not match - * the filter. + * null and non-{@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} + * filter, commits will have their ancestry simplified and parents rewritten + * to hide commits that do not match the filter. *

      * This behavior can be bypassed by passing false to this method. * @@ -596,8 +616,8 @@ * care and would prefer to discard the body of a commit as early as * possible, to reduce memory usage. *

      - * True by default on {@link RevWalk} and false by default for - * {@link ObjectWalk}. + * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by + * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}. * * @return true if the body should be retained; false it is discarded. */ @@ -608,14 +628,15 @@ /** * Set whether or not the body of a commit or tag is retained. *

      - * If a body of a commit or tag is not retained, the application must - * call {@link #parseBody(RevObject)} before the body can be safely - * accessed through the type specific access methods. + * If a body of a commit or tag is not retained, the application must call + * {@link #parseBody(RevObject)} before the body can be safely accessed + * through the type specific access methods. *

      - * True by default on {@link RevWalk} and false by default for - * {@link ObjectWalk}. + * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by + * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}. * - * @param retain true to retain bodies; false to discard them early. + * @param retain + * true to retain bodies; false to discard them early. */ public void setRetainBody(final boolean retain) { retainBody = retain; @@ -631,6 +652,7 @@ * name of the blob object. * @return reference to the blob object. Never null. */ + @NonNull public RevBlob lookupBlob(final AnyObjectId id) { RevBlob c = (RevBlob) objects.get(id); if (c == null) { @@ -650,6 +672,7 @@ * name of the tree object. * @return reference to the tree object. Never null. */ + @NonNull public RevTree lookupTree(final AnyObjectId id) { RevTree c = (RevTree) objects.get(id); if (c == null) { @@ -672,6 +695,7 @@ * name of the commit object. * @return reference to the commit object. Never null. */ + @NonNull public RevCommit lookupCommit(final AnyObjectId id) { RevCommit c = (RevCommit) objects.get(id); if (c == null) { @@ -691,6 +715,7 @@ * name of the tag object. * @return reference to the tag object. Never null. */ + @NonNull public RevTag lookupTag(final AnyObjectId id) { RevTag c = (RevTag) objects.get(id); if (c == null) { @@ -712,6 +737,7 @@ * type of the object. Must be a valid Git object type. * @return reference to the object. Never null. */ + @NonNull public RevObject lookupAny(final AnyObjectId id, final int type) { RevObject r = objects.get(id); if (r == null) { @@ -759,13 +785,14 @@ * @param id * name of the commit object. * @return reference to the commit object. Never null. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied commit does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ + @NonNull public RevCommit parseCommit(final AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -786,13 +813,14 @@ * name of the tree object, or a commit or annotated tag that may * reference a tree. * @return reference to the tree object. Never null. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied tree does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a tree, a commit or an annotated tag. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ + @NonNull public RevTree parseTree(final AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -820,13 +848,14 @@ * @param id * name of the tag object. * @return reference to the tag object. Never null. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied tag does not exist. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a tag or an annotated tag. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ + @NonNull public RevTag parseTag(final AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { RevObject c = parseAny(id); @@ -847,11 +876,12 @@ * @param id * name of the object. * @return reference to the object. Never null. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ + @NonNull public RevObject parseAny(final AnyObjectId id) throws MissingObjectException, IOException { RevObject r = objects.get(id); @@ -916,8 +946,6 @@ /** * Asynchronous object parsing. * - * @param - * any ObjectId type. * @param objectIds * objects to open from the object store. The supplied collection * must not be modified until the queue has finished. @@ -931,8 +959,8 @@ */ public AsyncRevObjectQueue parseAny( Iterable objectIds, boolean reportMissing) { - List need = new ArrayList(); - List have = new ArrayList(); + List need = new ArrayList<>(); + List have = new ArrayList<>(); for (T id : objectIds) { RevObject r = objects.get(id); if (r != null && (r.flags & PARSED) != 0) @@ -944,14 +972,17 @@ final Iterator objItr = have.iterator(); if (need.isEmpty()) { return new AsyncRevObjectQueue() { + @Override public RevObject next() { return objItr.hasNext() ? objItr.next() : null; } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public void release() { // In-memory only, no action required. } @@ -960,6 +991,7 @@ final AsyncObjectLoaderQueue lItr = reader.open(need, reportMissing); return new AsyncRevObjectQueue() { + @Override public RevObject next() throws MissingObjectException, IncorrectObjectTypeException, IOException { if (objItr.hasNext()) @@ -983,10 +1015,12 @@ return r; } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return lItr.cancel(mayInterruptIfRunning); } + @Override public void release() { lItr.release(); } @@ -1001,9 +1035,9 @@ * * @param obj * the object the caller needs to be parsed. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void parseHeaders(final RevObject obj) @@ -1020,9 +1054,9 @@ * * @param obj * the object the caller needs to be parsed. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public void parseBody(final RevObject obj) @@ -1038,9 +1072,9 @@ * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise * the first non-tag object that {@code obj} references. The * returned object's headers have been parsed. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a referenced object cannot be found. - * @throws IOException + * @throws java.io.IOException * a pack file or loose object could not be read. */ public RevObject peel(RevObject obj) throws MissingObjectException, @@ -1063,7 +1097,7 @@ * @param name * description of the flag, primarily useful for debugging. * @return newly constructed flag instance. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * too many flags have been reserved on this revision walker. */ public RevFlag newFlag(final String name) { @@ -1295,7 +1329,6 @@ retainOnReset = 0; carryFlags = UNINTERESTING; objects.clear(); - reader.close(); roots.clear(); queue = new DateRevQueue(); pending = new StartGenerator(this); @@ -1303,6 +1336,8 @@ } /** + * {@inheritDoc} + *

      * Returns an Iterator over the commits of this walker. *

      * The returned iterator is only useful for one walk. If this RevWalk gets @@ -1311,12 +1346,12 @@ * Applications must not use both the Iterator and the {@link #next()} API * at the same time. Pick one API and use that for the entire walk. *

      - * If a checked exception is thrown during the walk (see {@link #next()}) - * it is rethrown from the Iterator as a {@link RevWalkException}. + * If a checked exception is thrown during the walk (see {@link #next()}) it + * is rethrown from the Iterator as a {@link RevWalkException}. * - * @return an iterator over this walker's commits. * @see RevWalkException */ + @Override public Iterator iterator() { final RevCommit first; try { @@ -1332,10 +1367,12 @@ return new Iterator() { RevCommit next = first; + @Override public boolean hasNext() { return next != null; } + @Override public RevCommit next() { try { final RevCommit r = next; @@ -1350,13 +1387,16 @@ } } + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - /** Throws an exception if we have started producing output. */ + /** + * Throws an exception if we have started producing output. + */ protected void assertNotStarted() { if (isNotStarted()) return; @@ -1368,7 +1408,8 @@ } /** - * Create and return an {@link ObjectWalk} using the same objects. + * Create and return an {@link org.eclipse.jgit.revwalk.ObjectWalk} using + * the same objects. *

      * Prior to using this method, the caller must reset this RevWalk to clean * any flags that were used during the last traversal. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,7 @@ import org.eclipse.jgit.lib.Ref; /** - * Utility methods for {@link RevWalk}. + * Utility methods for {@link org.eclipse.jgit.revwalk.RevWalk}. */ public final class RevWalkUtils { @@ -67,9 +67,10 @@ * other words, count the number of commits that are in start, * but not in end. *

      - * Note that this method calls {@link RevWalk#reset()} at the beginning. - * Also note that the existing rev filter on the walk is left as-is, so be - * sure to set the right rev filter before calling this method. + * Note that this method calls + * {@link org.eclipse.jgit.revwalk.RevWalk#reset()} at the beginning. Also + * note that the existing rev filter on the walk is left as-is, so be sure + * to set the right rev filter before calling this method. * * @param walk * the rev walk to use @@ -78,11 +79,10 @@ * @param end * the commit where counting should end, or null if counting * should be done until there are no more commits - * * @return the number of commits - * @throws MissingObjectException - * @throws IncorrectObjectTypeException - * @throws IOException + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws java.io.IOException */ public static int count(final RevWalk walk, final RevCommit start, final RevCommit end) throws MissingObjectException, @@ -96,9 +96,10 @@ * Find of commits that are in start, but not in * end. *

      - * Note that this method calls {@link RevWalk#reset()} at the beginning. - * Also note that the existing rev filter on the walk is left as-is, so be - * sure to set the right rev filter before calling this method. + * Note that this method calls + * {@link org.eclipse.jgit.revwalk.RevWalk#reset()} at the beginning. Also + * note that the existing rev filter on the walk is left as-is, so be sure + * to set the right rev filter before calling this method. * * @param walk * the rev walk to use @@ -108,9 +109,9 @@ * the commit where counting should end, or null if counting * should be done until there are no more commits * @return the commits found - * @throws MissingObjectException - * @throws IncorrectObjectTypeException - * @throws IOException + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws java.io.IOException */ public static List find(final RevWalk walk, final RevCommit start, final RevCommit end) @@ -121,7 +122,7 @@ if (end != null) walk.markUninteresting(end); - List commits = new ArrayList(); + List commits = new ArrayList<>(); for (RevCommit c : walk) commits.add(c); return commits; @@ -131,7 +132,8 @@ * Find the list of branches a given commit is reachable from when following * parent.s *

      - * Note that this method calls {@link RevWalk#reset()} at the beginning. + * Note that this method calls + * {@link org.eclipse.jgit.revwalk.RevWalk#reset()} at the beginning. *

      * In order to improve performance this method assumes clock skew among * committers is never larger than 24 hours. @@ -143,9 +145,9 @@ * @param refs * the set of branches we want to see reachability from * @return the list of branches a given commit is reachable from - * @throws MissingObjectException - * @throws IncorrectObjectTypeException - * @throws IOException + * @throws org.eclipse.jgit.errors.MissingObjectException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * @throws java.io.IOException */ public static List findBranchesReachableFrom(RevCommit commit, RevWalk revWalk, Collection refs) @@ -155,7 +157,7 @@ // Make sure commit is from the same RevWalk commit = revWalk.parseCommit(commit.getId()); revWalk.reset(); - List result = new ArrayList(); + List result = new ArrayList<>(); final int SKEW = 24*3600; // one day clock skew diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,27 +93,25 @@ @Override RevCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException { - for (;;) { - final RevCommit c = source.next(); - if (c == null) - return null; - - boolean rewrote = false; - final RevCommit[] pList = c.parents; - final int nParents = pList.length; - for (int i = 0; i < nParents; i++) { - final RevCommit oldp = pList[i]; - final RevCommit newp = rewrite(oldp); - if (oldp != newp) { - pList[i] = newp; - rewrote = true; - } + final RevCommit c = source.next(); + if (c == null) { + return null; + } + boolean rewrote = false; + final RevCommit[] pList = c.parents; + final int nParents = pList.length; + for (int i = 0; i < nParents; i++) { + final RevCommit oldp = pList[i]; + final RevCommit newp = rewrite(oldp); + if (oldp != newp) { + pList[i] = newp; + rewrote = true; } - if (rewrote) - c.parents = cleanup(pList); - - return c; } + if (rewrote) { + c.parents = cleanup(pList); + } + return c; } private RevCommit rewrite(RevCommit p) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,10 +60,12 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter; /** - * Filter applying a {@link TreeFilter} against changed paths in each commit. + * Filter applying a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} against + * changed paths in each commit. *

      * Each commit is differenced concurrently against all of its parents to look - * for tree entries that are interesting to the {@link TreeFilter}. + * for tree entries that are interesting to the + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter}. * * @since 3.5 */ @@ -76,14 +78,15 @@ private final TreeWalk pathFilter; /** - * Create a {@link RevFilter} from a {@link TreeFilter}. + * Create a {@link org.eclipse.jgit.revwalk.filter.RevFilter} from a + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter}. * * @param walker * walker used for reading trees. * @param t - * filter to compare against any changed paths in each commit. If a - * {@link FollowFilter}, will be replaced with a new filter - * following new paths after a rename. + * filter to compare against any changed paths in each commit. If + * a {@link org.eclipse.jgit.revwalk.FollowFilter}, will be + * replaced with a new filter following new paths after a rename. * @since 3.5 */ public TreeRevFilter(final RevWalk walker, final TreeFilter t) { @@ -121,11 +124,13 @@ this.rewriteFlag = rewriteFlag; } + /** {@inheritDoc} */ @Override public RevFilter clone() { throw new UnsupportedOperationException(); } + /** {@inheritDoc} */ @Override public boolean include(final RevWalk walker, final RevCommit c) throws StopWalkException, MissingObjectException, @@ -260,6 +265,7 @@ return false; } + /** {@inheritDoc} */ @Override public boolean requiresCommitBody() { return false; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,8 +55,9 @@ import java.io.IOException; import java.text.MessageFormat; -import org.eclipse.jgit.errors.LockFailedException; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.internal.storage.file.LockFile; @@ -65,13 +66,19 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The configuration file that is stored in the file of the file system. */ public class FileBasedConfig extends StoredConfig { + private final static Logger LOG = LoggerFactory + .getLogger(FileBasedConfig.class); + private final File configFile; private final FS fs; @@ -114,67 +121,90 @@ this.hash = ObjectId.zeroId(); } + /** {@inheritDoc} */ @Override protected boolean notifyUponTransientChanges() { // we will notify listeners upon save() return false; } - /** @return location of the configuration file on disk */ + /** + * Get location of the configuration file on disk + * + * @return location of the configuration file on disk + */ public final File getFile() { return configFile; } /** + * {@inheritDoc} + *

      * Load the configuration as a Git text style configuration file. *

      * If the file does not exist, this configuration is cleared, and thus * behaves the same as though the file exists, but is empty. - * - * @throws IOException - * the file could not be read (but does exist). - * @throws ConfigInvalidException - * the file is not a properly formatted configuration file. */ @Override public void load() throws IOException, ConfigInvalidException { - final FileSnapshot oldSnapshot = snapshot; - final FileSnapshot newSnapshot = FileSnapshot.save(getFile()); - try { - final byte[] in = IO.readFully(getFile()); - final ObjectId newHash = hash(in); - if (hash.equals(newHash)) { - if (oldSnapshot.equals(newSnapshot)) - oldSnapshot.setClean(newSnapshot); - else - snapshot = newSnapshot; - } else { - final String decoded; - if (in.length >= 3 && in[0] == (byte) 0xEF - && in[1] == (byte) 0xBB && in[2] == (byte) 0xBF) { - decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, - in, 3, in.length); - utf8Bom = true; + final int maxStaleRetries = 5; + int retries = 0; + while (true) { + final FileSnapshot oldSnapshot = snapshot; + final FileSnapshot newSnapshot = FileSnapshot.save(getFile()); + try { + final byte[] in = IO.readFully(getFile()); + final ObjectId newHash = hash(in); + if (hash.equals(newHash)) { + if (oldSnapshot.equals(newSnapshot)) { + oldSnapshot.setClean(newSnapshot); + } else { + snapshot = newSnapshot; + } } else { - decoded = RawParseUtils.decode(in); + final String decoded; + if (isUtf8(in)) { + decoded = RawParseUtils.decode( + RawParseUtils.UTF8_CHARSET, in, 3, in.length); + utf8Bom = true; + } else { + decoded = RawParseUtils.decode(in); + } + fromText(decoded); + snapshot = newSnapshot; + hash = newHash; + } + return; + } catch (FileNotFoundException noFile) { + if (configFile.exists()) { + throw noFile; } - fromText(decoded); + clear(); snapshot = newSnapshot; - hash = newHash; + return; + } catch (IOException e) { + if (FileUtils.isStaleFileHandle(e) + && retries < maxStaleRetries) { + if (LOG.isDebugEnabled()) { + LOG.debug(MessageFormat.format( + JGitText.get().configHandleIsStale, + Integer.valueOf(retries)), e); + } + retries++; + continue; + } + throw new IOException(MessageFormat + .format(JGitText.get().cannotReadFile, getFile()), e); + } catch (ConfigInvalidException e) { + throw new ConfigInvalidException(MessageFormat + .format(JGitText.get().cannotReadFile, getFile()), e); } - } catch (FileNotFoundException noFile) { - clear(); - snapshot = newSnapshot; - } catch (IOException e) { - final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile())); - e2.initCause(e); - throw e2; - } catch (ConfigInvalidException e) { - throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e); } } /** + * {@inheritDoc} + *

      * Save the configuration as a Git text style configuration file. *

      * Warning: Although this method uses the traditional Git file @@ -182,10 +212,8 @@ * configuration file, it does not ensure that the file has not been * modified since the last read, which means updates performed by other * objects accessing the same backing file may be lost. - * - * @throws IOException - * the file could not be written. */ + @Override public void save() throws IOException { final byte[] out; final String text = toText(); @@ -200,7 +228,7 @@ out = Constants.encode(text); } - final LockFile lf = new LockFile(getFile(), fs); + final LockFile lf = new LockFile(getFile()); if (!lf.lock()) throw new LockFailedException(getFile()); try { @@ -217,6 +245,7 @@ fireConfigChangedEvent(); } + /** {@inheritDoc} */ @Override public void clear() { hash = hash(new byte[0]); @@ -227,6 +256,7 @@ return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText)); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { @@ -234,10 +264,40 @@ } /** + * Whether the currently loaded configuration file is outdated + * * @return returns true if the currently loaded configuration file is older - * than the file on disk + * than the file on disk */ public boolean isOutdated() { return snapshot.isModified(getFile()); } + + /** + * {@inheritDoc} + * + * @since 4.10 + */ + @Override + @Nullable + protected byte[] readIncludedConfig(String relPath) + throws ConfigInvalidException { + final File file; + if (relPath.startsWith("~/")) { //$NON-NLS-1$ + file = fs.resolve(fs.userHome(), relPath.substring(2)); + } else { + file = fs.resolve(configFile.getParentFile(), relPath); + } + + if (!file.exists()) { + return null; + } + + try { + return IO.readFully(file); + } catch (IOException ioe) { + throw new ConfigInvalidException(MessageFormat + .format(JGitText.get().cannotReadFile, relPath), ioe); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,7 @@ import org.eclipse.jgit.lib.Repository; /** - * Constructs a {@link FileRepository}. + * Constructs a {@link org.eclipse.jgit.internal.storage.file.FileRepository}. *

      * Applications must set one of {@link #setGitDir(File)} or * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or @@ -73,18 +73,14 @@ public class FileRepositoryBuilder extends BaseRepositoryBuilder { /** + * {@inheritDoc} + *

      * Create a repository matching the configuration in this builder. *

      * If an option was not set, the build method will try to default the option * based on other options. If insufficient information is available, an * exception is thrown to the caller. * - * @return a repository matching this configuration. - * @throws IllegalArgumentException - * insufficient parameters were set. - * @throws IOException - * the repository could not be accessed to configure the rest of - * the builder's parameters. * @since 3.0 */ @Override @@ -96,12 +92,13 @@ } /** - * Convenience factory method to construct a {@link FileRepository}. + * Convenience factory method to construct a + * {@link org.eclipse.jgit.internal.storage.file.FileRepository}. * * @param gitDir * {@code GIT_DIR}, the repository meta directory. * @return a repository matching this configuration. - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed to configure the rest of * the builder's parameters. * @since 3.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,9 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.storage.pack.PackConfig; -/** Configuration parameters for JVM-wide buffer cache used by JGit. */ +/** + * Configuration parameters for JVM-wide buffer cache used by JGit. + */ public class WindowCacheConfig { /** 1024 (number of bytes in one kibibyte/kilobyte) */ public static final int KB = 1024; @@ -67,7 +69,9 @@ private int streamFileThreshold; - /** Create a default configuration. */ + /** + * Create a default configuration. + */ public WindowCacheConfig() { packedGitOpenFiles = 128; packedGitLimit = 10 * MB; @@ -78,6 +82,8 @@ } /** + * Get maximum number of streams to open at a time. + * * @return maximum number of streams to open at a time. Open packs count * against the process limits. Default is 128. */ @@ -86,6 +92,8 @@ } /** + * Set maximum number of streams to open at a time. + * * @param fdLimit * maximum number of streams to open at a time. Open packs count * against the process limits @@ -95,6 +103,9 @@ } /** + * Get maximum number bytes of heap memory to dedicate to caching pack file + * data. + * * @return maximum number bytes of heap memory to dedicate to caching pack * file data. Default is 10 MB. */ @@ -103,6 +114,9 @@ } /** + * Set maximum number bytes of heap memory to dedicate to caching pack file + * data. + * * @param newLimit * maximum number bytes of heap memory to dedicate to caching * pack file data. @@ -112,6 +126,9 @@ } /** + * Get size in bytes of a single window mapped or read in from the pack + * file. + * * @return size in bytes of a single window mapped or read in from the pack * file. Default is 8 KB. */ @@ -120,6 +137,8 @@ } /** + * Set size in bytes of a single window read in from the pack file. + * * @param newSize * size in bytes of a single window read in from the pack file. */ @@ -128,25 +147,32 @@ } /** - * @return true enables use of Java NIO virtual memory mapping for windows; - * false reads entire window into a byte[] with standard read calls. - * Default false. + * Whether to use Java NIO virtual memory mapping for windows + * + * @return {@code true} enables use of Java NIO virtual memory mapping for + * windows; false reads entire window into a byte[] with standard + * read calls. Default false. */ public boolean isPackedGitMMAP() { return packedGitMMAP; } /** + * Set whether to enable use of Java NIO virtual memory mapping for windows + * * @param usemmap - * true enables use of Java NIO virtual memory mapping for - * windows; false reads entire window into a byte[] with standard - * read calls. + * {@code true} enables use of Java NIO virtual memory mapping + * for windows; false reads entire window into a byte[] with + * standard read calls. */ public void setPackedGitMMAP(final boolean usemmap) { packedGitMMAP = usemmap; } /** + * Get maximum number of bytes to cache in delta base cache for inflated, + * recently accessed objects, without delta chains. + * * @return maximum number of bytes to cache in delta base cache for * inflated, recently accessed objects, without delta chains. * Default 10 MB. @@ -156,6 +182,9 @@ } /** + * Set maximum number of bytes to cache in delta base cache for inflated, + * recently accessed objects, without delta chains. + * * @param newLimit * maximum number of bytes to cache in delta base cache for * inflated, recently accessed objects, without delta chains. @@ -164,12 +193,18 @@ deltaBaseCacheLimit = newLimit; } - /** @return the size threshold beyond which objects must be streamed. */ + /** + * Get the size threshold beyond which objects must be streamed. + * + * @return the size threshold beyond which objects must be streamed. + */ public int getStreamFileThreshold() { return streamFileThreshold; } /** + * Set new byte limit for objects that must be streamed. + * * @param newLimit * new byte limit for objects that must be streamed. Objects * smaller than this size can be obtained as a contiguous byte diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018, David Pursehouse + * 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.storage.file; + +import org.eclipse.jgit.internal.storage.file.WindowCache; + +/** + * Accessor for stats about {@link WindowCache}. + * + * @since 4.11 + * + */ +public class WindowCacheStats { + /** + * @return the number of open files. + */ + public static int getOpenFiles() { + return WindowCache.getInstance().getOpenFiles(); + } + + /** + * @return the number of open bytes. + */ + public static long getOpenBytes() { + return WindowCache.getInstance().getOpenBytes(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,6 +75,20 @@ public static final boolean DEFAULT_REUSE_OBJECTS = true; /** + * Default value of keep old packs option: {@value} + * @see #setPreserveOldPacks(boolean) + * @since 4.7 + */ + public static final boolean DEFAULT_PRESERVE_OLD_PACKS = false; + + /** + * Default value of prune old packs option: {@value} + * @see #setPrunePreserved(boolean) + * @since 4.7 + */ + public static final boolean DEFAULT_PRUNE_PRESERVED = false; + + /** * Default value of delta compress option: {@value} * * @see #setDeltaCompress(boolean) @@ -138,6 +152,65 @@ */ public static final boolean DEFAULT_BUILD_BITMAPS = true; + /** + * Default count of most recent commits to select for bitmaps. Only applies + * when bitmaps are enabled: {@value} + * + * @see #setBitmapContiguousCommitCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT = 100; + + /** + * Count at which the span between selected commits changes from + * "bitmapRecentCommitSpan" to "bitmapDistantCommitSpan". Only applies when + * bitmaps are enabled: {@value} + * + * @see #setBitmapRecentCommitCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_RECENT_COMMIT_COUNT = 20000; + + /** + * Default spacing between commits in recent history when selecting commits + * for bitmaps. Only applies when bitmaps are enabled: {@value} + * + * @see #setBitmapRecentCommitSpan(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_RECENT_COMMIT_SPAN = 100; + + /** + * Default spacing between commits in distant history when selecting commits + * for bitmaps. Only applies when bitmaps are enabled: {@value} + * + * @see #setBitmapDistantCommitSpan(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_DISTANT_COMMIT_SPAN = 5000; + + /** + * Default count of branches required to activate inactive branch commit + * selection. If the number of branches is less than this then bitmaps for + * the entire commit history of all branches will be created, otherwise + * branches marked as "inactive" will have coverage for only partial + * history: {@value} + * + * @see #setBitmapExcessiveBranchCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT = 100; + + /** + * Default age at which a branch is considered inactive. Age is taken as the + * number of days ago that the most recent commit was made to a branch. Only + * affects bitmap processing if bitmaps are enabled and the + * "excessive branch count" has been exceeded: {@value} + * + * @see #setBitmapInactiveBranchAgeInDays(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90; private int compressionLevel = Deflater.DEFAULT_COMPRESSION; @@ -145,6 +218,10 @@ private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; + private boolean preserveOldPacks = DEFAULT_PRESERVE_OLD_PACKS; + + private boolean prunePreserved = DEFAULT_PRUNE_PRESERVED; + private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; private boolean deltaCompress = DEFAULT_DELTA_COMPRESS; @@ -169,9 +246,25 @@ private boolean buildBitmaps = DEFAULT_BUILD_BITMAPS; + private int bitmapContiguousCommitCount = DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT; + + private int bitmapRecentCommitCount = DEFAULT_BITMAP_RECENT_COMMIT_COUNT; + + private int bitmapRecentCommitSpan = DEFAULT_BITMAP_RECENT_COMMIT_SPAN; + + private int bitmapDistantCommitSpan = DEFAULT_BITMAP_DISTANT_COMMIT_SPAN; + + private int bitmapExcessiveBranchCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT; + + private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS; + private boolean cutDeltaChains; - /** Create a default configuration. */ + private boolean singlePack; + + /** + * Create a default configuration. + */ public PackConfig() { // Fields are initialized to defaults. } @@ -189,7 +282,8 @@ } /** - * Create a configuration honoring settings in a {@link Config}. + * Create a configuration honoring settings in a + * {@link org.eclipse.jgit.lib.Config}. * * @param cfg * the source to read settings from. The source is not retained @@ -210,6 +304,8 @@ this.compressionLevel = cfg.compressionLevel; this.reuseDeltas = cfg.reuseDeltas; this.reuseObjects = cfg.reuseObjects; + this.preserveOldPacks = cfg.preserveOldPacks; + this.prunePreserved = cfg.prunePreserved; this.deltaBaseAsOffset = cfg.deltaBaseAsOffset; this.deltaCompress = cfg.deltaCompress; this.maxDeltaDepth = cfg.maxDeltaDepth; @@ -222,7 +318,14 @@ this.executor = cfg.executor; this.indexVersion = cfg.indexVersion; this.buildBitmaps = cfg.buildBitmaps; + this.bitmapContiguousCommitCount = cfg.bitmapContiguousCommitCount; + this.bitmapRecentCommitCount = cfg.bitmapRecentCommitCount; + this.bitmapRecentCommitSpan = cfg.bitmapRecentCommitSpan; + this.bitmapDistantCommitSpan = cfg.bitmapDistantCommitSpan; + this.bitmapExcessiveBranchCount = cfg.bitmapExcessiveBranchCount; + this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays; this.cutDeltaChains = cfg.cutDeltaChains; + this.singlePack = cfg.singlePack; } /** @@ -287,6 +390,61 @@ } /** + * Checks whether to preserve old packs in a preserved directory + * + * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS} + * + * @return true if repacking will preserve old pack files. + * @since 4.7 + */ + public boolean isPreserveOldPacks() { + return preserveOldPacks; + } + + /** + * Set preserve old packs configuration option for repacking. + * + * If enabled, old pack files are moved into a preserved subdirectory instead + * of being deleted + * + * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS} + * + * @param preserveOldPacks + * boolean indicating whether or not preserve old pack files + * @since 4.7 + */ + public void setPreserveOldPacks(boolean preserveOldPacks) { + this.preserveOldPacks = preserveOldPacks; + } + + /** + * Checks whether to remove preserved pack files in a preserved directory + * + * Default setting: {@value #DEFAULT_PRUNE_PRESERVED} + * + * @return true if repacking will remove preserved pack files. + * @since 4.7 + */ + public boolean isPrunePreserved() { + return prunePreserved; + } + + /** + * Set prune preserved configuration option for repacking. + * + * If enabled, preserved pack files are removed from a preserved subdirectory + * + * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS} + * + * @param prunePreserved + * boolean indicating whether or not preserve old pack files + * @since 4.7 + */ + public void setPrunePreserved(boolean prunePreserved) { + this.prunePreserved = prunePreserved; + } + + /** * True if writer can use offsets to point to a delta base. * * If true the writer may choose to use an offset to point to a delta base @@ -376,6 +534,9 @@ } /** + * Whether existing delta chains should be cut at + * {@link #getMaxDeltaDepth()}. + * * @return true if existing delta chains should be cut at * {@link #getMaxDeltaDepth()}. Default is false, allowing existing * chains to be of any length. @@ -403,6 +564,32 @@ } /** + * Whether all of refs/* should be packed in a single pack. + * + * @return true if all of refs/* should be packed in a single pack. Default + * is false, packing a separate GC_REST pack for references outside + * of refs/heads/* and refs/tags/*. + * @since 4.9 + */ + public boolean getSinglePack() { + return singlePack; + } + + /** + * If {@code true}, packs a single GC pack for all objects reachable from + * refs/*. Otherwise packs the GC pack with objects reachable from + * refs/heads/* and refs/tags/*, and a GC_REST pack with the remaining + * reachable objects. Disabled by default, packing GC and GC_REST. + * + * @param single + * true to pack a single GC pack rather than GC and GC_REST packs + * @since 4.9 + */ + public void setSinglePack(boolean single) { + singlePack = single; + } + + /** * Get the number of objects to try when looking for a delta base. * * This limit is per thread, if 4 threads are used the actual memory used @@ -606,7 +793,11 @@ this.threads = threads; } - /** @return the preferred thread pool to execute delta search on. */ + /** + * Get the preferred thread pool to execute delta search on. + * + * @return the preferred thread pool to execute delta search on. + */ public Executor getExecutor() { return executor; } @@ -650,7 +841,7 @@ * oldest (most compatible) format available for the objects. * @see PackIndexWriter */ - public void setIndexVersion(final int version) { + public void setIndexVersion(int version) { indexVersion = version; } @@ -684,6 +875,162 @@ } /** + * Get the count of most recent commits for which to build bitmaps. + * + * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT} + * + * @return the count of most recent commits for which to build bitmaps + * @since 4.2 + */ + public int getBitmapContiguousCommitCount() { + return bitmapContiguousCommitCount; + } + + /** + * Set the count of most recent commits for which to build bitmaps. + * + * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT} + * + * @param count + * the count of most recent commits for which to build bitmaps + * @since 4.2 + */ + public void setBitmapContiguousCommitCount(int count) { + bitmapContiguousCommitCount = count; + } + + /** + * Get the count at which to switch from "bitmapRecentCommitSpan" to + * "bitmapDistantCommitSpan". + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT} + * + * @return the count for switching between recent and distant spans + * @since 4.2 + */ + public int getBitmapRecentCommitCount() { + return bitmapRecentCommitCount; + } + + /** + * Set the count at which to switch from "bitmapRecentCommitSpan" to + * "bitmapDistantCommitSpan". + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT} + * + * @param count + * the count for switching between recent and distant spans + * @since 4.2 + */ + public void setBitmapRecentCommitCount(int count) { + bitmapRecentCommitCount = count; + } + + /** + * Get the span of commits when building bitmaps for recent history. + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN} + * + * @return the span of commits when building bitmaps for recent history + * @since 4.2 + */ + public int getBitmapRecentCommitSpan() { + return bitmapRecentCommitSpan; + } + + /** + * Set the span of commits when building bitmaps for recent history. + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN} + * + * @param span + * the span of commits when building bitmaps for recent history + * @since 4.2 + */ + public void setBitmapRecentCommitSpan(int span) { + bitmapRecentCommitSpan = span; + } + + /** + * Get the span of commits when building bitmaps for distant history. + * + * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN} + * + * @return the span of commits when building bitmaps for distant history + * @since 4.2 + */ + public int getBitmapDistantCommitSpan() { + return bitmapDistantCommitSpan; + } + + /** + * Set the span of commits when building bitmaps for distant history. + * + * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN} + * + * @param span + * the span of commits when building bitmaps for distant history + * @since 4.2 + */ + public void setBitmapDistantCommitSpan(int span) { + bitmapDistantCommitSpan = span; + } + + /** + * Get the count of branches deemed "excessive". If the count of branches in + * a repository exceeds this number and bitmaps are enabled, "inactive" + * branches will have fewer bitmaps than "active" branches. + * + * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} + * + * @return the count of branches deemed "excessive" + * @since 4.2 + */ + public int getBitmapExcessiveBranchCount() { + return bitmapExcessiveBranchCount; + } + + /** + * Set the count of branches deemed "excessive". If the count of branches in + * a repository exceeds this number and bitmaps are enabled, "inactive" + * branches will have fewer bitmaps than "active" branches. + * + * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} + * + * @param count + * the count of branches deemed "excessive" + * @since 4.2 + */ + public void setBitmapExcessiveBranchCount(int count) { + bitmapExcessiveBranchCount = count; + } + + /** + * Get the the age in days that marks a branch as "inactive". + * + * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} + * + * @return the age in days that marks a branch as "inactive" + * @since 4.2 + */ + public int getBitmapInactiveBranchAgeInDays() { + return bitmapInactiveBranchAgeInDays; + } + + /** + * Set the the age in days that marks a branch as "inactive". + * + * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} + * + * @param ageInDays + * the age in days that marks a branch as "inactive" + * @since 4.2 + */ + public void setBitmapInactiveBranchAgeInDays(int ageInDays) { + bitmapInactiveBranchAgeInDays = ageInDays; + } + + /** * Update properties by setting fields from the configuration. * * If a property's corresponding variable is not defined in the supplied @@ -712,19 +1059,40 @@ // These variables aren't standardized // setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas())); //$NON-NLS-1$ //$NON-NLS-2$ - setReuseObjects(rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$ - setDeltaCompress(rc.getBoolean( - "pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$ - setCutDeltaChains(rc.getBoolean( - "pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$ - setBuildBitmaps(rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$ + setReuseObjects( + rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$ + setDeltaCompress( + rc.getBoolean("pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$ + setCutDeltaChains( + rc.getBoolean("pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$ + setSinglePack( + rc.getBoolean("pack", "singlepack", getSinglePack())); //$NON-NLS-1$ //$NON-NLS-2$ + setBuildBitmaps( + rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$ + setBitmapContiguousCommitCount( + rc.getInt("pack", "bitmapcontiguouscommitcount", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapContiguousCommitCount())); + setBitmapRecentCommitCount(rc.getInt("pack", "bitmaprecentcommitcount", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapRecentCommitCount())); + setBitmapRecentCommitSpan(rc.getInt("pack", "bitmaprecentcommitspan", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapRecentCommitSpan())); + setBitmapDistantCommitSpan(rc.getInt("pack", "bitmapdistantcommitspan", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapDistantCommitSpan())); + setBitmapExcessiveBranchCount(rc.getInt("pack", //$NON-NLS-1$ + "bitmapexcessivebranchcount", getBitmapExcessiveBranchCount())); //$NON-NLS-1$ + setBitmapInactiveBranchAgeInDays( + rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapInactiveBranchAgeInDays())); } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder b = new StringBuilder(); b.append("maxDeltaDepth=").append(getMaxDeltaDepth()); //$NON-NLS-1$ b.append(", deltaSearchWindowSize=").append(getDeltaSearchWindowSize()); //$NON-NLS-1$ - b.append(", deltaSearchMemoryLimit=").append(getDeltaSearchMemoryLimit()); //$NON-NLS-1$ + b.append(", deltaSearchMemoryLimit=") //$NON-NLS-1$ + .append(getDeltaSearchMemoryLimit()); b.append(", deltaCacheSize=").append(getDeltaCacheSize()); //$NON-NLS-1$ b.append(", deltaCacheLimit=").append(getDeltaCacheLimit()); //$NON-NLS-1$ b.append(", compressionLevel=").append(getCompressionLevel()); //$NON-NLS-1$ @@ -735,6 +1103,19 @@ b.append(", reuseObjects=").append(isReuseObjects()); //$NON-NLS-1$ b.append(", deltaCompress=").append(isDeltaCompress()); //$NON-NLS-1$ b.append(", buildBitmaps=").append(isBuildBitmaps()); //$NON-NLS-1$ + b.append(", bitmapContiguousCommitCount=") //$NON-NLS-1$ + .append(getBitmapContiguousCommitCount()); + b.append(", bitmapRecentCommitCount=") //$NON-NLS-1$ + .append(getBitmapRecentCommitCount()); + b.append(", bitmapRecentCommitSpan=") //$NON-NLS-1$ + .append(getBitmapRecentCommitSpan()); + b.append(", bitmapDistantCommitSpan=") //$NON-NLS-1$ + .append(getBitmapDistantCommitSpan()); + b.append(", bitmapExcessiveBranchCount=") //$NON-NLS-1$ + .append(getBitmapExcessiveBranchCount()); + b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$ + .append(getBitmapInactiveBranchAgeInDays()); + b.append(", singlePack=").append(getSinglePack()); //$NON-NLS-1$ return b.toString(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java 2019-09-03 12:37:49.000000000 +0000 @@ -166,6 +166,36 @@ * POJO for accumulating the statistics. */ public static class Accumulator { + /** + * The count of references in the ref advertisement. + * + * @since 4.11 + */ + public long advertised; + + /** + * The count of client wants. + * + * @since 4.11 + */ + public long wants; + + /** + * The count of client haves. + * + * @since 4.11 + */ + public long haves; + + /** + * Time in ms spent in the negotiation phase. For non-bidirectional + * transports (e.g., HTTP), this is only for the final request that + * sends back the pack file. + * + * @since 4.11 + */ + public long timeNegotiating; + /** The set of objects to be included in the pack. */ public Set interestingObjects; @@ -254,7 +284,8 @@ private Accumulator statistics; /** - * Creates a new {@link PackStatistics} object from the accumulator. + * Creates a new {@link org.eclipse.jgit.storage.pack.PackStatistics} object + * from the accumulator. * * @param accumulator * the accumulator of the statistics @@ -270,6 +301,50 @@ } /** + * Get the count of references in the ref advertisement. + * + * @return count of refs in the ref advertisement. + * @since 4.11 + */ + public long getAdvertised() { + return statistics.advertised; + } + + /** + * Get the count of client wants. + * + * @return count of client wants. + * @since 4.11 + */ + public long getWants() { + return statistics.wants; + } + + /** + * Get the count of client haves. + * + * @return count of client haves. + * @since 4.11 + */ + public long getHaves() { + return statistics.haves; + } + + /** + * Time in ms spent in the negotiation phase. For non-bidirectional + * transports (e.g., HTTP), this is only for the final request that sends + * back the pack file. + * + * @return time for ref advertisement in ms. + * @since 4.11 + */ + public long getTimeNegotiating() { + return statistics.timeNegotiating; + } + + /** + * Get unmodifiable collection of objects to be included in the pack. + * * @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. */ @@ -278,6 +353,9 @@ } /** + * Get unmodifiable collection of objects that should be excluded from the + * pack + * * @return unmodifiable collection of objects that should be excluded from * the pack, as the peer that will receive the pack already has * these objects. @@ -287,6 +365,9 @@ } /** + * Get unmodifiable collection of objects that were shallow commits on the + * client. + * * @return unmodifiable collection of objects that were shallow commits on * the client. */ @@ -295,6 +376,8 @@ } /** + * Get unmodifiable list of the cached packs that were reused in the output + * * @return unmodifiable list of the cached packs that were reused in the * output, if any were selected for reuse. */ @@ -302,12 +385,19 @@ return statistics.reusedPacks; } - /** @return unmodifiable collection of the root commits of the history. */ + /** + * Get unmodifiable collection of the root commits of the history. + * + * @return unmodifiable collection of the root commits of the history. + */ public Set getRootCommits() { return statistics.rootCommits; } /** + * Get number of objects in the output pack that went through the delta + * search process in order to find a potential delta base. + * * @return number of objects in the output pack that went through the delta * search process in order to find a potential delta base. */ @@ -316,6 +406,9 @@ } /** + * Get number of objects in the output pack that went through delta base + * search and found a suitable base. + * * @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()}. @@ -325,6 +418,8 @@ } /** + * Get total number of objects output. + * * @return total number of objects output. This total includes the value of * {@link #getTotalDeltas()}. */ @@ -333,6 +428,9 @@ } /** + * Get the count of objects that needed to be discovered through an object + * walk because they were not found in bitmap indices. + * * @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. @@ -342,6 +440,8 @@ } /** + * Get total number of deltas output. + * * @return total number of deltas output. This may be lower than the actual * number of deltas if a cached pack was reused. */ @@ -350,6 +450,9 @@ } /** + * Get number of objects whose existing representation was reused in the + * output. + * * @return number of objects whose existing representation was reused in the * output. This count includes {@link #getReusedDeltas()}. */ @@ -358,6 +461,9 @@ } /** + * Get number of deltas whose existing representation was reused in the + * output. + * * @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 @@ -368,6 +474,8 @@ } /** + * Get total number of bytes written. + * * @return total number of bytes written. This size includes the pack * header, trailer, thin pack, and reused cached pack(s). */ @@ -376,6 +484,8 @@ } /** + * Get size of the thin pack in bytes. + * * @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 @@ -387,6 +497,8 @@ } /** + * Get information about this type of object in the pack. + * * @param typeCode * object type code, e.g. OBJ_COMMIT or OBJ_TREE. * @return information about this type of object in the pack. @@ -395,17 +507,28 @@ return new ObjectType(statistics.objectTypes[typeCode]); } - /** @return true if the resulting pack file was a shallow pack. */ + /** + * Whether the resulting pack file was a shallow pack. + * + * @return {@code 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. */ + /** + * Get depth (in commits) the pack includes if shallow. + * + * @return depth (in commits) the pack includes if shallow. + */ public int getDepth() { return statistics.depth; } /** + * Get time in milliseconds spent enumerating the objects that need to be + * included in the output. + * * @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. @@ -415,6 +538,9 @@ } /** + * Get time in milliseconds spent matching existing representations against + * objects that will be transmitted. + * * @return time in milliseconds spent matching existing representations * against objects that will be transmitted, or that the client can * be assumed to already have. @@ -424,6 +550,9 @@ } /** + * Get time in milliseconds spent finding the sizes of all objects that will + * enter the delta compression search window. + * * @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 @@ -434,6 +563,8 @@ } /** + * Get time in milliseconds spent on delta compression. + * * @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. @@ -443,6 +574,9 @@ } /** + * Get time in milliseconds spent writing the pack output, from start of + * header until end of trailer. + * * @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. @@ -451,7 +585,11 @@ return statistics.timeWriting; } - /** @return total time spent processing this pack. */ + /** + * Get total time spent processing this pack. + * + * @return total time spent processing this pack. + */ public long getTimeTotal() { return statistics.timeCounting + statistics.timeSearchingForReuse + statistics.timeSearchingForSizes + statistics.timeCompressing @@ -459,14 +597,20 @@ } /** - * @return get the average output speed in terms of bytes-per-second. + * Get the average output speed in terms of bytes-per-second. + * + * @return 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. */ + /** + * Get formatted message string for display to clients. + * + * @return formatted message string for display to clients. + */ public String getMessage() { return MessageFormat.format(JGitText.get().packWriterStatistics, Long.valueOf(statistics.totalObjects), @@ -475,7 +619,11 @@ Long.valueOf(statistics.reusedDeltas)); } - /** @return a map containing ObjectType statistics. */ + /** + * Get a map containing ObjectType statistics. + * + * @return a map containing ObjectType statistics. + */ public Map getObjectTypes() { HashMap map = new HashMap<>(); map.put(Integer.valueOf(OBJ_BLOB), new ObjectType( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017, Two Sigma Open Source + * 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.submodule; + +import org.eclipse.jgit.diff.Sequence; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Merges expect that conflicts will consist of Sequences, but that doesn't + * really make sense for submodules. So this represents a submodule conflict. + * + * @since 4.11 + */ +public class SubmoduleConflict extends Sequence { + private final ObjectId objectId; + + /** + * Create a SubmoduleConflict for the given submodule object id + * @param objectId + */ + public SubmoduleConflict(ObjectId objectId) { + super(); + this.objectId = objectId; + } + + @Override + public int size() { + return 1; + } + + /** + * @return the object id for the conflicting submodule + */ + public ObjectId getObjectId() { + return objectId; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,8 +61,12 @@ * Create submodule status * * @param type + * a {@link org.eclipse.jgit.submodule.SubmoduleStatusType} + * object. * @param path + * submodule path * @param indexId + * an {@link org.eclipse.jgit.lib.ObjectId} object. */ public SubmoduleStatus(final SubmoduleStatusType type, final String path, final ObjectId indexId) { @@ -73,9 +77,14 @@ * Create submodule status * * @param type + * a {@link org.eclipse.jgit.submodule.SubmoduleStatusType} + * object. * @param path + * submodule path * @param indexId + * index id * @param headId + * head id */ public SubmoduleStatus(final SubmoduleStatusType type, final String path, final ObjectId indexId, final ObjectId headId) { @@ -86,6 +95,8 @@ } /** + * Get type + * * @return type */ public SubmoduleStatusType getType() { @@ -93,13 +104,17 @@ } /** - * @return path + * Get submodule path + * + * @return path submodule path */ public String getPath() { return path; } /** + * Get index object id + * * @return index object id */ public ObjectId getIndexId() { @@ -107,6 +122,8 @@ } /** + * Get HEAD object id + * * @return HEAD object id */ public ObjectId getHeadId() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,8 @@ import java.io.File; import java.io.IOException; import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheIterator; @@ -79,7 +81,7 @@ public class SubmoduleWalk implements AutoCloseable { /** - * The values for the config param submodule..ignore + * The values for the config parameter submodule.<name>.ignore * * @since 3.6 */ @@ -112,8 +114,9 @@ * The {@code .gitmodules} file is read from the index. * * @param repository + * a {@link org.eclipse.jgit.lib.Repository} object. * @return generator over submodule index entries - * @throws IOException + * @throws java.io.IOException */ public static SubmoduleWalk forIndex(Repository repository) throws IOException { @@ -133,12 +136,14 @@ * path * * @param repository + * a {@link org.eclipse.jgit.lib.Repository} object. * @param treeId - * the root of a tree containing both a submodule at the given path - * and .gitmodules at the root. + * the root of a tree containing both a submodule at the given + * path and .gitmodules at the root. * @param path + * a {@link java.lang.String} object. * @return generator at given path, null if no submodule at given path - * @throws IOException + * @throws java.io.IOException */ public static SubmoduleWalk forPath(Repository repository, AnyObjectId treeId, String path) throws IOException { @@ -164,12 +169,14 @@ * path * * @param repository + * a {@link org.eclipse.jgit.lib.Repository} object. * @param iterator - * the root of a tree containing both a submodule at the given path - * and .gitmodules at the root. + * the root of a tree containing both a submodule at the given + * path and .gitmodules at the root. * @param path + * a {@link java.lang.String} object. * @return generator at given path, null if no submodule at given path - * @throws IOException + * @throws java.io.IOException */ public static SubmoduleWalk forPath(Repository repository, AbstractTreeIterator iterator, String path) throws IOException { @@ -194,7 +201,9 @@ * Get submodule directory * * @param parent + * the {@link org.eclipse.jgit.lib.Repository}. * @param path + * submodule path * @return directory */ public static File getSubmoduleDirectory(final Repository parent, @@ -206,25 +215,47 @@ * Get submodule repository * * @param parent + * the {@link org.eclipse.jgit.lib.Repository}. * @param path + * submodule path * @return repository or null if repository doesn't exist - * @throws IOException + * @throws java.io.IOException */ public static Repository getSubmoduleRepository(final Repository parent, final String path) throws IOException { - return getSubmoduleRepository(parent.getWorkTree(), path); + return getSubmoduleRepository(parent.getWorkTree(), path, + parent.getFS()); } /** * Get submodule repository at path * * @param parent + * the parent * @param path + * submodule path * @return repository or null if repository doesn't exist - * @throws IOException + * @throws java.io.IOException */ public static Repository getSubmoduleRepository(final File parent, final String path) throws IOException { + return getSubmoduleRepository(parent, path, FS.DETECTED); + } + + /** + * Get submodule repository at path, using the specified file system + * abstraction + * + * @param parent + * @param path + * @param fs + * the file system abstraction to be used + * @return repository or null if repository doesn't exist + * @throws IOException + * @since 4.10 + */ + public static Repository getSubmoduleRepository(final File parent, + final String path, FS fs) throws IOException { File subWorkTree = new File(parent, path); if (!subWorkTree.isDirectory()) return null; @@ -232,7 +263,7 @@ try { return new RepositoryBuilder() // .setMustExist(true) // - .setFS(FS.DETECTED) // + .setFS(fs) // .setWorkTree(workTree) // .build(); } catch (RepositoryNotFoundException e) { @@ -255,7 +286,7 @@ * @param url * absolute or relative URL of the submodule repository * @return resolved URL - * @throws IOException + * @throws java.io.IOException */ public static String getSubmoduleRemoteUrl(final Repository parent, final String url) throws IOException { @@ -264,7 +295,7 @@ String remoteName = null; // Look up remote URL associated wit HEAD ref - Ref ref = parent.getRef(Constants.HEAD); + Ref ref = parent.exactRef(Constants.HEAD); if (ref != null) { if (ref.isSymbolic()) ref = ref.getLeaf(); @@ -329,11 +360,14 @@ private String path; + private Map pathToName; + /** * Create submodule generator * * @param repository - * @throws IOException + * the {@link org.eclipse.jgit.lib.Repository}. + * @throws java.io.IOException */ public SubmoduleWalk(final Repository repository) throws IOException { this.repository = repository; @@ -354,6 +388,7 @@ */ public SubmoduleWalk setModulesConfig(final Config config) { modulesConfig = config; + loadPathNames(); return this; } @@ -373,6 +408,7 @@ public SubmoduleWalk setRootTree(final AbstractTreeIterator tree) { rootTree = tree; modulesConfig = null; + pathToName = null; return this; } @@ -388,13 +424,14 @@ * @param id * ID of a tree containing .gitmodules * @return this generator - * @throws IOException + * @throws java.io.IOException */ public SubmoduleWalk setRootTree(final AnyObjectId id) throws IOException { final CanonicalTreeParser p = new CanonicalTreeParser(); p.reset(walk.getObjectReader(), id); rootTree = p; modulesConfig = null; + pathToName = null; return this; } @@ -407,8 +444,9 @@ * If no submodule config is found, loads an empty config. * * @return this generator - * @throws IOException if an error occurred, or if the repository is bare - * @throws ConfigInvalidException + * @throws java.io.IOException + * if an error occurred, or if the repository is bare + * @throws org.eclipse.jgit.errors.ConfigInvalidException */ public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidException { if (rootTree == null) { @@ -418,6 +456,7 @@ repository.getFS()); config.load(); modulesConfig = config; + loadPathNames(); } else { try (TreeWalk configWalk = new TreeWalk(repository)) { configWalk.addTree(rootTree); @@ -437,10 +476,12 @@ if (filter.isDone(configWalk)) { modulesConfig = new BlobBasedConfig(null, repository, configWalk.getObjectId(0)); + loadPathNames(); return this; } } modulesConfig = new Config(); + pathToName = null; } finally { if (idx > 0) rootTree.next(idx); @@ -450,6 +491,20 @@ return this; } + private void loadPathNames() { + pathToName = null; + if (modulesConfig != null) { + HashMap pathNames = new HashMap<>(); + for (String name : modulesConfig + .getSubsections(ConfigConstants.CONFIG_SUBMODULE_SECTION)) { + pathNames.put(modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, name, + ConfigConstants.CONFIG_KEY_PATH), name); + } + pathToName = pathNames; + } + } + /** * Checks whether the working tree contains a .gitmodules file. That's a * hint that the repo contains submodules. @@ -459,8 +514,8 @@ * @return true if the working tree contains a .gitmodules file, * false otherwise. Always returns false * for bare repositories. - * @throws IOException - * @throws CorruptObjectException + * @throws java.io.IOException + * @throws CorruptObjectException if any. * @since 3.6 */ public static boolean containsGitModulesFile(Repository repository) @@ -474,14 +529,21 @@ } private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException { - if (modulesConfig == null) + if (modulesConfig == null) { loadModulesConfig(); + } + } + + private String getModuleName(String modulePath) { + String name = pathToName != null ? pathToName.get(modulePath) : null; + return name != null ? name : modulePath; } /** * Set tree filter * * @param filter + * a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object. * @return this generator */ public SubmoduleWalk setFilter(TreeFilter filter) { @@ -493,8 +555,10 @@ * Set the tree iterator used for finding submodule entries * * @param iterator + * an {@link org.eclipse.jgit.treewalk.AbstractTreeIterator} + * object. * @return this generator - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException */ public SubmoduleWalk setTree(final AbstractTreeIterator iterator) throws CorruptObjectException { @@ -506,10 +570,13 @@ * Set the tree used for finding submodule entries * * @param treeId + * an {@link org.eclipse.jgit.lib.AnyObjectId} object. * @return this generator - * @throws IOException + * @throws java.io.IOException * @throws IncorrectObjectTypeException + * if any. * @throws MissingObjectException + * if any. */ public SubmoduleWalk setTree(final AnyObjectId treeId) throws IOException { walk.addTree(treeId); @@ -524,6 +591,7 @@ public SubmoduleWalk reset() { repoConfig = repository.getConfig(); modulesConfig = null; + pathToName = null; walk.reset(); return this; } @@ -544,7 +612,7 @@ * {@link #getObjectId()} and {@link #getPath()}. * * @return true if entry found, false otherwise - * @throws IOException + * @throws java.io.IOException */ public boolean next() throws IOException { while (walk.next()) { @@ -567,6 +635,15 @@ } /** + * The module name for the current submodule entry (used for the section name of .git/config) + * @since 4.10 + * @return name + */ + public String getModuleName() { + return getModuleName(path); + } + + /** * Get object id of current submodule entry * * @return object id @@ -580,14 +657,13 @@ * the .gitmodules file in the current repository's working tree. * * @return configured path - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException */ public String getModulesPath() throws IOException, ConfigInvalidException { lazyLoadModulesConfig(); - return modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_PATH); + return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + getModuleName(), ConfigConstants.CONFIG_KEY_PATH); } /** @@ -595,12 +671,12 @@ * from the repository's config. * * @return configured URL - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException */ public String getConfigUrl() throws IOException, ConfigInvalidException { return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, - path, ConfigConstants.CONFIG_KEY_URL); + getModuleName(), ConfigConstants.CONFIG_KEY_URL); } /** @@ -608,14 +684,13 @@ * from the .gitmodules file in the current repository's working tree. * * @return configured URL - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException */ public String getModulesUrl() throws IOException, ConfigInvalidException { lazyLoadModulesConfig(); - return modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_URL); + return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + getModuleName(), ConfigConstants.CONFIG_KEY_URL); } /** @@ -623,12 +698,12 @@ * from the repository's config. * * @return update value - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException */ public String getConfigUpdate() throws IOException, ConfigInvalidException { return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, - path, ConfigConstants.CONFIG_KEY_UPDATE); + getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE); } /** @@ -636,14 +711,13 @@ * from the .gitmodules file in the current repository's working tree. * * @return update value - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException */ public String getModulesUpdate() throws IOException, ConfigInvalidException { lazyLoadModulesConfig(); - return modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_UPDATE); + return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE); } /** @@ -651,26 +725,23 @@ * value from the .gitmodules file in the current repository's working tree. * * @return ignore value - * @throws ConfigInvalidException - * @throws IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException + * @throws java.io.IOException * @since 3.6 */ public IgnoreSubmoduleMode getModulesIgnore() throws IOException, ConfigInvalidException { lazyLoadModulesConfig(); - String name = modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path, - ConfigConstants.CONFIG_KEY_IGNORE); - if (name == null) - return null; - return IgnoreSubmoduleMode.valueOf(name.trim().toUpperCase()); + return modulesConfig.getEnum(IgnoreSubmoduleMode.values(), + ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(), + ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE); } /** * Get repository for current submodule entry * * @return repository or null if non-existent - * @throws IOException + * @throws java.io.IOException */ public Repository getRepository() throws IOException { return getSubmoduleRepository(repository, path); @@ -680,7 +751,7 @@ * Get commit id that HEAD points to in the current submodule's repository * * @return object id of HEAD reference - * @throws IOException + * @throws java.io.IOException */ public ObjectId getHead() throws IOException { Repository subRepo = getRepository(); @@ -697,14 +768,14 @@ * Get ref that HEAD points to in the current submodule's repository * * @return ref name, null on failures - * @throws IOException + * @throws java.io.IOException */ public String getHeadRef() throws IOException { Repository subRepo = getRepository(); if (subRepo == null) return null; try { - Ref head = subRepo.getRef(Constants.HEAD); + Ref head = subRepo.exactRef(Constants.HEAD); return head != null ? head.getLeaf().getName() : null; } finally { subRepo.close(); @@ -718,8 +789,8 @@ * URL * * @return resolved remote URL - * @throws IOException - * @throws ConfigInvalidException + * @throws java.io.IOException + * @throws org.eclipse.jgit.errors.ConfigInvalidException */ public String getRemoteUrl() throws IOException, ConfigInvalidException { String url = getModulesUrl(); @@ -727,6 +798,8 @@ } /** + * {@inheritDoc} + *

      * Release any resources used by this walker's reader. * * @since 4.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,18 +51,22 @@ import org.eclipse.jgit.revwalk.RevWalk; /** - * Implementation of {@link AdvertiseRefsHook} that advertises the same refs for + * Implementation of {@link org.eclipse.jgit.transport.AdvertiseRefsHook} that advertises the same refs for * upload-pack and receive-pack. * * @since 2.0 */ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook { + /** {@inheritDoc} */ + @Override public void advertiseRefs(UploadPack uploadPack) throws ServiceMayNotContinueException { uploadPack.setAdvertisedRefs(getAdvertisedRefs( uploadPack.getRepository(), uploadPack.getRevWalk())); } + /** {@inheritDoc} */ + @Override public void advertiseRefs(BaseReceivePack receivePack) throws ServiceMayNotContinueException { Map refs = getAdvertisedRefs(receivePack.getRepository(), @@ -80,7 +84,7 @@ * @param revWalk * open rev walk on the repository. * @return set of refs to advertise. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ protected abstract Map getAdvertisedRefs( @@ -95,8 +99,8 @@ * @param revWalk * open rev walk on the repository. * @return set of additional haves; see - * {@link ReceivePack#getAdvertisedObjects()}. - * @throws ServiceMayNotContinueException + * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedObjects()}. + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ protected Set getAdvertisedHaves( diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,13 +46,14 @@ import java.util.List; /** - * {@link AdvertiseRefsHook} that delegates to a list of other hooks. + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} that delegates to a list + * of other hooks. *

      * Hooks are run in the order passed to the constructor. A hook may inspect or * modify the results of the previous hooks in the chain by calling - * {@link UploadPack#getAdvertisedRefs()}, or - * {@link BaseReceivePack#getAdvertisedRefs()} or - * {@link BaseReceivePack#getAdvertisedObjects()}. + * {@link org.eclipse.jgit.transport.UploadPack#getAdvertisedRefs()}, or + * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedRefs()} or + * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedObjects()}. */ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { private final AdvertiseRefsHook[] hooks; @@ -79,12 +80,16 @@ return new AdvertiseRefsHookChain(newHooks, i); } + /** {@inheritDoc} */ + @Override public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].advertiseRefs(rp); } + /** {@inheritDoc} */ + @Override public void advertiseRefs(UploadPack rp) throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,10 +56,12 @@ * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. */ public static final AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { + @Override public void advertiseRefs(UploadPack uploadPack) { // Do nothing. } + @Override public void advertiseRefs(BaseReceivePack receivePack) { // Do nothing. } @@ -68,10 +70,11 @@ /** * Advertise refs for upload-pack. * - * @param uploadPack instance on which to call - * {@link UploadPack#setAdvertisedRefs(java.util.Map)} + * @param uploadPack + * instance on which to call + * {@link org.eclipse.jgit.transport.UploadPack#setAdvertisedRefs(java.util.Map)} * if necessary. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void advertiseRefs(UploadPack uploadPack) @@ -80,10 +83,11 @@ /** * Advertise refs for receive-pack. * - * @param receivePack instance on which to call - * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} + * @param receivePack + * instance on which to call + * {@link org.eclipse.jgit.transport.BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} * if necessary. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void advertiseRefs(BaseReceivePack receivePack) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.transport; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -56,10 +58,10 @@ import java.net.URL; import java.net.URLConnection; import java.security.DigestOutputStream; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -120,7 +122,7 @@ private static final String X_AMZ_META = "x-amz-meta-"; //$NON-NLS-1$ static { - SIGNED_HEADERS = new HashSet(); + SIGNED_HEADERS = new HashSet<>(); SIGNED_HEADERS.add("content-type"); //$NON-NLS-1$ SIGNED_HEADERS.add("content-md5"); //$NON-NLS-1$ SIGNED_HEADERS.add("date"); //$NON-NLS-1$ @@ -175,7 +177,7 @@ private final String acl; /** Maximum number of times to try an operation. */ - private final int maxAttempts; + final int maxAttempts; /** Encryption algorithm, may be a null instance that provides pass-through. */ private final WalkEncryption encryption; @@ -186,6 +188,19 @@ /** S3 Bucket Domain. */ private final String domain; + /** Property names used in amazon connection configuration file. */ + interface Keys { + String ACCESS_KEY = "accesskey"; //$NON-NLS-1$ + String SECRET_KEY = "secretkey"; //$NON-NLS-1$ + String PASSWORD = "password"; //$NON-NLS-1$ + String CRYPTO_ALG = "crypto.algorithm"; //$NON-NLS-1$ + String CRYPTO_VER = "crypto.version"; //$NON-NLS-1$ + String ACL = "acl"; //$NON-NLS-1$ + String DOMAIN = "domain"; //$NON-NLS-1$ + String HTTP_RETRY = "httpclient.retry-max"; //$NON-NLS-1$ + String TMP_DIR = "tmpdir"; //$NON-NLS-1$ + } + /** * Create a new S3 client for the supplied user information. *

      @@ -216,20 +231,20 @@ * * @param props * connection properties. - * */ public AmazonS3(final Properties props) { - domain = props.getProperty("domain", "s3.amazonaws.com"); //$NON-NLS-1$ //$NON-NLS-2$ - publicKey = props.getProperty("accesskey"); //$NON-NLS-1$ + domain = props.getProperty(Keys.DOMAIN, "s3.amazonaws.com"); //$NON-NLS-1$ + + publicKey = props.getProperty(Keys.ACCESS_KEY); if (publicKey == null) throw new IllegalArgumentException(JGitText.get().missingAccesskey); - final String secret = props.getProperty("secretkey"); //$NON-NLS-1$ + final String secret = props.getProperty(Keys.SECRET_KEY); if (secret == null) throw new IllegalArgumentException(JGitText.get().missingSecretkey); privateKey = new SecretKeySpec(Constants.encodeASCII(secret), HMAC); - final String pacl = props.getProperty("acl", "PRIVATE"); //$NON-NLS-1$ //$NON-NLS-2$ + final String pacl = props.getProperty(Keys.ACL, "PRIVATE"); //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase("PRIVATE", pacl)) //$NON-NLS-1$ acl = "private"; //$NON-NLS-1$ else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl)) //$NON-NLS-1$ @@ -242,26 +257,16 @@ throw new IllegalArgumentException("Invalid acl: " + pacl); //$NON-NLS-1$ try { - final String cPas = props.getProperty("password"); //$NON-NLS-1$ - if (cPas != null) { - String cAlg = props.getProperty("crypto.algorithm"); //$NON-NLS-1$ - if (cAlg == null) - cAlg = "PBEWithMD5AndDES"; //$NON-NLS-1$ - encryption = new WalkEncryption.ObjectEncryptionV2(cAlg, cPas); - } else { - encryption = WalkEncryption.NONE; - } - } catch (InvalidKeySpecException e) { - throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); - } catch (NoSuchAlgorithmException e) { + encryption = WalkEncryption.instance(props); + } catch (GeneralSecurityException e) { throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); } - maxAttempts = Integer.parseInt(props.getProperty( - "httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$ + maxAttempts = Integer + .parseInt(props.getProperty(Keys.HTTP_RETRY, "3")); //$NON-NLS-1$ proxySelector = ProxySelector.getDefault(); - String tmp = props.getProperty("tmpdir"); //$NON-NLS-1$ + String tmp = props.getProperty(Keys.TMP_DIR); tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null; } @@ -275,7 +280,7 @@ * @return connection to stream the content of the object. The request * properties of the connection may not be modified by the caller as * the request parameters have already been signed. - * @throws IOException + * @throws java.io.IOException * sending the request was not possible. */ public URLConnection get(final String bucket, final String key) @@ -304,7 +309,7 @@ * @param u * connection previously created by {@link #get(String, String)}}. * @return stream to read plain text from. - * @throws IOException + * @throws java.io.IOException * decryption could not be configured. */ public InputStream decrypt(final URLConnection u) throws IOException { @@ -327,7 +332,7 @@ * @return list of keys starting with prefix, after removing * prefix (or prefix + "/")from all * of them. - * @throws IOException + * @throws java.io.IOException * sending the request was not possible, or the response XML * document could not be parsed properly. */ @@ -351,7 +356,7 @@ * name of the bucket storing the object. * @param key * key of the object within its bucket. - * @throws IOException + * @throws java.io.IOException * deletion failed due to communications error. */ public void delete(final String bucket, final String key) @@ -388,7 +393,7 @@ * @param data * new data content for the object. Must not be null. Zero length * array will create a zero length object. - * @throws IOException + * @throws java.io.IOException * creation/updating failed due to communications error. */ public void put(final String bucket, final String key, final byte[] data) @@ -397,9 +402,9 @@ // We have to copy to produce the cipher text anyway so use // the large object code path as it supports that behavior. // - final OutputStream os = beginPut(bucket, key, null, null); - os.write(data); - os.close(); + try (OutputStream os = beginPut(bucket, key, null, null)) { + os.write(data); + } return; } @@ -413,11 +418,8 @@ authorize(c); c.setDoOutput(true); c.setFixedLengthStreamingMode(data.length); - final OutputStream os = c.getOutputStream(); - try { + try (OutputStream os = c.getOutputStream()) { os.write(data); - } finally { - os.close(); } switch (HttpSupport.response(c)) { @@ -457,7 +459,7 @@ * @param monitorTask * (optional) task name to display during the close method. * @return a stream which accepts the new data, and transmits once closed. - * @throws IOException + * @throws java.io.IOException * if encryption was enabled it could not be configured. */ public OutputStream beginPut(final String bucket, final String key, @@ -479,7 +481,7 @@ return encryption.encrypt(new DigestOutputStream(buffer, md5)); } - private void putImpl(final String bucket, final String key, + void putImpl(final String bucket, final String key, final byte[] csum, final TemporaryBuffer buf, ProgressMonitor monitor, String monitorTask) throws IOException { if (monitor == null) @@ -498,12 +500,10 @@ authorize(c); c.setDoOutput(true); monitor.beginTask(monitorTask, (int) (len / 1024)); - final OutputStream os = c.getOutputStream(); - try { + try (OutputStream os = c.getOutputStream()) { buf.writeTo(os, monitor); } finally { monitor.endTask(); - os.close(); } switch (HttpSupport.response(c)) { @@ -518,32 +518,40 @@ throw maxAttempts(JGitText.get().s3ActionWriting, key); } - private IOException error(final String action, final String key, + IOException error(final String action, final String key, final HttpURLConnection c) throws IOException { final IOException err = new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailed, action, key, Integer.valueOf(HttpSupport.response(c)), c.getResponseMessage())); final InputStream errorStream = c.getErrorStream(); - if (errorStream == null) + if (errorStream == null) { return err; + } - final ByteArrayOutputStream b = new ByteArrayOutputStream(); - byte[] buf = new byte[2048]; - for (;;) { - final int n = errorStream.read(buf); - if (n < 0) - break; - if (n > 0) - b.write(buf, 0, n); - } - buf = b.toByteArray(); - if (buf.length > 0) - err.initCause(new IOException("\n" + new String(buf))); //$NON-NLS-1$ + try { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + byte[] buf = new byte[2048]; + for (;;) { + final int n = errorStream.read(buf); + if (n < 0) { + break; + } + if (n > 0) { + b.write(buf, 0, n); + } + } + buf = b.toByteArray(); + if (buf.length > 0) { + err.initCause(new IOException("\n" + new String(buf))); //$NON-NLS-1$ + } + } finally { + errorStream.close(); + } return err; } - private IOException maxAttempts(final String action, final String key) { + IOException maxAttempts(final String action, final String key) { return new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailedGivingUp, action, key, Integer.valueOf(maxAttempts))); @@ -555,7 +563,7 @@ return open(method, bucket, key, noArgs); } - private HttpURLConnection open(final String method, final String bucket, + HttpURLConnection open(final String method, final String bucket, final String key, final Map args) throws IOException { final StringBuilder urlstr = new StringBuilder(); @@ -592,9 +600,9 @@ return c; } - private void authorize(final HttpURLConnection c) throws IOException { + void authorize(final HttpURLConnection c) throws IOException { final Map> reqHdr = c.getRequestProperties(); - final SortedMap sigHdr = new TreeMap(); + final SortedMap sigHdr = new TreeMap<>(); for (final Map.Entry> entry : reqHdr.entrySet()) { final String hdr = entry.getKey(); if (isSignedHeader(hdr)) @@ -630,7 +638,7 @@ try { final Mac m = Mac.getInstance(HMAC); m.init(privateKey); - sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes("UTF-8"))); //$NON-NLS-1$ + sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes(UTF_8))); } catch (NoSuchAlgorithmException e) { throw new IOException(MessageFormat.format(JGitText.get().noHMACsupport, HMAC, e.getMessage())); } catch (InvalidKeyException e) { @@ -642,17 +650,14 @@ static Properties properties(final File authFile) throws FileNotFoundException, IOException { final Properties p = new Properties(); - final FileInputStream in = new FileInputStream(authFile); - try { + try (FileInputStream in = new FileInputStream(authFile)) { p.load(in); - } finally { - in.close(); } return p; } private final class ListParser extends DefaultHandler { - final List entries = new ArrayList(); + final List entries = new ArrayList<>(); private final String bucket; @@ -668,7 +673,7 @@ } void list() throws IOException { - final Map args = new TreeMap(); + final Map args = new TreeMap<>(); if (prefix.length() > 0) args.put("prefix", prefix); //$NON-NLS-1$ if (!entries.isEmpty()) @@ -689,16 +694,13 @@ throw new IOException(JGitText.get().noXMLParserAvailable); } xr.setContentHandler(this); - final InputStream in = c.getInputStream(); - try { + try (InputStream in = c.getInputStream()) { xr.parse(new InputSource(in)); } catch (SAXException parsingError) { - final IOException p; - p = new IOException(MessageFormat.format(JGitText.get().errorListing, prefix)); - p.initCause(parsingError); - throw p; - } finally { - in.close(); + throw new IOException( + MessageFormat.format( + JGitText.get().errorListing, prefix), + parsingError); } return; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,29 +71,37 @@ private Writer messageWriter; + /** {@inheritDoc} */ + @Override public Map getRefsMap() { return advertisedRefs; } + /** {@inheritDoc} */ + @Override public final Collection getRefs() { return advertisedRefs.values(); } + /** {@inheritDoc} */ + @Override public final Ref getRef(final String name) { return advertisedRefs.get(name); } + /** {@inheritDoc} */ + @Override public String getMessages() { return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$ } /** - * User agent advertised by the remote server. + * {@inheritDoc} * - * @return agent (version of Git) running on the remote server. Null if the - * server does not advertise this version. + * User agent advertised by the remote server. * @since 4.0 */ + @Override public String getPeerUserAgent() { return peerUserAgent; } @@ -109,6 +117,8 @@ peerUserAgent = agent; } + /** {@inheritDoc} */ + @Override public abstract void close(); /** @@ -130,7 +140,7 @@ * Helper method for ensuring one-operation per connection. Check whether * operation was already marked as started, and mark it as started. * - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * if operation was already marked as started. */ protected void markStartedOperation() throws TransportException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,12 +64,16 @@ */ abstract class BaseFetchConnection extends BaseConnection implements FetchConnection { + /** {@inheritDoc} */ + @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { fetch(monitor, want, have, null); } + /** {@inheritDoc} */ + @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream out) throws TransportException { @@ -78,9 +82,12 @@ } /** + * {@inheritDoc} + * * Default implementation of {@link FetchConnection#didFetchIncludeTags()} - * returning false. */ + @Override public boolean didFetchIncludeTags() { return false; } @@ -95,7 +102,7 @@ * as in {@link #fetch(ProgressMonitor, Collection, Set)} * @param have * as in {@link #fetch(ProgressMonitor, Collection, Set)} - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * as in {@link #fetch(ProgressMonitor, Collection, Set)}, but * implementation doesn't have to care about multiple * {@link #fetch(ProgressMonitor, Collection, Set)} calls, as it diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -117,10 +117,10 @@ protected boolean statelessRPC; /** Capability tokens advertised by the remote side. */ - private final Set remoteCapablities = new HashSet(); + private final Set remoteCapablities = new HashSet<>(); /** Extra objects the remote has, but which aren't offered as refs. */ - protected final Set additionalHaves = new HashSet(); + protected final Set additionalHaves = new HashSet<>(); BasePackConnection(final PackTransport packTransport) { transport = (Transport) packTransport; @@ -143,7 +143,9 @@ final int timeout = transport.getTimeout(); if (timeout > 0) { final Thread caller = Thread.currentThread(); - myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ + if (myTimer == null) { + myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ + } timeoutIn = new TimeoutInputStream(myIn, myTimer); timeoutOut = new TimeoutOutputStream(myOut, myTimer); timeoutIn.setTimeout(timeout * 1000); @@ -168,9 +170,9 @@ *

      * If any errors occur, this connection is automatically closed by invoking * {@link #close()} and the exception is wrapped (if necessary) and thrown - * as a {@link TransportException}. + * as a {@link org.eclipse.jgit.errors.TransportException}. * - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the reference list could not be scanned. */ protected void readAdvertisedRefs() throws TransportException { @@ -189,7 +191,7 @@ } private void readAdvertisedRefsImpl() throws IOException { - final LinkedHashMap avail = new LinkedHashMap(); + final LinkedHashMap avail = new LinkedHashMap<>(); for (;;) { String line; @@ -265,10 +267,26 @@ return new NoRemoteRepositoryException(uri, JGitText.get().notFound); } + /** + * Whether this option is supported + * + * @param option + * option string + * @return whether this option is supported + */ protected boolean isCapableOf(final String option) { return remoteCapablities.contains(option); } + /** + * Request capability + * + * @param b + * buffer + * @param option + * option we want + * @return {@code true} if the requested option is supported + */ protected boolean wantCapability(final StringBuilder b, final String option) { if (!isCapableOf(option)) return false; @@ -277,6 +295,12 @@ return true; } + /** + * Add user agent capability + * + * @param b + * a {@link java.lang.StringBuilder} object. + */ protected void addUserAgentCapability(StringBuilder b) { String a = UserAgent.get(); if (a != null && UserAgent.hasAgent(remoteCapablities)) { @@ -284,6 +308,7 @@ } } + /** {@inheritDoc} */ @Override public String getPeerUserAgent() { return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent()); @@ -293,6 +318,7 @@ return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); } + /** {@inheritDoc} */ @Override public void close() { if (out != null) { @@ -332,7 +358,9 @@ } } - /** Tell the peer we are disconnecting, if it cares to know. */ + /** + * Tell the peer we are disconnecting, if it cares to know. + */ protected void endOut() { if (outNeedsEnd && out != null) { try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,6 +54,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -63,7 +64,6 @@ import org.eclipse.jgit.internal.storage.file.PackLock; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -71,7 +71,6 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommitList; import org.eclipse.jgit.revwalk.RevFlag; @@ -80,6 +79,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck; import org.eclipse.jgit.transport.PacketLineIn.AckNackResult; import org.eclipse.jgit.util.TemporaryBuffer; @@ -95,10 +95,10 @@ * easily wrapped up into a local process pipe, anonymous TCP socket, or a * command executed through an SSH tunnel. *

      - * If {@link BasePackConnection#statelessRPC} is {@code true}, this connection - * can be tunneled over a request-response style RPC system like HTTP. The RPC - * call boundary is determined by this class switching from writing to the - * OutputStream to reading from the InputStream. + * If {@link org.eclipse.jgit.transport.BasePackConnection#statelessRPC} is + * {@code true}, this connection can be tunneled over a request-response style + * RPC system like HTTP. The RPC call boundary is determined by this class + * switching from writing to the OutputStream to reading from the InputStream. *

      * Concrete implementations should just call * {@link #init(java.io.InputStream, java.io.OutputStream)} and @@ -231,6 +231,8 @@ private boolean noProgress; + private Set minimalNegotiationSet; + private String lockMessage; private PackLock packLock; @@ -250,8 +252,11 @@ super(packTransport); if (local != null) { - final FetchConfig cfg = local.getConfig().get(FetchConfig.KEY); + final FetchConfig cfg = getFetchConfig(); allowOfsDelta = cfg.allowOfsDelta; + if (cfg.minimalNegotiation) { + minimalNegotiationSet = new HashSet<>(); + } } else { allowOfsDelta = true; } @@ -260,7 +265,7 @@ if (local != null) { walk = new RevWalk(local); - reachableCommits = new RevCommitList(); + reachableCommits = new RevCommitList<>(); REACHABLE = walk.newFlag("REACHABLE"); //$NON-NLS-1$ COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$ STATE = walk.newFlag("STATE"); //$NON-NLS-1$ @@ -278,29 +283,33 @@ } } - private static class FetchConfig { - static final SectionParser KEY = new SectionParser() { - public FetchConfig parse(final Config cfg) { - return new FetchConfig(cfg); - } - }; - + static class FetchConfig { final boolean allowOfsDelta; + final boolean minimalNegotiation; + FetchConfig(final Config c) { allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true); //$NON-NLS-1$ //$NON-NLS-2$ + minimalNegotiation = c.getBoolean("fetch", "useminimalnegotiation", //$NON-NLS-1$ //$NON-NLS-2$ + false); + } + + FetchConfig(boolean allowOfsDelta, boolean minimalNegotiation) { + this.allowOfsDelta = allowOfsDelta; + this.minimalNegotiation = minimalNegotiation; } } + /** {@inheritDoc} */ + @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { fetch(monitor, want, have, null); } - /** - * @since 3.0 - */ + /** {@inheritDoc} */ + @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream outputStream) throws TransportException { @@ -308,18 +317,26 @@ doFetch(monitor, want, have, outputStream); } + /** {@inheritDoc} */ + @Override public boolean didFetchIncludeTags() { return false; } + /** {@inheritDoc} */ + @Override public boolean didFetchTestConnectivity() { return false; } + /** {@inheritDoc} */ + @Override public void setPackLockMessage(final String message) { lockMessage = message; } + /** {@inheritDoc} */ + @Override public Collection getPackLocks() { if (packLock != null) return Collections.singleton(packLock); @@ -331,7 +348,7 @@ * * @param monitor * progress monitor to receive status updates. If the monitor is - * the {@link NullProgressMonitor#INSTANCE}, then the no-progress + * the {@link org.eclipse.jgit.lib.NullProgressMonitor#INSTANCE}, then the no-progress * option enabled. * @param want * the advertised remote references the caller wants to fetch. @@ -341,7 +358,7 @@ * destination repository's references. * @param outputStream * ouputStream to write sideband messages to - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * if any exception occurs. * @since 3.0 */ @@ -381,6 +398,7 @@ } } + /** {@inheritDoc} */ @Override public void close() { if (walk != null) @@ -388,6 +406,10 @@ super.close(); } + FetchConfig getFetchConfig() { + return local.getConfig().get(FetchConfig::new); + } + private int maxTimeWanted(final Collection wants) { int maxTime = 0; for (final Ref r : wants) { @@ -464,8 +486,12 @@ final PacketLineOut p = statelessRPC ? pckState : pckOut; boolean first = true; for (final Ref r : want) { + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + continue; + } try { - if (walk.parseAny(r.getObjectId()).has(REACHABLE)) { + if (walk.parseAny(objectId).has(REACHABLE)) { // We already have this object. Asking for it is // not a very good idea. // @@ -478,16 +504,26 @@ final StringBuilder line = new StringBuilder(46); line.append("want "); //$NON-NLS-1$ - line.append(r.getObjectId().name()); + line.append(objectId.name()); if (first) { line.append(enableCapabilities()); first = false; } line.append('\n'); p.writeString(line.toString()); + if (minimalNegotiationSet != null) { + Ref current = local.exactRef(r.getName()); + if (current != null) { + ObjectId o = current.getObjectId(); + if (o != null && !o.equals(ObjectId.zeroId())) { + minimalNegotiationSet.add(o); + } + } + } } - if (first) + if (first) { return false; + } p.end(); outNeedsEnd = false; return true; @@ -542,18 +578,24 @@ boolean receivedAck = false; boolean receivedReady = false; - if (statelessRPC) + if (statelessRPC) { state.writeTo(out, null); + } negotiateBegin(); SEND_HAVES: for (;;) { final RevCommit c = walk.next(); - if (c == null) + if (c == null) { break SEND_HAVES; + } - pckOut.writeString("have " + c.getId().name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + ObjectId o = c.getId(); + pckOut.writeString("have " + o.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ havesSent++; havesSinceLastContinue++; + if (minimalNegotiationSet != null) { + minimalNegotiationSet.remove(o); + } if ((31 & havesSent) != 0) { // We group the have lines into blocks of 32, each marked @@ -563,8 +605,9 @@ continue; } - if (monitor.isCancelled()) + if (monitor.isCancelled()) { throw new CancelledException(); + } pckOut.end(); resultsPending++; // Each end will cause a result to come back. @@ -586,6 +629,16 @@ // pack on the remote side. Keep doing that. // resultsPending--; + if (minimalNegotiationSet != null + && minimalNegotiationSet.isEmpty()) { + // Minimal negotiation was requested and we sent out our + // current reference values for our wants, so terminate + // negotiation early. + if (statelessRPC) { + state.writeTo(out, null); + } + break SEND_HAVES; + } break READ_RESULT; case ACK: @@ -596,8 +649,9 @@ multiAck = MultiAck.OFF; resultsPending = 0; receivedAck = true; - if (statelessRPC) + if (statelessRPC) { state.writeTo(out, null); + } break SEND_HAVES; case ACK_CONTINUE: @@ -612,19 +666,31 @@ receivedAck = true; receivedContinue = true; havesSinceLastContinue = 0; - if (anr == AckNackResult.ACK_READY) + if (anr == AckNackResult.ACK_READY) { receivedReady = true; + } + if (minimalNegotiationSet != null && minimalNegotiationSet.isEmpty()) { + // Minimal negotiation was requested and we sent out our current reference + // values for our wants, so terminate negotiation early. + if (statelessRPC) { + state.writeTo(out, null); + } + break SEND_HAVES; + } break; } - if (monitor.isCancelled()) + if (monitor.isCancelled()) { throw new CancelledException(); + } } - if (noDone & receivedReady) + if (noDone & receivedReady) { break SEND_HAVES; - if (statelessRPC) + } + if (statelessRPC) { state.writeTo(out, null); + } if (receivedContinue && havesSinceLastContinue > MAX_HAVES) { // Our history must be really different from the remote's. @@ -638,8 +704,9 @@ // Tell the remote side we have run out of things to talk about. // - if (monitor.isCancelled()) + if (monitor.isCancelled()) { throw new CancelledException(); + } if (!receivedReady || !noDone) { // When statelessRPC is true we should always leave SEND_HAVES @@ -684,8 +751,9 @@ break; } - if (monitor.isCancelled()) + if (monitor.isCancelled()) { throw new CancelledException(); + } } } @@ -773,7 +841,7 @@ /** * Notification event delivered just before the pack is received from the - * network. This event can be used by RPC such as {@link TransportHttp} to + * network. This event can be used by RPC such as {@link org.eclipse.jgit.transport.TransportHttp} to * disable its request magic and ensure the pack stream is read correctly. * * @since 2.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,17 +44,22 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; + import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; @@ -76,7 +81,8 @@ * easily wrapped up into a local process pipe, anonymous TCP socket, or a * command executed through an SSH tunnel. *

      - * This implementation honors {@link Transport#isPushThin()} option. + * This implementation honors + * {@link org.eclipse.jgit.transport.Transport#isPushThin()} option. *

      * Concrete implementations should just call * {@link #init(java.io.InputStream, java.io.OutputStream)} and @@ -109,18 +115,26 @@ */ public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; + /** + * The server supports the receiving of push options. + * @since 4.5 + */ + public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; + private final boolean thinPack; + private final boolean atomic; - private boolean capableDeleteRefs; + /** A list of option strings associated with this push. */ + private List pushOptions; + private boolean capableAtomic; + private boolean capableDeleteRefs; private boolean capableReport; - private boolean capableSideBand; - private boolean capableOfsDelta; + private boolean capablePushOptions; private boolean sentCommand; - private boolean writePack; /** Time in milliseconds spent transferring the pack data. */ @@ -135,17 +149,20 @@ public BasePackPushConnection(final PackTransport packTransport) { super(packTransport); thinPack = transport.isPushThin(); + atomic = transport.isPushAtomic(); + pushOptions = transport.getPushOptions(); } + /** {@inheritDoc} */ + @Override public void push(final ProgressMonitor monitor, final Map refUpdates) throws TransportException { push(monitor, refUpdates, null); } - /** - * @since 3.0 - */ + /** {@inheritDoc} */ + @Override public void push(final ProgressMonitor monitor, final Map refUpdates, OutputStream outputStream) throws TransportException { @@ -153,6 +170,7 @@ doPush(monitor, refUpdates, outputStream); } + /** {@inheritDoc} */ @Override protected TransportException noRepository() { // Sadly we cannot tell the "invalid URI" case from "push not allowed". @@ -185,7 +203,7 @@ * update commands to be applied to the remote repository. * @param outputStream * output stream to write sideband messages to - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * if any exception occurs. * @since 3.0 */ @@ -194,6 +212,9 @@ OutputStream outputStream) throws TransportException { try { writeCommands(refUpdates.values(), monitor, outputStream); + + if (pushOptions != null && capablePushOptions) + transmitOptions(); if (writePack) writePack(refUpdates, monitor); if (sentCommand) { @@ -224,6 +245,17 @@ private void writeCommands(final Collection refUpdates, final ProgressMonitor monitor, OutputStream outputStream) throws IOException { final String capabilities = enableCapabilities(monitor, outputStream); + if (atomic && !capableAtomic) { + throw new TransportException(uri, + JGitText.get().atomicPushNotSupported); + } + + if (pushOptions != null && !capablePushOptions) { + throw new TransportException(uri, + MessageFormat.format(JGitText.get().pushOptionsNotSupported, + pushOptions.toString())); + } + for (final RemoteRefUpdate rru : refUpdates) { if (!capableDeleteRefs && rru.isDelete()) { rru.setStatus(Status.REJECTED_NODELETE); @@ -231,9 +263,14 @@ } final StringBuilder sb = new StringBuilder(); - final Ref advertisedRef = getRef(rru.getRemoteName()); - final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId() - : advertisedRef.getObjectId()); + ObjectId oldId = rru.getExpectedOldObjectId(); + if (oldId == null) { + final Ref advertised = getRef(rru.getRemoteName()); + oldId = advertised != null ? advertised.getObjectId() : null; + if (oldId == null) { + oldId = ObjectId.zeroId(); + } + } sb.append(oldId.name()); sb.append(' '); sb.append(rru.getNewObjectId().name()); @@ -256,13 +293,27 @@ outNeedsEnd = false; } + private void transmitOptions() throws IOException { + for (final String pushOption : pushOptions) { + pckOut.writeString(pushOption); + } + + pckOut.end(); + } + private String enableCapabilities(final ProgressMonitor monitor, OutputStream outputStream) { final StringBuilder line = new StringBuilder(); + if (atomic) + capableAtomic = wantCapability(line, CAPABILITY_ATOMIC); capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS); capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS); capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA); + if (pushOptions != null) { + capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS); + } + capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K); if (capableSideBand) { in = new SideBandInputStream(in, monitor, getMessageWriter(), @@ -278,8 +329,8 @@ private void writePack(final Map refUpdates, final ProgressMonitor monitor) throws IOException { - Set remoteObjects = new HashSet(); - Set newObjects = new HashSet(); + Set remoteObjects = new HashSet<>(); + Set newObjects = new HashSet<>(); try (final PackWriter writer = new PackWriter(transport.getPackConfig(), local.newObjectReader())) { @@ -303,7 +354,12 @@ writer.setReuseValidatingObjects(false); writer.setDeltaBaseAsOffset(capableOfsDelta); writer.preparePack(monitor, newObjects, remoteObjects); - writer.writePack(monitor, monitor, out); + + OutputStream packOut = out; + if (capableSideBand) { + packOut = new CheckingSideBandOutputStream(in, out); + } + writer.writePack(monitor, monitor, packOut); packTransferTime = writer.getStatistics().getTimeWriting(); } @@ -313,14 +369,19 @@ throws IOException { final String unpackLine = readStringLongTimeout(); if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$ - throw new PackProtocolException(uri, MessageFormat.format(JGitText.get().unexpectedReportLine, unpackLine)); + throw new PackProtocolException(uri, MessageFormat + .format(JGitText.get().unexpectedReportLine, unpackLine)); final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$ - if (unpackStatus.startsWith("error Pack exceeds the limit of")) //$NON-NLS-1$ + if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$ throw new TooLargePackException(uri, unpackStatus.substring("error ".length())); //$NON-NLS-1$ - if (!unpackStatus.equals("ok")) //$NON-NLS-1$ + } else if (unpackStatus.startsWith("error Object too large")) {//$NON-NLS-1$ + throw new TooLargeObjectInPackException(uri, + unpackStatus.substring("error ".length())); //$NON-NLS-1$ + } else if (!unpackStatus.equals("ok")) { //$NON-NLS-1$ throw new TransportException(uri, MessageFormat.format( JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus)); + } String refLine; while ((refLine = pckIn.readString()) != PacketLineIn.END) { @@ -372,10 +433,65 @@ final int oldTimeout = timeoutIn.getTimeout(); final int sendTime = (int) Math.min(packTransferTime, 28800000L); try { - timeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout)); + int timeout = 10 * Math.max(sendTime, oldTimeout); + timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout); return pckIn.readString(); } finally { timeoutIn.setTimeout(oldTimeout); } } + + /** + * Gets the list of option strings associated with this push. + * + * @return pushOptions + * @since 4.5 + */ + public List getPushOptions() { + return pushOptions; + } + + private static class CheckingSideBandOutputStream extends OutputStream { + private final InputStream in; + private final OutputStream out; + + CheckingSideBandOutputStream(InputStream in, OutputStream out) { + this.in = in; + this.out = out; + } + + @Override + public void write(int b) throws IOException { + write(new byte[] { (byte) b }); + } + + @Override + public void write(byte[] buf, int ptr, int cnt) throws IOException { + try { + out.write(buf, ptr, cnt); + } catch (IOException e) { + throw checkError(e); + } + } + + @Override + public void flush() throws IOException { + try { + out.flush(); + } catch (IOException e) { + throw checkError(e); + } + } + + private IOException checkError(IOException e1) { + try { + in.read(); + } catch (TransportException e2) { + return e2; + } catch (IOException e2) { + return e1; + } + return e1; + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,14 +43,17 @@ package org.eclipse.jgit.transport; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; +import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; @@ -67,20 +70,26 @@ import java.util.Set; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.PackLock; +import org.eclipse.jgit.internal.submodule.SubmoduleValidator; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GitmoduleEntry; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; @@ -93,6 +102,7 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.PacketLineIn.InputOverLimitIOException; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.LimitedInputStream; @@ -119,7 +129,7 @@ * line from the client. */ public FirstLine(String line) { - final HashSet caps = new HashSet(); + final HashSet caps = new HashSet<>(); final int nul = line.indexOf('\0'); if (nul >= 0) { for (String c : line.substring(nul + 1).split(" ")) //$NON-NLS-1$ @@ -176,6 +186,15 @@ /** Should an incoming transfer permit non-fast-forward requests? */ private boolean allowNonFastForwards; + /** Should an incoming transfer permit push options? **/ + private boolean allowPushOptions; + + /** + * Should the requested ref updates be performed as a single atomic + * transaction? + */ + private boolean atomic; + private boolean allowOfsDelta; private boolean allowQuiet = true; @@ -208,6 +227,7 @@ /** Optional message output stream. */ protected OutputStream msgOut; + private SideBandOutputStream errOut; /** Packet line input stream around {@link #rawIn}. */ protected PacketLineIn pckIn; @@ -230,6 +250,8 @@ String userAgent; private Set clientShallowCommits; private List commands; + private long maxCommandBytes; + private long maxDiscardBytes; private StringBuilder advertiseError; @@ -255,6 +277,7 @@ private PushCertificateParser pushCertificateParser; private SignedPushConfig signedPushConfig; private PushCertificate pushCert; + private ReceivedPackStatistics stats; /** * Get the push certificate used to verify the pusher's identity. @@ -293,72 +316,53 @@ db = into; walk = new RevWalk(db); - final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY); - objectChecker = cfg.newObjectChecker(); - allowCreates = cfg.allowCreates; + TransferConfig tc = db.getConfig().get(TransferConfig.KEY); + objectChecker = tc.newReceiveObjectChecker(); + + ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new); + allowCreates = rc.allowCreates; allowAnyDeletes = true; - allowBranchDeletes = cfg.allowDeletes; - allowNonFastForwards = cfg.allowNonFastForwards; - allowOfsDelta = cfg.allowOfsDelta; + allowBranchDeletes = rc.allowDeletes; + allowNonFastForwards = rc.allowNonFastForwards; + allowOfsDelta = rc.allowOfsDelta; + allowPushOptions = rc.allowPushOptions; + maxCommandBytes = rc.maxCommandBytes; + maxDiscardBytes = rc.maxDiscardBytes; advertiseRefsHook = AdvertiseRefsHook.DEFAULT; refFilter = RefFilter.DEFAULT; - advertisedHaves = new HashSet(); - clientShallowCommits = new HashSet(); - signedPushConfig = cfg.signedPush; + advertisedHaves = new HashSet<>(); + clientShallowCommits = new HashSet<>(); + signedPushConfig = rc.signedPush; } /** Configuration for receive operations. */ protected static class ReceiveConfig { - static final SectionParser KEY = new SectionParser() { - public ReceiveConfig parse(final Config cfg) { - return new ReceiveConfig(cfg); - } - }; - - final boolean checkReceivedObjects; - final boolean allowLeadingZeroFileMode; - final boolean allowInvalidPersonIdent; - final boolean safeForWindows; - final boolean safeForMacOS; - final boolean allowCreates; final boolean allowDeletes; final boolean allowNonFastForwards; final boolean allowOfsDelta; - + final boolean allowPushOptions; + final long maxCommandBytes; + final long maxDiscardBytes; final SignedPushConfig signedPush; ReceiveConfig(final Config config) { - checkReceivedObjects = config.getBoolean( - "receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ - config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ - allowLeadingZeroFileMode = checkReceivedObjects - && config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowInvalidPersonIdent = checkReceivedObjects - && config.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForWindows = checkReceivedObjects - && config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForMacOS = checkReceivedObjects - && config.getBoolean("fsck", "safeForMacOS", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowCreates = true; allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ "denynonfastforwards", false); //$NON-NLS-1$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ true); + allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$ + false); + maxCommandBytes = config.getLong("receive", //$NON-NLS-1$ + "maxCommandBytes", //$NON-NLS-1$ + 3 << 20); + maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$ + "maxCommandDiscardBytes", //$NON-NLS-1$ + -1); signedPush = SignedPushConfig.KEY.parse(config); } - - ObjectChecker newObjectChecker() { - if (!checkReceivedObjects) - return null; - return new ObjectChecker() - .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) - .setAllowInvalidPersonIdent(allowInvalidPersonIdent) - .setSafeForWindows(safeForWindows) - .setSafeForMacOS(safeForMacOS); - } } /** @@ -407,15 +411,27 @@ } } - /** @return the process name used for pack lock messages. */ + /** + * Get the process name used for pack lock messages. + * + * @return the process name used for pack lock messages. + */ protected abstract String getLockMessageProcessName(); - /** @return the repository this receive completes into. */ + /** + * Get the repository this receive completes into. + * + * @return the repository this receive completes into. + */ public final Repository getRepository() { return db; } - /** @return the RevWalk instance used by this connection. */ + /** + * Get the RevWalk instance used by this connection. + * + * @return the RevWalk instance used by this connection. + */ public final RevWalk getRevWalk() { return walk; } @@ -433,14 +449,15 @@ /** * Set the refs advertised by this ReceivePack. *

      - * Intended to be called from a {@link PreReceiveHook}. + * Intended to be called from a + * {@link org.eclipse.jgit.transport.PreReceiveHook}. * * @param allRefs * explicit set of references to claim as advertised by this - * ReceivePack instance. This overrides any references that - * may exist in the source repository. The map is passed - * to the configured {@link #getRefFilter()}. If null, assumes - * all refs were advertised. + * ReceivePack instance. This overrides any references that may + * exist in the source repository. The map is passed to the + * configured {@link #getRefFilter()}. If null, assumes all refs + * were advertised. * @param additionalHaves * explicit set of additional haves to claim as advertised. If * null, assumes the default set of additional haves from the @@ -449,6 +466,7 @@ public void setAdvertisedRefs(Map allRefs, Set additionalHaves) { refs = allRefs != null ? allRefs : db.getAllRefs(); refs = refFilter.filter(refs); + advertisedHaves.clear(); Ref head = refs.get(Constants.HEAD); if (head != null && head.isSymbolic()) @@ -476,6 +494,9 @@ } /** + * Whether this instance will validate all referenced, but not supplied by + * the client, objects are reachable from another reference. + * * @return true if this instance will validate all referenced, but not * supplied by the client, objects are reachable from another * reference. @@ -494,8 +515,9 @@ * This feature is useful when the application doesn't trust the client to * not provide a forged SHA-1 reference to an object, in an attempt to * access parts of the DAG that they aren't allowed to see and which have - * been hidden from them via the configured {@link AdvertiseRefsHook} or - * {@link RefFilter}. + * been hidden from them via the configured + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} or + * {@link org.eclipse.jgit.transport.RefFilter}. *

      * Enabling this feature may imply at least some, if not all, of the same * functionality performed by {@link #setCheckReceivedObjects(boolean)}. @@ -509,6 +531,9 @@ } /** + * Whether this class expects a bi-directional pipe opened between the + * client and itself. + * * @return true if this class expects a bi-directional pipe opened between * the client and itself. The default is true. */ @@ -517,6 +542,10 @@ } /** + * Whether this class will assume the socket is a fully bidirectional pipe + * between the two peers and takes advantage of that by first transmitting + * the known refs, then waiting to read commands. + * * @param twoWay * if true, this class will assume the socket is a fully * bidirectional pipe between the two peers and takes advantage @@ -529,32 +558,44 @@ biDirectionalPipe = twoWay; } - /** @return true if there is data expected after the pack footer. */ + /** + * Whether there is data expected after the pack footer. + * + * @return {@code true} if there is data expected after the pack footer. + */ public boolean isExpectDataAfterPackFooter() { return expectDataAfterPackFooter; } /** + * Whether there is additional data in InputStream after pack. + * * @param e - * true if there is additional data in InputStream after pack. + * {@code true} if there is additional data in InputStream after + * pack. */ public void setExpectDataAfterPackFooter(boolean e) { expectDataAfterPackFooter = e; } /** - * @return true if this instance will verify received objects are formatted - * correctly. Validating objects requires more CPU time on this side - * of the connection. + * Whether this instance will verify received objects are formatted + * correctly. + * + * @return {@code true} if this instance will verify received objects are + * formatted correctly. Validating objects requires more CPU time on + * this side of the connection. */ public boolean isCheckReceivedObjects() { return objectChecker != null; } /** + * Whether to enable checking received objects + * * @param check - * true to enable checking received objects; false to assume all - * received objects are valid. + * {@code true} to enable checking received objects; false to + * assume all received objects are valid. * @see #setObjectChecker(ObjectChecker) */ public void setCheckReceivedObjects(final boolean check) { @@ -565,42 +606,59 @@ } /** - * @param impl if non-null the object checking instance to verify each - * received object with; null to disable object checking. + * Set the object checking instance to verify each received object with + * + * @param impl + * if non-null the object checking instance to verify each + * received object with; null to disable object checking. * @since 3.4 */ public void setObjectChecker(ObjectChecker impl) { objectChecker = impl; } - /** @return true if the client can request refs to be created. */ + /** + * Whether the client can request refs to be created. + * + * @return {@code true} if the client can request refs to be created. + */ public boolean isAllowCreates() { return allowCreates; } /** + * Whether to permit create ref commands to be processed. + * * @param canCreate - * true to permit create ref commands to be processed. + * {@code true} to permit create ref commands to be processed. */ public void setAllowCreates(final boolean canCreate) { allowCreates = canCreate; } - /** @return true if the client can request refs to be deleted. */ + /** + * Whether the client can request refs to be deleted. + * + * @return {@code true} if the client can request refs to be deleted. + */ public boolean isAllowDeletes() { return allowAnyDeletes; } /** + * Whether to permit delete ref commands to be processed. + * * @param canDelete - * true to permit delete ref commands to be processed. + * {@code true} to permit delete ref commands to be processed. */ public void setAllowDeletes(final boolean canDelete) { allowAnyDeletes = canDelete; } /** - * @return true if the client can delete from {@code refs/heads/}. + * Whether the client can delete from {@code refs/heads/}. + * + * @return {@code true} if the client can delete from {@code refs/heads/}. * @since 3.6 */ public boolean isAllowBranchDeletes() { @@ -608,8 +666,11 @@ } /** + * Configure whether to permit deletion of branches from the + * {@code refs/heads/} namespace. + * * @param canDelete - * true to permit deletion of branches from the + * {@code true} to permit deletion of branches from the * {@code refs/heads/} namespace. * @since 3.6 */ @@ -618,23 +679,58 @@ } /** - * @return true if the client can request non-fast-forward updates of a ref, - * possibly making objects unreachable. + * Whether the client can request non-fast-forward updates of a ref, + * possibly making objects unreachable. + * + * @return {@code true} if the client can request non-fast-forward updates + * of a ref, possibly making objects unreachable. */ public boolean isAllowNonFastForwards() { return allowNonFastForwards; } /** + * Configure whether to permit the client to ask for non-fast-forward + * updates of an existing ref. + * * @param canRewind - * true to permit the client to ask for non-fast-forward updates - * of an existing ref. + * {@code true} to permit the client to ask for non-fast-forward + * updates of an existing ref. */ public void setAllowNonFastForwards(final boolean canRewind) { allowNonFastForwards = canRewind; } - /** @return identity of the user making the changes in the reflog. */ + /** + * Whether the client's commands should be performed as a single atomic + * transaction. + * + * @return {@code true} if the client's commands should be performed as a + * single atomic transaction. + * @since 4.4 + */ + public boolean isAtomic() { + return atomic; + } + + /** + * Configure whether to perform the client's commands as a single atomic + * transaction. + * + * @param atomic + * {@code true} to perform the client's commands as a single + * atomic transaction. + * @since 4.4 + */ + public void setAtomic(boolean atomic) { + this.atomic = atomic; + } + + /** + * Get identity of the user making the changes in the reflog. + * + * @return identity of the user making the changes in the reflog. + */ public PersonIdent getRefLogIdent() { return refLogIdent; } @@ -655,12 +751,20 @@ refLogIdent = pi; } - /** @return the hook used while advertising the refs to the client */ + /** + * Get the hook used while advertising the refs to the client + * + * @return the hook used while advertising the refs to the client + */ public AdvertiseRefsHook getAdvertiseRefsHook() { return advertiseRefsHook; } - /** @return the filter used while advertising the refs to the client */ + /** + * Get the filter used while advertising the refs to the client + * + * @return the filter used while advertising the refs to the client + */ public RefFilter getRefFilter() { return refFilter; } @@ -668,12 +772,14 @@ /** * Set the hook used while advertising the refs to the client. *

      - * If the {@link AdvertiseRefsHook} chooses to call - * {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook - * and selected by the {@link RefFilter} will be shown to the client. - * Clients may still attempt to create or update a reference not advertised by - * the configured {@link AdvertiseRefsHook}. These attempts should be rejected - * by a matching {@link PreReceiveHook}. + * If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to + * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook + * and selected by the {@link org.eclipse.jgit.transport.RefFilter} + * will be shown to the client. Clients may still attempt to create or + * update a reference not advertised by the configured + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts + * should be rejected by a matching + * {@link org.eclipse.jgit.transport.PreReceiveHook}. * * @param advertiseRefsHook * the hook; may be null to show all refs. @@ -688,9 +794,9 @@ /** * Set the filter used while advertising the refs to the client. *

      - * Only refs allowed by this filter will be shown to the client. - * The filter is run against the refs specified by the - * {@link AdvertiseRefsHook} (if applicable). + * Only refs allowed by this filter will be shown to the client. The filter + * is run against the refs specified by the + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). * * @param refFilter * the filter; may be null to show all refs. @@ -699,7 +805,11 @@ this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } - /** @return timeout (in seconds) before aborting an IO operation. */ + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @return timeout (in seconds) before aborting an IO operation. + */ public int getTimeout() { return timeout; } @@ -717,6 +827,38 @@ } /** + * Set the maximum number of command bytes to read from the client. + * + * @param limit + * command limit in bytes; if 0 there is no limit. + * @since 4.7 + */ + public void setMaxCommandBytes(long limit) { + maxCommandBytes = limit; + } + + /** + * Set the maximum number of command bytes to discard from the client. + *

      + * Discarding remaining bytes allows this instance to consume the rest of + * the command block and send a human readable over-limit error via the + * side-band channel. If the client sends an excessive number of bytes this + * limit kicks in and the instance disconnects, resulting in a non-specific + * 'pipe closed', 'end of stream', or similar generic error at the client. + *

      + * When the limit is set to {@code -1} the implementation will default to + * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}. + * + * @param limit + * discard limit in bytes; if 0 there is no limit; if -1 the + * implementation tries to set a reasonable default. + * @since 4.7 + */ + public void setMaxCommandDiscardBytes(long limit) { + maxDiscardBytes = limit; + } + + /** * Set the maximum allowed Git object size. *

      * If an object is larger than the given size the pack-parsing will throw an @@ -729,7 +871,6 @@ maxObjectSizeLimit = limit; } - /** * Set the maximum allowed pack size. *

      @@ -737,7 +878,6 @@ * * @param limit * the pack size limit, in bytes - * * @since 3.3 */ public void setMaxPackSizeLimit(final long limit) { @@ -752,19 +892,20 @@ * * @return true if the client has advertised a side-band capability, false * otherwise. - * @throws RequestNotYetReadException + * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, so * we do not know if they expect side-band. Note that the client * may have already written the request, it just has not been * read. */ public boolean isSideBand() throws RequestNotYetReadException { - if (enabledCapabilities == null) - throw new RequestNotYetReadException(); + checkRequestWasRead(); return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); } /** + * Whether clients may request avoiding noisy progress messages. + * * @return true if clients may request avoiding noisy progress messages. * @since 4.0 */ @@ -787,10 +928,31 @@ } /** + * Whether the server supports receiving push options. + * + * @return true if the server supports receiving push options. + * @since 4.5 + */ + public boolean isAllowPushOptions() { + return allowPushOptions; + } + + /** + * Configure if the server supports receiving push options. + * + * @param allow + * true to optionally accept option strings from the client. + * @since 4.5 + */ + public void setAllowPushOptions(boolean allow) { + allowPushOptions = allow; + } + + /** * True if the client wants less verbose output. * * @return true if the client has requested the server to be less verbose. - * @throws RequestNotYetReadException + * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, * so we do not know if they expect side-band. Note that the * client may have already written the request, it just has not @@ -798,8 +960,7 @@ * @since 4.0 */ public boolean isQuiet() throws RequestNotYetReadException { - if (enabledCapabilities == null) - throw new RequestNotYetReadException(); + checkRequestWasRead(); return quiet; } @@ -842,7 +1003,11 @@ return UserAgent.getAgent(enabledCapabilities, userAgent); } - /** @return all of the command received by the current request. */ + /** + * Get all of the command received by the current request. + * + * @return all of the command received by the current request. + */ public List getAllCommands() { return Collections.unmodifiableList(commands); } @@ -860,11 +1025,13 @@ * message will be discarded, with no other indication to the caller or to * the client. *

      - * {@link PreReceiveHook}s should always try to use - * {@link ReceiveCommand#setResult(Result, String)} with a result status of - * {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for - * rejecting an update. Messages attached to a command are much more likely - * to be returned to the client. + * {@link org.eclipse.jgit.transport.PreReceiveHook}s should always try to + * use + * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)} + * with a result status of + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON} + * to indicate any reasons for rejecting an update. Messages attached to a + * command are much more likely to be returned to the client. * * @param what * string describing the problem identified by the hook. The @@ -880,6 +1047,19 @@ } } + private void fatalError(String msg) { + if (errOut != null) { + try { + errOut.write(Constants.encode(msg)); + errOut.flush(); + } catch (IOException e) { + // Ignore write failures + } + } else { + sendError(msg); + } + } + /** * Send a message to the client, if it supports receiving them. *

      @@ -894,7 +1074,11 @@ msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$ } - /** @return an underlying stream for sending messages to the client. */ + /** + * Get an underlying stream for sending messages to the client. + * + * @return an underlying stream for sending messages to the client. + */ public OutputStream getMessageOutputStream() { return msgOutWrapper; } @@ -905,7 +1089,7 @@ * This can only be called if the pack is already received. * * @return the size of the received pack including index size - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if called before the pack has been received * @since 3.3 */ @@ -927,12 +1111,20 @@ return clientShallowCommits; } - /** @return true if any commands to be executed have been read. */ + /** + * Whether any commands to be executed have been read. + * + * @return {@code true} if any commands to be executed have been read. + */ protected boolean hasCommands() { return !commands.isEmpty(); } - /** @return true if an error occurred that should be advertised. */ + /** + * Whether an error occurred that should be advertised. + * + * @return true if an error occurred that should be advertised. + */ protected boolean hasError() { return advertiseError != null; } @@ -972,23 +1164,19 @@ rawOut = o; } - if (maxPackSizeLimit >= 0) - rawIn = new LimitedInputStream(rawIn, maxPackSizeLimit) { - @Override - protected void limitExceeded() throws TooLargePackException { - throw new TooLargePackException(limit); - } - }; - pckIn = new PacketLineIn(rawIn); pckOut = new PacketLineOut(rawOut); pckOut.setFlushOnEnd(false); - enabledCapabilities = new HashSet(); - commands = new ArrayList(); + enabledCapabilities = new HashSet<>(); + commands = new ArrayList<>(); } - /** @return advertised refs, or the default if not explicitly advertised. */ + /** + * Get advertised refs, or the default if not explicitly advertised. + * + * @return advertised refs, or the default if not explicitly advertised. + */ protected Map getAdvertisedOrDefaultRefs() { if (refs == null) setAdvertisedRefs(null, null); @@ -998,20 +1186,22 @@ /** * Receive a pack from the stream and check connectivity if necessary. * - * @throws IOException + * @throws java.io.IOException * an error occurred during unpacking or connectivity checking. */ protected void receivePackAndCheckConnectivity() throws IOException { receivePack(); - if (needCheckConnectivity()) + if (needCheckConnectivity()) { + checkSubmodules(); checkConnectivity(); + } parser = null; } /** * Unlock the pack written by this object. * - * @throws IOException + * @throws java.io.IOException * the pack could not be unlocked. */ protected void unlockPack() throws IOException { @@ -1026,9 +1216,9 @@ * * @param adv * the advertisement formatter. - * @throws IOException + * @throws java.io.IOException * the formatter failed to write an advertisement. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * the hook denied advertisement. */ public void sendAdvertisedRefs(final RefAdvertiser adv) @@ -1062,6 +1252,9 @@ adv.advertiseCapability(CAPABILITY_ATOMIC); if (allowOfsDelta) adv.advertiseCapability(CAPABILITY_OFS_DELTA); + if (allowPushOptions) { + adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS); + } adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.send(getAdvertisedOrDefaultRefs()); for (ObjectId obj : advertisedHaves) @@ -1072,18 +1265,33 @@ } /** + * Returns the statistics on the received pack if available. This should be + * called after {@link #receivePack} is called. + * + * @return ReceivedPackStatistics + * @since 4.6 + */ + @Nullable + public ReceivedPackStatistics getReceivedPackStatistics() { + return stats; + } + + /** * Receive a list of commands from the input. * - * @throws IOException + * @throws java.io.IOException */ protected void recvCommands() throws IOException { + PacketLineIn pck = maxCommandBytes > 0 + ? new PacketLineIn(rawIn, maxCommandBytes) + : pckIn; PushCertificateParser certParser = getPushCertificateParser(); - FirstLine firstLine = null; + boolean firstPkt = true; try { for (;;) { String line; try { - line = pckIn.readString(); + line = pck.readString(); } catch (EOFException eof) { if (commands.isEmpty()) return; @@ -1094,33 +1302,29 @@ } if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ - clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48))); + parseShallow(line.substring(8, 48)); continue; } - if (firstLine == null) { - firstLine = new FirstLine(line); + if (firstPkt) { + firstPkt = false; + FirstLine firstLine = new FirstLine(line); enabledCapabilities = firstLine.getCapabilities(); line = firstLine.getLine(); + enableCapabilities(); if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) { - certParser.receiveHeader(pckIn, !isBiDirectionalPipe()); + certParser.receiveHeader(pck, !isBiDirectionalPipe()); continue; } } if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) { - certParser.receiveSignature(pckIn); + certParser.receiveSignature(pck); continue; } - ReceiveCommand cmd; - try { - cmd = parseCommand(line); - } catch (PackProtocolException e) { - sendError(e.getMessage()); - throw e; - } + ReceiveCommand cmd = parseCommand(line); if (cmd.getRefName().equals(Constants.HEAD)) { cmd.setResult(Result.REJECTED_CURRENT_BRANCH); } else { @@ -1132,10 +1336,43 @@ } } pushCert = certParser.build(); + if (hasCommands()) { + readPostCommands(pck); + } } catch (PackProtocolException e) { - sendError(e.getMessage()); + discardCommands(); + fatalError(e.getMessage()); throw e; + } catch (InputOverLimitIOException e) { + String msg = JGitText.get().tooManyCommands; + discardCommands(); + fatalError(msg); + throw new PackProtocolException(msg); + } + } + + private void discardCommands() { + if (sideBand) { + long max = maxDiscardBytes; + if (max < 0) { + max = Math.max(3 * maxCommandBytes, 3L << 20); + } + try { + new PacketLineIn(rawIn, max).discardUntilEnd(); + } catch (IOException e) { + // Ignore read failures attempting to discard. + } + } + } + + private void parseShallow(String idStr) throws PackProtocolException { + ObjectId id; + try { + id = ObjectId.fromString(idStr); + } catch (InvalidObjectIdException e) { + throw new PackProtocolException(e.getMessage(), e); } + clientShallowCommits.add(id); } static ReceiveCommand parseCommand(String line) throws PackProtocolException { @@ -1149,7 +1386,7 @@ try { oldId = ObjectId.fromString(oldStr); newId = ObjectId.fromString(newStr); - } catch (IllegalArgumentException e) { + } catch (InvalidObjectIdException e) { throw new PackProtocolException( JGitText.get().errorInvalidProtocolWantedOldNewRef, e); } @@ -1161,7 +1398,19 @@ return new ReceiveCommand(oldId, newId, name); } - /** Enable capabilities based on a previously read capabilities line. */ + /** + * @param in + * request stream. + * @throws IOException + * request line cannot be read. + */ + void readPostCommands(PacketLineIn in) throws IOException { + // Do nothing by default. + } + + /** + * Enable capabilities based on a previously read capabilities line. + */ protected void enableCapabilities() { sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K); quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET); @@ -1170,6 +1419,7 @@ rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); + errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out); pckOut = new PacketLineOut(rawOut); pckOut.setFlushOnEnd(false); @@ -1187,7 +1437,16 @@ return enabledCapabilities.contains(name); } - /** @return true if a pack is expected based on the list of commands. */ + void checkRequestWasRead() { + if (enabledCapabilities == null) + throw new RequestNotYetReadException(); + } + + /** + * Whether a pack is expected based on the list of commands. + * + * @return {@code true} if a pack is expected based on the list of commands. + */ protected boolean needPack() { for (final ReceiveCommand cmd : commands) { if (cmd.getType() != ReceiveCommand.Type.DELETE) @@ -1220,7 +1479,7 @@ if (getRefLogIdent() != null) lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$ - parser = ins.newPackParser(rawIn); + parser = ins.newPackParser(packInputStream()); parser.setAllowThin(true); parser.setNeedNewObjectIds(checkReferencedIsReachable); parser.setNeedBaseObjectIds(checkReferencedIsReachable); @@ -1232,6 +1491,7 @@ parser.setMaxObjectSizeLimit(maxObjectSizeLimit); packLock = parser.parse(receiving, resolving); packSize = Long.valueOf(parser.getPackSize()); + stats = parser.getReceivedPackStatistics(); ins.flush(); } @@ -1239,12 +1499,40 @@ timeoutIn.setTimeout(timeout * 1000); } + private InputStream packInputStream() { + InputStream packIn = rawIn; + if (maxPackSizeLimit >= 0) { + packIn = new LimitedInputStream(packIn, maxPackSizeLimit) { + @Override + protected void limitExceeded() throws TooLargePackException { + throw new TooLargePackException(limit); + } + }; + } + return packIn; + } + private boolean needCheckConnectivity() { return isCheckReceivedObjects() || isCheckReferencedObjectsAreReachable() || !getClientShallowCommits().isEmpty(); } + private void checkSubmodules() + throws IOException { + ObjectDatabase odb = db.getObjectDatabase(); + if (objectChecker == null) { + return; + } + for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) { + AnyObjectId blobId = entry.getBlobId(); + ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB); + + SubmoduleValidator.assertValidGitModulesFile( + new String(blob.getBytes(), UTF_8)); + } + } + private void checkConnectivity() throws IOException { ObjectIdSubclassMap baseObjects = null; ObjectIdSubclassMap providedObjects = null; @@ -1327,7 +1615,9 @@ } } - /** Validate the command list. */ + /** + * Validate the command list. + */ protected void validateCommands() { for (final ReceiveCommand cmd : commands) { final Ref ref = cmd.getRef(); @@ -1372,16 +1662,21 @@ } } - if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null - && !ObjectId.zeroId().equals(cmd.getOldId()) - && !ref.getObjectId().equals(cmd.getOldId())) { - // Delete commands can be sent with the old id matching our - // advertised value, *OR* with the old id being 0{40}. Any - // other requested old id is invalid. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().invalidOldIdSent); - continue; + if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) { + ObjectId id = ref.getObjectId(); + if (id == null) { + id = ObjectId.zeroId(); + } + if (!ObjectId.zeroId().equals(cmd.getOldId()) + && !id.equals(cmd.getOldId())) { + // Delete commands can be sent with the old id matching our + // advertised value, *OR* with the old id being 0{40}. Any + // other requested old id is invalid. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().invalidOldIdSent); + continue; + } } if (cmd.getType() == ReceiveCommand.Type.UPDATE) { @@ -1391,8 +1686,15 @@ cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef); continue; } + ObjectId id = ref.getObjectId(); + if (id == null) { + // We cannot update unborn branch + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().cannotUpdateUnbornBranch); + continue; + } - if (!ref.getObjectId().equals(cmd.getOldId())) { + if (!id.equals(cmd.getOldId())) { // A properly functioning client will send the same // object id we advertised. // @@ -1452,6 +1754,8 @@ } /** + * Whether any commands have been rejected so far. + * * @return if any commands have been rejected so far. * @since 3.6 */ @@ -1465,13 +1769,11 @@ /** * Set the result to fail for any command that was not processed yet. + * * @since 3.6 */ protected void failPendingCommands() { - for (ReceiveCommand cmd : commands) { - if (cmd.getResult() == Result.NOT_ATTEMPTED) - cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted); - } + ReceiveCommand.abort(commands); } /** @@ -1486,7 +1788,9 @@ return ReceiveCommand.filter(commands, want); } - /** Execute commands to update references. */ + /** + * Execute commands to update references. + */ protected void executeCommands() { List toApply = filterCommands(Result.NOT_ATTEMPTED); if (toApply.isEmpty()) @@ -1501,6 +1805,7 @@ BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate(); batch.setAllowNonFastForwards(isAllowNonFastForwards()); + batch.setAtomic(isAtomic()); batch.setRefLogIdent(getRefLogIdent()); batch.setRefLogMessage("push", true); //$NON-NLS-1$ batch.addCommand(toApply); @@ -1525,7 +1830,7 @@ * an error that occurred during unpacking, or {@code null} * @param out * the reporter for sending the status strings. - * @throws IOException + * @throws java.io.IOException * an error occurred writing the status report. */ protected void sendStatusReport(final boolean forClient, @@ -1612,7 +1917,7 @@ /** * Close and flush (if necessary) the underlying streams. * - * @throws IOException + * @throws java.io.IOException */ protected void close() throws IOException { if (sideBand) { @@ -1644,7 +1949,7 @@ /** * Release any resources used by this object. * - * @throws IOException + * @throws java.io.IOException * the pack could not be unlocked. */ protected void release() throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -92,7 +92,7 @@ InputStream bin; - final Map prereqs = new HashMap(); + final Map prereqs = new HashMap<>(); private String lockMessage; @@ -130,7 +130,7 @@ private void readBundleV2() throws IOException { final byte[] hdrbuf = new byte[1024]; - final LinkedHashMap avail = new LinkedHashMap(); + final LinkedHashMap avail = new LinkedHashMap<>(); for (;;) { String line = readLine(hdrbuf); if (line.length() == 0) @@ -161,22 +161,32 @@ } private String readLine(final byte[] hdrbuf) throws IOException { - bin.mark(hdrbuf.length); - final int cnt = bin.read(hdrbuf); - int lf = 0; - while (lf < cnt && hdrbuf[lf] != '\n') - lf++; - bin.reset(); - IO.skipFully(bin, lf); - if (lf < cnt && hdrbuf[lf] == '\n') - IO.skipFully(bin, 1); - return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf); + StringBuilder line = new StringBuilder(); + boolean done = false; + while (!done) { + bin.mark(hdrbuf.length); + final int cnt = bin.read(hdrbuf); + int lf = 0; + while (lf < cnt && hdrbuf[lf] != '\n') + lf++; + bin.reset(); + IO.skipFully(bin, lf); + if (lf < cnt && hdrbuf[lf] == '\n') { + IO.skipFully(bin, 1); + done = true; + } + line.append(RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf)); + } + return line.toString(); } + /** {@inheritDoc} */ + @Override public boolean didFetchTestConnectivity() { return false; } + /** {@inheritDoc} */ @Override protected void doFetch(final ProgressMonitor monitor, final Collection want, final Set have) @@ -200,10 +210,14 @@ } } + /** {@inheritDoc} */ + @Override public void setPackLockMessage(final String message) { lockMessage = message; } + /** {@inheritDoc} */ + @Override public Collection getPackLocks() { if (packLock != null) return Collections.singleton(packLock); @@ -218,8 +232,8 @@ final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$ final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$ - final Map missing = new HashMap(); - final List commits = new ArrayList(); + final Map missing = new HashMap<>(); + final List commits = new ArrayList<>(); for (final Map.Entry e : prereqs.entrySet()) { ObjectId p = e.getKey(); try { @@ -280,6 +294,7 @@ } } + /** {@inheritDoc} */ @Override public void close() { if (bin != null) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,6 +58,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; @@ -69,7 +70,7 @@ *

      * Bundles generated by this class can be later read in from a file URI using * the bundle transport, or from an application controlled buffer by the more - * generic {@link TransportBundleStream}. + * generic {@link org.eclipse.jgit.transport.TransportBundleStream}. *

      * Applications creating bundles need to call one or more include * calls to reflect which objects should be available as refs in the bundle for @@ -84,6 +85,8 @@ public class BundleWriter { private final Repository db; + private final ObjectReader reader; + private final Map include; private final Set assume; @@ -100,11 +103,29 @@ * @param repo * repository where objects are stored. */ - public BundleWriter(final Repository repo) { + public BundleWriter(Repository repo) { db = repo; - include = new TreeMap(); - assume = new HashSet(); - tagTargets = new HashSet(); + reader = null; + include = new TreeMap<>(); + assume = new HashSet<>(); + tagTargets = new HashSet<>(); + } + + /** + * Create a writer for a bundle. + * + * @param or + * reader for reading objects. Will be closed at the end of {@link + * #writeBundle(ProgressMonitor, OutputStream)}, but readers may be + * reused after closing. + * @since 4.8 + */ + public BundleWriter(ObjectReader or) { + db = null; + reader = or; + include = new TreeMap<>(); + assume = new HashSet<>(); + tagTargets = new HashSet<>(); } /** @@ -112,7 +133,8 @@ * * @param pc * configuration controlling packing parameters. If null the - * source repository's settings will be used. + * source repository's settings will be used, or the default + * settings if constructed without a repo. */ public void setPackConfig(PackConfig pc) { this.packConfig = pc; @@ -186,24 +208,18 @@ * the stream the bundle is written to. The stream should be * buffered by the caller. The caller is responsible for closing * the stream. - * @throws IOException + * @throws java.io.IOException * 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 { - PackConfig pc = packConfig; - if (pc == null) - pc = new PackConfig(db); - try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) { + try (PackWriter packWriter = newPackWriter()) { packWriter.setObjectCountCallback(callback); - final HashSet inc = new HashSet(); - final HashSet exc = new HashSet(); + final HashSet inc = new HashSet<>(); + final HashSet exc = new HashSet<>(); inc.addAll(include.values()); for (final RevCommit r : assume) exc.add(r.getId()); @@ -242,18 +258,25 @@ } } + private PackWriter newPackWriter() { + PackConfig pc = packConfig; + if (pc == null) { + pc = db != null ? new PackConfig(db) : new PackConfig(); + } + return new PackWriter(pc, reader != null ? reader : db.newObjectReader()); + } + /** - * Set the {@link ObjectCountCallback}. + * Set the {@link org.eclipse.jgit.transport.ObjectCountCallback}. *

      * It should be set before calling * {@link #writeBundle(ProgressMonitor, OutputStream)}. *

      * This callback will be passed on to - * {@link PackWriter#setObjectCountCallback}. + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#setObjectCountCallback}. * * @param callback * the callback to set - * * @return this object for chaining. * @since 4.1 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,17 +68,11 @@ * here */ public ChainingCredentialsProvider(CredentialsProvider... providers) { - this.credentialProviders = new ArrayList( + this.credentialProviders = new ArrayList<>( Arrays.asList(providers)); - for (CredentialsProvider p : providers) - credentialProviders.add(p); } - /** - * @return {@code true} if any of the credential providers in the list is - * interactive, otherwise {@code false} - * @see org.eclipse.jgit.transport.CredentialsProvider#isInteractive() - */ + /** {@inheritDoc} */ @Override public boolean isInteractive() { for (CredentialsProvider p : credentialProviders) @@ -87,11 +81,7 @@ return false; } - /** - * @return {@code true} if any of the credential providers in the list - * supports the requested items, otherwise {@code false} - * @see org.eclipse.jgit.transport.CredentialsProvider#supports(org.eclipse.jgit.transport.CredentialItem[]) - */ + /** {@inheritDoc} */ @Override public boolean supports(CredentialItem... items) { for (CredentialsProvider p : credentialProviders) @@ -101,11 +91,11 @@ } /** + * {@inheritDoc} + *

      * Populates the credential items with the credentials provided by the first * credential provider in the list which populates them with non-null values * - * @return {@code true} if any of the credential providers in the list - * supports the requested items, otherwise {@code false} * @see org.eclipse.jgit.transport.CredentialsProvider#supports(org.eclipse.jgit.transport.CredentialItem[]) */ @Override @@ -113,19 +103,18 @@ throws UnsupportedCredentialItem { for (CredentialsProvider p : credentialProviders) { if (p.supports(items)) { - p.get(uri, items); - if (isAnyNull(items)) + if (!p.get(uri, items)) { + if (p.isInteractive()) { + return false; // user cancelled the request + } continue; + } + if (isAnyNull(items)) { + continue; + } return true; } } return false; } - - private boolean isAnyNull(CredentialItem... items) { - for (CredentialItem i : items) - if (i == null) - return true; - return false; - } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,8 +59,7 @@ * * @see Transport */ -public interface Connection { - +public interface Connection extends AutoCloseable { /** * Get the complete map of refs advertised as available for fetching or * pushing. @@ -99,6 +98,8 @@ public Ref getRef(final String name); /** + * {@inheritDoc} + *

      * Close any resources used by this connection. *

      * If the remote repository is contacted by a network socket this method @@ -108,7 +109,12 @@ *

      * If additional messages were produced by the remote peer, these should * still be retained in the connection instance for {@link #getMessages()}. + *

      + * {@code AutoClosable.close()} declares that it throws {@link Exception}. + * Implementers shouldn't throw checked exceptions. This override narrows + * the signature to prevent them from doing so. */ + @Override public void close(); /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,14 +48,19 @@ import org.eclipse.jgit.internal.JGitText; /** - * A credential requested from a {@link CredentialsProvider}. + * A credential requested from a + * {@link org.eclipse.jgit.transport.CredentialsProvider}. * * Most users should work with the specialized subclasses: *

        - *
      • {@link Username} for usernames
      • - *
      • {@link Password} for passwords
      • - *
      • {@link StringType} for other general string information
      • - *
      • {@link CharArrayType} for other general secret information
      • + *
      • {@link org.eclipse.jgit.transport.CredentialItem.Username} for + * usernames
      • + *
      • {@link org.eclipse.jgit.transport.CredentialItem.Password} for + * passwords
      • + *
      • {@link org.eclipse.jgit.transport.CredentialItem.StringType} for other + * general string information
      • + *
      • {@link org.eclipse.jgit.transport.CredentialItem.CharArrayType} for other + * general secret information
      • *
      * * This class is not thread-safe. Applications should construct their own @@ -83,17 +88,27 @@ this.valueSecure = maskValue; } - /** @return prompt to display to the user. */ + /** + * Get prompt to display to the user. + * + * @return prompt to display to the user. + */ public String getPromptText() { return promptText; } - /** @return true if the value should be masked when entered. */ + /** + * Whether the value should be masked when entered. + * + * @return true if the value should be masked when entered. + */ public boolean isValueSecure() { return valueSecure; } - /** Clear the stored value, destroying it as much as possible. */ + /** + * Clear the stored value, destroying it as much as possible. + */ public abstract void clear(); /** diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,9 +52,10 @@ * Provide credentials for use in connecting to Git repositories. * * Implementors are strongly encouraged to support at least the minimal - * {@link CredentialItem.Username} and {@link CredentialItem.Password} items. - * More sophisticated implementors may implement additional types, such as - * {@link CredentialItem.StringType}. + * {@link org.eclipse.jgit.transport.CredentialItem.Username} and + * {@link org.eclipse.jgit.transport.CredentialItem.Password} items. More + * sophisticated implementors may implement additional types, such as + * {@link org.eclipse.jgit.transport.CredentialItem.StringType}. * * CredentialItems are usually presented in bulk, allowing implementors to * combine them into a single UI widget and streamline the authentication @@ -65,7 +66,11 @@ public abstract class CredentialsProvider { private static volatile CredentialsProvider defaultProvider; - /** @return the default credentials provider, or null. */ + /** + * Get the default credentials provider, or null. + * + * @return the default credentials provider, or null. + */ public static CredentialsProvider getDefault() { return defaultProvider; } @@ -81,6 +86,22 @@ } /** + * Whether any of the passed items is null + * + * @param items + * credential items to check + * @return {@code true} if any of the passed items is null, {@code false} + * otherwise + * @since 4.2 + */ + protected static boolean isAnyNull(CredentialItem... items) { + for (CredentialItem i : items) + if (i == null) + return true; + return false; + } + + /** * Check if the provider is interactive with the end-user. * * An interactive provider may try to open a dialog box, or prompt for input @@ -92,12 +113,14 @@ public abstract boolean isInteractive(); /** - * Check if the provider can supply the necessary {@link CredentialItem}s. + * Check if the provider can supply the necessary + * {@link org.eclipse.jgit.transport.CredentialItem}s. * * @param items * the items the application requires to complete authentication. - * @return {@code true} if this {@link CredentialsProvider} supports all of - * the items supplied. + * @return {@code true} if this + * {@link org.eclipse.jgit.transport.CredentialsProvider} supports + * all of the items supplied. */ public abstract boolean supports(CredentialItem... items); @@ -111,7 +134,7 @@ * @return {@code true} if the request was successful and values were * supplied; {@code false} if the user canceled the request and did * not supply all requested values. - * @throws UnsupportedCredentialItem + * @throws org.eclipse.jgit.errors.UnsupportedCredentialItem * if one of the items supplied is not supported. */ public abstract boolean get(URIish uri, CredentialItem... items) @@ -127,7 +150,7 @@ * @return {@code true} if the request was successful and values were * supplied; {@code false} if the user canceled the request and did * not supply all requested values. - * @throws UnsupportedCredentialItem + * @throws org.eclipse.jgit.errors.UnsupportedCredentialItem * if one of the items supplied is not supported. */ public boolean get(URIish uri, List items) @@ -139,6 +162,7 @@ * Reset the credentials provider for the given URI * * @param uri + * a {@link org.eclipse.jgit.transport.URIish} object. */ public void reset(URIish uri) { // default does nothing diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,10 @@ import com.jcraft.jsch.UIKeyboardInteractive; import com.jcraft.jsch.UserInfo; -/** A JSch {@link UserInfo} adapter for a {@link CredentialsProvider}. */ +/** + * A JSch {@link com.jcraft.jsch.UserInfo} adapter for a + * {@link org.eclipse.jgit.transport.CredentialsProvider}. + */ public class CredentialsProviderUserInfo implements UserInfo, UIKeyboardInteractive { private final URIish uri; @@ -85,14 +88,20 @@ return uri; } + /** {@inheritDoc} */ + @Override public String getPassword() { return password; } + /** {@inheritDoc} */ + @Override public String getPassphrase() { return passphrase; } + /** {@inheritDoc} */ + @Override public boolean promptPassphrase(String msg) { CredentialItem.StringType v = newPrompt(msg); if (provider.get(uri, v)) { @@ -104,6 +113,8 @@ } } + /** {@inheritDoc} */ + @Override public boolean promptPassword(String msg) { CredentialItem.Password p = new CredentialItem.Password(msg); if (provider.get(uri, p)) { @@ -119,22 +130,28 @@ return new CredentialItem.StringType(msg, true); } + /** {@inheritDoc} */ + @Override public boolean promptYesNo(String msg) { CredentialItem.YesNoType v = new CredentialItem.YesNoType(msg); return provider.get(uri, v) && v.getValue(); } + /** {@inheritDoc} */ + @Override public void showMessage(String msg) { provider.get(uri, new CredentialItem.InformationalMessage(msg)); } + /** {@inheritDoc} */ + @Override public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) { CredentialItem.StringType[] v = new CredentialItem.StringType[prompt.length]; for (int i = 0; i < prompt.length; i++) v[i] = new CredentialItem.StringType(prompt[i], !echo[i]); - List items = new ArrayList(); + List items = new ArrayList<>(); if (instruction != null && instruction.length() > 0) items.add(new CredentialItem.InformationalMessage(instruction)); items.addAll(Arrays.asList(v)); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -52,9 +53,10 @@ import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; -/** Active network client of {@link Daemon}. */ +/** + * Active network client of {@link org.eclipse.jgit.transport.Daemon}. + */ public class DaemonClient { private final Daemon daemon; @@ -72,22 +74,38 @@ peer = ia; } - /** @return the daemon which spawned this client. */ + /** + * Get the daemon which spawned this client. + * + * @return the daemon which spawned this client. + */ public Daemon getDaemon() { return daemon; } - /** @return Internet address of the remote client. */ + /** + * Get Internet address of the remote client. + * + * @return Internet address of the remote client. + */ public InetAddress getRemoteAddress() { return peer; } - /** @return input stream to read from the connected client. */ + /** + * Get input stream to read from the connected client. + * + * @return input stream to read from the connected client. + */ public InputStream getInputStream() { return rawIn; } - /** @return output stream to send data to the connected client. */ + /** + * Get output stream to send data to the connected client. + * + * @return output stream to send data to the connected client. + */ public OutputStream getOutputStream() { return rawOut; } @@ -95,7 +113,7 @@ void execute(final Socket sock) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { rawIn = new BufferedInputStream(sock.getInputStream()); - rawOut = new SafeBufferedOutputStream(sock.getOutputStream()); + rawOut = new BufferedOutputStream(sock.getOutputStream()); if (0 < daemon.getTimeout()) sock.setSoTimeout(daemon.getTimeout() * 1000); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,13 +45,14 @@ import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketException; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.internal.JGitText; @@ -64,7 +65,9 @@ import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; -/** Basic daemon for the anonymous git:// transport protocol. */ +/** + * Basic daemon for the anonymous git:// transport protocol. + */ public class Daemon { /** 9418: IANA assigned port number for Git. */ public static final int DEFAULT_PORT = 9418; @@ -77,9 +80,7 @@ private final ThreadGroup processors; - private boolean run; - - private Thread acceptThread; + private Acceptor acceptThread; private int timeout; @@ -87,11 +88,13 @@ private volatile RepositoryResolver repositoryResolver; - private volatile UploadPackFactory uploadPackFactory; + volatile UploadPackFactory uploadPackFactory; - private volatile ReceivePackFactory receivePackFactory; + volatile ReceivePackFactory receivePackFactory; - /** Configure a daemon to listen on any available network port. */ + /** + * Configure a daemon to listen on any available network port. + */ public Daemon() { this(null); } @@ -111,6 +114,7 @@ repositoryResolver = (RepositoryResolver) RepositoryResolver.NONE; uploadPackFactory = new UploadPackFactory() { + @Override public UploadPack create(DaemonClient req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { @@ -122,6 +126,7 @@ }; receivePackFactory = new ReceivePackFactory() { + @Override public ReceivePack create(DaemonClient req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { @@ -174,7 +179,11 @@ } }; } - /** @return the address connections are received on. */ + /** + * Get the address connections are received on. + * + * @return the address connections are received on. + */ public synchronized InetSocketAddress getAddress() { return myAddress; } @@ -198,7 +207,11 @@ return null; } - /** @return timeout (in seconds) before aborting an IO operation. */ + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @return timeout (in seconds) before aborting an IO operation. + */ public int getTimeout() { return timeout; } @@ -215,7 +228,11 @@ timeout = seconds; } - /** @return configuration controlling packing, may be null. */ + /** + * Get configuration controlling packing, may be null. + * + * @return configuration controlling packing, may be null. + */ public PackConfig getPackConfig() { return packConfig; } @@ -256,6 +273,16 @@ } /** + * Get the factory used to construct per-request ReceivePack. + * + * @return the factory. + * @since 4.3 + */ + public ReceivePackFactory getReceivePackFactory() { + return receivePackFactory; + } + + /** * Set the factory to construct and configure per-request ReceivePack. * * @param factory @@ -269,64 +296,122 @@ receivePackFactory = (ReceivePackFactory) ReceivePackFactory.DISABLED; } + private class Acceptor extends Thread { + + private final ServerSocket listenSocket; + + private final AtomicBoolean running = new AtomicBoolean(true); + + public Acceptor(ThreadGroup group, String name, ServerSocket socket) { + super(group, name); + this.listenSocket = socket; + } + + @Override + public void run() { + setUncaughtExceptionHandler((thread, throwable) -> terminate()); + while (isRunning()) { + try { + startClient(listenSocket.accept()); + } catch (SocketException e) { + // Test again to see if we should keep accepting. + } catch (IOException e) { + break; + } + } + + terminate(); + } + + private void terminate() { + try { + shutDown(); + } finally { + clearThread(); + } + } + + public boolean isRunning() { + return running.get(); + } + + public void shutDown() { + running.set(false); + try { + listenSocket.close(); + } catch (IOException err) { + // + } + } + + } + /** * Start this daemon on a background thread. * - * @throws IOException + * @throws java.io.IOException * the server socket could not be opened. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * the daemon is already running. */ public synchronized void start() throws IOException { - if (acceptThread != null) + if (acceptThread != null) { throw new IllegalStateException(JGitText.get().daemonAlreadyRunning); + } + ServerSocket socket = new ServerSocket(); + socket.setReuseAddress(true); + if (myAddress != null) { + socket.bind(myAddress, BACKLOG); + } else { + socket.bind(new InetSocketAddress((InetAddress) null, 0), BACKLOG); + } + myAddress = (InetSocketAddress) socket.getLocalSocketAddress(); - final ServerSocket listenSock = new ServerSocket( - myAddress != null ? myAddress.getPort() : 0, BACKLOG, - myAddress != null ? myAddress.getAddress() : null); - myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress(); - - run = true; - acceptThread = new Thread(processors, "Git-Daemon-Accept") { //$NON-NLS-1$ - public void run() { - while (isRunning()) { - try { - startClient(listenSock.accept()); - } catch (InterruptedIOException e) { - // Test again to see if we should keep accepting. - } catch (IOException e) { - break; - } - } - - try { - listenSock.close(); - } catch (IOException err) { - // - } finally { - synchronized (Daemon.this) { - acceptThread = null; - } - } - } - }; + acceptThread = new Acceptor(processors, "Git-Daemon-Accept", socket); //$NON-NLS-1$ acceptThread.start(); } - /** @return true if this daemon is receiving connections. */ + private synchronized void clearThread() { + acceptThread = null; + } + + /** + * Whether this daemon is receiving connections. + * + * @return {@code true} if this daemon is receiving connections. + */ public synchronized boolean isRunning() { - return run; + return acceptThread != null && acceptThread.isRunning(); } - /** Stop this daemon. */ + /** + * Stop this daemon. + */ public synchronized void stop() { if (acceptThread != null) { - run = false; - acceptThread.interrupt(); + acceptThread.shutDown(); + } + } + + /** + * Stops this daemon and waits until it's acceptor thread has finished. + * + * @throws java.lang.InterruptedException + * if waiting for the acceptor thread is interrupted + * @since 4.9 + */ + public void stopAndWait() throws InterruptedException { + Thread acceptor = null; + synchronized (this) { + acceptor = acceptThread; + stop(); + } + if (acceptor != null) { + acceptor.join(); } } - private void startClient(final Socket s) { + void startClient(final Socket s) { final DaemonClient dc = new DaemonClient(this); final SocketAddress peer = s.getRemoteSocketAddress(); @@ -334,6 +419,7 @@ dc.setRemoteAddress(((InetSocketAddress) peer).getAddress()); new Thread(processors, "Git-Daemon-Client " + peer.toString()) { //$NON-NLS-1$ + @Override public void run() { try { dc.execute(s); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,12 +47,15 @@ import java.io.IOException; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; -/** A service exposed by {@link Daemon} over anonymous git://. */ +/** + * A service exposed by {@link org.eclipse.jgit.transport.Daemon} over anonymous + * git://. + */ public abstract class DaemonService { private final String command; @@ -64,11 +67,7 @@ DaemonService(final String cmdName, final String cfgName) { command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$ - configKey = new SectionParser() { - public ServiceConfig parse(final Config cfg) { - return new ServiceConfig(DaemonService.this, cfg, cfgName); - } - }; + configKey = cfg -> new ServiceConfig(DaemonService.this, cfg, cfgName); overridable = true; } @@ -81,34 +80,55 @@ } } - /** @return is this service enabled for invocation? */ + /** + * Whether this service is enabled for invocation. + * + * @return whether this service is enabled for invocation. + */ public boolean isEnabled() { return enabled; } /** + * Set if it is allowed to use this service + * * @param on - * true to allow this service to be used; false to deny it. + * {@code true} to allow this service to be used; {@code false} + * to deny it. */ public void setEnabled(final boolean on) { enabled = on; } /** @return can this service be configured in the repository config file? */ + /** + * Whether this service can be configured in the repository config file + * + * @return whether this service can be configured in the repository config + * file + */ public boolean isOverridable() { return overridable; } /** + * Whether to permit repositories to override this service's enabled state + * with the daemon.servicename config setting. + * * @param on - * true to permit repositories to override this service's enabled - * state with the daemon.servicename config setting. + * {@code true} to permit repositories to override this service's + * enabled state with the daemon.servicename config + * setting. */ public void setOverridable(final boolean on) { overridable = on; } - /** @return name of the command requested by clients. */ + /** + * Get name of the command requested by clients. + * + * @return name of the command requested by clients. + */ public String getCommandName() { return command; } @@ -130,23 +150,15 @@ throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { final String name = commandLine.substring(command.length() + 1); - Repository db; - try { - db = client.getDaemon().openRepository(client, name); + try (Repository db = client.getDaemon().openRepository(client, name)) { + if (isEnabledFor(db)) { + execute(client, db); + } } catch (ServiceMayNotContinueException e) { // An error when opening the repo means the client is expecting a ref // advertisement, so use that style of error. PacketLineOut pktOut = new PacketLineOut(client.getOutputStream()); pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ - db = null; - } - if (db == null) - return; - try { - if (isEnabledFor(db)) - execute(client, db); - } finally { - db.close(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,6 +59,8 @@ * connection will immediately fail. */ class DefaultSshSessionFactory extends JschConfigSessionFactory { + /** {@inheritDoc} */ + @Override protected void configure(final OpenSshConfig.Host hc, final Session session) { // No additional configuration required. } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,9 +64,9 @@ * one-way object transfer service to copy objects from the remote repository * into this local repository. *

      - * Instances of a FetchConnection must be created by a {@link Transport} that - * implements a specific object transfer protocol that both sides of the - * connection understand. + * Instances of a FetchConnection must be created by a + * {@link org.eclipse.jgit.transport.Transport} that implements a specific + * object transfer protocol that both sides of the connection understand. *

      * FetchConnection instances are not thread safe and may be accessed by only one * thread at a time. @@ -78,7 +78,7 @@ * Fetch objects we don't have but that are reachable from advertised refs. *

      * Only one call per connection is allowed. Subsequent calls will result in - * {@link TransportException}. + * {@link org.eclipse.jgit.errors.TransportException}. *

      *

      * Implementations are free to use network connections as necessary to @@ -87,7 +87,8 @@ * avoid replacing/overwriting/duplicating an object already available in * the local destination repository. Locally available objects and packs * should always be preferred over remotely available objects and packs. - * {@link Transport#isFetchThin()} should be honored if applicable. + * {@link org.eclipse.jgit.transport.Transport#isFetchThin()} should be + * honored if applicable. *

      * * @param monitor @@ -103,7 +104,7 @@ * repository, especially if they aren't yet reachable by the ref * database. Connections should take this set as an addition to * what is reachable through all Refs, not in replace of it. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * protocol error, or error on remote side, or connection was * already used for fetch. @@ -116,7 +117,7 @@ * Fetch objects we don't have but that are reachable from advertised refs. *

      * Only one call per connection is allowed. Subsequent calls will result in - * {@link TransportException}. + * {@link org.eclipse.jgit.errors.TransportException}. *

      *

      * Implementations are free to use network connections as necessary to @@ -125,7 +126,8 @@ * avoid replacing/overwriting/duplicating an object already available in * the local destination repository. Locally available objects and packs * should always be preferred over remotely available objects and packs. - * {@link Transport#isFetchThin()} should be honored if applicable. + * {@link org.eclipse.jgit.transport.Transport#isFetchThin()} should be + * honored if applicable. *

      * * @param monitor @@ -143,7 +145,7 @@ * what is reachable through all Refs, not in replace of it. * @param out * OutputStream to write sideband messages to - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * protocol error, or error on remote side, or connection was * already used for fetch. @@ -157,11 +159,12 @@ * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} get tags? *

      * Some Git aware transports are able to implicitly grab an annotated tag if - * {@link TagOpt#AUTO_FOLLOW} or {@link TagOpt#FETCH_TAGS} was selected and - * the object the tag peels to (references) was transferred as part of the - * last {@link #fetch(ProgressMonitor, Collection, Set)} call. If it is - * possible for such tags to have been included in the transfer this method - * returns true, allowing the caller to attempt tag discovery. + * {@link org.eclipse.jgit.transport.TagOpt#AUTO_FOLLOW} or + * {@link org.eclipse.jgit.transport.TagOpt#FETCH_TAGS} was selected and the + * object the tag peels to (references) was transferred as part of the last + * {@link #fetch(ProgressMonitor, Collection, Set)} call. If it is possible + * for such tags to have been included in the transfer this method returns + * true, allowing the caller to attempt tag discovery. *

      * By returning only true/false (and not the actual list of tags obtained) * the transport itself does not need to be aware of whether or not tags diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,6 +74,7 @@ import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; @@ -88,18 +89,18 @@ private final Collection toFetch; /** Set of refs we will actually wind up asking to obtain. */ - private final HashMap askFor = new HashMap(); + private final HashMap askFor = new HashMap<>(); /** Objects we know we have locally. */ - private final HashSet have = new HashSet(); + private final HashSet have = new HashSet<>(); /** Updates to local tracking branches (if any). */ - private final ArrayList localUpdates = new ArrayList(); + private final ArrayList localUpdates = new ArrayList<>(); /** Records to be recorded into FETCH_HEAD. */ - private final ArrayList fetchHeadUpdates = new ArrayList(); + private final ArrayList fetchHeadUpdates = new ArrayList<>(); - private final ArrayList packLocks = new ArrayList(); + private final ArrayList packLocks = new ArrayList<>(); private FetchConnection conn; @@ -137,7 +138,7 @@ try { result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); result.peerUserAgent = conn.getPeerUserAgent(); - final Set matched = new HashSet(); + final Set matched = new HashSet<>(); for (final RefSpec spec : toFetch) { if (spec.getSource() == null) throw new TransportException(MessageFormat.format( @@ -202,12 +203,10 @@ ((BatchingProgressMonitor) monitor).setDelayStart( 250, TimeUnit.MILLISECONDS); } - if (transport.isRemoveDeletedRefs()) + if (transport.isRemoveDeletedRefs()) { deleteStaleTrackingRefs(result, batch); - for (TrackingRefUpdate u : localUpdates) { - result.add(u); - batch.addCommand(u.asReceiveCommand()); } + addUpdateBatchCommands(result, batch); for (ReceiveCommand cmd : batch.getCommands()) { cmd.updateType(walk); if (cmd.getType() == UPDATE_NONFASTFORWARD @@ -220,8 +219,11 @@ if (cmd.getResult() == NOT_ATTEMPTED) cmd.setResult(OK); } - } else + } else { batch.execute(walk, monitor); + } + } catch (TransportException e) { + throw e; } catch (IOException err) { throw new TransportException(MessageFormat.format( JGitText.get().failureUpdatingTrackingRef, @@ -238,6 +240,23 @@ } } + private void addUpdateBatchCommands(FetchResult result, + BatchRefUpdate batch) throws TransportException { + Map refs = new HashMap<>(); + for (TrackingRefUpdate u : localUpdates) { + // Try to skip duplicates if they'd update to the same object ID + ObjectId existing = refs.get(u.getLocalName()); + if (existing == null) { + refs.put(u.getLocalName(), u.getNewObjectId()); + result.add(u); + batch.addCommand(u.asReceiveCommand()); + } else if (!existing.equals(u.getNewObjectId())) { + throw new TransportException(MessageFormat + .format(JGitText.get().duplicateRef, u.getLocalName())); + } + } + } + private void fetchObjects(final ProgressMonitor monitor) throws TransportException { try { @@ -275,11 +294,11 @@ // We rebuild our askFor list using only the refs that the // new connection has offered to us. // - final HashMap avail = new HashMap(); + final HashMap avail = new HashMap<>(); for (final Ref r : conn.getRefs()) avail.put(r.getObjectId(), r); - final Collection wants = new ArrayList(askFor.values()); + final Collection wants = new ArrayList<>(askFor.values()); askFor.clear(); for (final Ref want : wants) { final Ref newRef = avail.get(want.getObjectId()); @@ -314,18 +333,15 @@ File meta = transport.local.getDirectory(); if (meta == null) return; - final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD"), //$NON-NLS-1$ - transport.local.getFS()); + final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); //$NON-NLS-1$ try { if (lock.lock()) { - final Writer w = new OutputStreamWriter(lock.getOutputStream()); - try { + try (Writer w = new OutputStreamWriter( + lock.getOutputStream())) { for (final FetchHeadRecord h : fetchHeadUpdates) { h.write(w); result.add(h); } - } finally { - w.close(); } lock.commit(); } @@ -361,16 +377,23 @@ private void expandSingle(final RefSpec spec, final Set matched) throws TransportException { - final Ref src = conn.getRef(spec.getSource()); + String want = spec.getSource(); + if (ObjectId.isId(want)) { + want(ObjectId.fromString(want)); + return; + } + + Ref src = conn.getRef(want); if (src == null) { - throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, spec.getSource())); + throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, want)); } - if (matched.add(src)) + if (matched.add(src)) { want(src, spec); + } } private Collection expandAutoFollowTags() throws TransportException { - final Collection additionalTags = new ArrayList(); + final Collection additionalTags = new ArrayList<>(); final Map haveRefs = localRefs(); for (final Ref r : conn.getRefs()) { if (!isTag(r)) @@ -397,11 +420,17 @@ private void expandFetchTags() throws TransportException { final Map haveRefs = localRefs(); for (final Ref r : conn.getRefs()) { - if (!isTag(r)) + if (!isTag(r)) { + continue; + } + ObjectId id = r.getObjectId(); + if (id == null) { continue; + } final Ref local = haveRefs.get(r.getName()); - if (local == null || !r.getObjectId().equals(local.getObjectId())) + if (local == null || !id.equals(local.getObjectId())) { wantTag(r); + } } } @@ -413,6 +442,11 @@ private void want(final Ref src, final RefSpec spec) throws TransportException { final ObjectId newId = src.getObjectId(); + if (newId == null) { + throw new NullPointerException(MessageFormat.format( + JGitText.get().transportProvidedRefWithNoObjectId, + src.getName())); + } if (spec.getDestination() != null) { final TrackingRefUpdate tru = createUpdate(spec, newId); if (newId.equals(tru.getOldObjectId())) @@ -430,6 +464,11 @@ fetchHeadUpdates.add(fhr); } + private void want(ObjectId id) { + askFor.put(id, + new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), id)); + } + private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId) throws TransportException { Ref ref = localRefs().get(spec.getDestination()); @@ -458,12 +497,14 @@ private void deleteStaleTrackingRefs(FetchResult result, BatchRefUpdate batch) throws IOException { + final Set processed = new HashSet<>(); for (final Ref ref : localRefs().values()) { final String refname = ref.getName(); for (final RefSpec spec : toFetch) { if (spec.matchDestination(refname)) { final RefSpec s = spec.expandFromDestination(refname); - if (result.getAdvertisedRef(s.getSource()) == null) { + if (result.getAdvertisedRef(s.getSource()) == null + && processed.add(ref)) { deleteTrackingRef(result, batch, s, ref); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,10 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Final status after a successful fetch from a remote repository. @@ -58,12 +61,39 @@ public class FetchResult extends OperationResult { private final List forMerge; + private final Map submodules; + FetchResult() { - forMerge = new ArrayList(); + forMerge = new ArrayList<>(); + submodules = new HashMap<>(); } void add(final FetchHeadRecord r) { if (!r.notForMerge) forMerge.add(r); } + + /** + * Add fetch results for a submodule. + * + * @param path + * the submodule path + * @param result + * the fetch result + * @since 4.7 + */ + public void addSubmodule(String path, FetchResult result) { + submodules.put(path, result); + } + + /** + * Get fetch results for submodules. + * + * @return Fetch results for submodules as a map of submodule paths to fetch + * results. + * @since 4.7 + */ + public Map submoduleResults() { + return Collections.unmodifiableMap(submodules); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java 2019-09-03 12:37:49.000000000 +0000 @@ -208,6 +208,13 @@ */ public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$ + /** + * The server supports the receiving of push options. + * + * @since 4.5 + */ + public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$ + static enum MultiAck { OFF, CONTINUE, DETAILED; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,7 +42,10 @@ */ package org.eclipse.jgit.transport; -import java.io.UnsupportedEncodingException; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.File; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -51,7 +54,6 @@ import org.eclipse.jgit.internal.storage.dfs.DfsRepository; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.NonceGenerator; import org.eclipse.jgit.transport.PushCertificate.NonceStatus; /** @@ -64,12 +66,15 @@ private Mac mac; /** + * Constructor for HMACSHA1NonceGenerator. + * * @param seed - * @throws IllegalStateException + * seed the generator + * @throws java.lang.IllegalStateException */ public HMACSHA1NonceGenerator(String seed) throws IllegalStateException { try { - byte[] keyBytes = seed.getBytes("ISO-8859-1"); //$NON-NLS-1$ + byte[] keyBytes = seed.getBytes(ISO_8859_1); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); //$NON-NLS-1$ mac = Mac.getInstance("HmacSHA1"); //$NON-NLS-1$ mac.init(signingKey); @@ -77,31 +82,31 @@ throw new IllegalStateException(e); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); } } + /** {@inheritDoc} */ + @Override public synchronized String createNonce(Repository repo, long timestamp) throws IllegalStateException { String path; - if (repo instanceof DfsRepository) + if (repo instanceof DfsRepository) { path = ((DfsRepository) repo).getDescription().getRepositoryName(); - else if (repo.getDirectory() != null) - path = repo.getDirectory().getPath(); - else - throw new IllegalStateException(); + } else { + File directory = repo.getDirectory(); + if (directory != null) { + path = directory.getPath(); + } else { + throw new IllegalStateException(); + } + } String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$ - byte[] rawHmac; - try { - rawHmac = mac.doFinal(input.getBytes("UTF-8")); //$NON-NLS-1$ - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } + byte[] rawHmac = mac.doFinal(input.getBytes(UTF_8)); return Long.toString(timestamp) + "-" + toHex(rawHmac); //$NON-NLS-1$ } + /** {@inheritDoc} */ @Override public NonceStatus verify(String received, String sent, Repository db, boolean allowSlop, int slop) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,30 +47,33 @@ import java.net.URL; /** - * The interface of a factory returning {@link HttpConnection} + * The interface of a factory returning + * {@link org.eclipse.jgit.transport.http.HttpConnection} * * @since 3.3 */ public interface HttpConnectionFactory { /** - * Creates a new connection to a destination defined by a {@link URL} + * Creates a new connection to a destination defined by a + * {@link java.net.URL} * * @param url - * @return a {@link HttpConnection} - * @throws IOException + * a {@link java.net.URL} object. + * @return a {@link org.eclipse.jgit.transport.http.HttpConnection} + * @throws java.io.IOException */ public HttpConnection create(URL url) throws IOException; /** - * Creates a new connection to a destination defined by a {@link URL} using - * a proxy + * Creates a new connection to a destination defined by a + * {@link java.net.URL} using a proxy * * @param url + * a {@link java.net.URL} object. * @param proxy * the proxy to be used - * @return a {@link HttpConnection} - * - * @throws IOException + * @return a {@link org.eclipse.jgit.transport.http.HttpConnection} + * @throws java.io.IOException */ public HttpConnection create(URL url, Proxy proxy) throws IOException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Christian Halstrick + * Copyright (C) 2013, 2017 Christian Halstrick * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -56,13 +56,12 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; /** * The interface of connections used during HTTP communication. This interface - * is that subset of the interface exposed by {@link HttpURLConnection} which is - * used by JGit + * is that subset of the interface exposed by {@link java.net.HttpURLConnection} + * which is used by JGit * * @since 3.3 */ @@ -73,6 +72,32 @@ public static final int HTTP_OK = java.net.HttpURLConnection.HTTP_OK; /** + * @see HttpURLConnection#HTTP_MOVED_PERM + * @since 4.7 + */ + public static final int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM; + + /** + * @see HttpURLConnection#HTTP_MOVED_TEMP + * @since 4.9 + */ + public static final int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP; + + /** + * @see HttpURLConnection#HTTP_SEE_OTHER + * @since 4.9 + */ + public static final int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER; + + /** + * HTTP 1.1 additional MOVED_TEMP status code; value = 307. + * + * @see #HTTP_MOVED_TEMP + * @since 4.9 + */ + public static final int HTTP_11_MOVED_TEMP = 307; + + /** * @see HttpURLConnection#HTTP_NOT_FOUND */ public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND; @@ -88,32 +113,42 @@ public static final int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN; /** + * Get response code + * * @see HttpURLConnection#getResponseCode() * @return the HTTP Status-Code, or -1 - * @throws IOException + * @throws java.io.IOException */ public int getResponseCode() throws IOException; /** + * Get URL + * * @see HttpURLConnection#getURL() * @return the URL. */ public URL getURL(); /** + * Get response message + * * @see HttpURLConnection#getResponseMessage() * @return the HTTP response message, or null - * @throws IOException + * @throws java.io.IOException */ public String getResponseMessage() throws IOException; /** + * Get list of header fields + * * @see HttpURLConnection#getHeaderFields() * @return a Map of header fields */ public Map> getHeaderFields(); /** + * Set request property + * * @see HttpURLConnection#setRequestProperty(String, String) * @param key * the keyword by which the request is known (e.g., " @@ -124,17 +159,23 @@ public void setRequestProperty(String key, String value); /** + * Set request method + * * @see HttpURLConnection#setRequestMethod(String) * @param method * the HTTP method * @exception ProtocolException * if the method cannot be reset or if the requested method * isn't valid for HTTP. + * @throws java.net.ProtocolException + * if any. */ public void setRequestMethod(String method) throws ProtocolException; /** + * Set if to use caches + * * @see HttpURLConnection#setUseCaches(boolean) * @param usecaches * a boolean indicating whether or not to allow @@ -143,6 +184,8 @@ public void setUseCaches(boolean usecaches); /** + * Set connect timeout + * * @see HttpURLConnection#setConnectTimeout(int) * @param timeout * an int that specifies the connect timeout value @@ -151,6 +194,8 @@ public void setConnectTimeout(int timeout); /** + * Set read timeout + * * @see HttpURLConnection#setReadTimeout(int) * @param timeout * an int that specifies the timeout value to be @@ -159,6 +204,8 @@ public void setReadTimeout(int timeout); /** + * Get content type + * * @see HttpURLConnection#getContentType() * @return the content type of the resource that the URL references, or * null if not known. @@ -166,14 +213,20 @@ public String getContentType(); /** + * Get input stream + * * @see HttpURLConnection#getInputStream() * @return an input stream that reads from this open connection. * @exception IOException * if an I/O error occurs while creating the input stream. + * @throws java.io.IOException + * if any. */ public InputStream getInputStream() throws IOException; /** + * Get header field + * * @see HttpURLConnection#getHeaderField(String) * @param name * the name of a header field. @@ -183,6 +236,8 @@ public String getHeaderField(String name); /** + * Get content length + * * @see HttpURLConnection#getContentLength() * @return the content length of the resource that this connection's URL * references, {@code -1} if the content length is not known, or if @@ -191,6 +246,8 @@ public int getContentLength(); /** + * Set whether or not to follow HTTP redirects. + * * @see HttpURLConnection#setInstanceFollowRedirects(boolean) * @param followRedirects * a boolean indicating whether or not to follow @@ -199,27 +256,35 @@ public void setInstanceFollowRedirects(boolean followRedirects); /** + * Set if to do output + * * @see HttpURLConnection#setDoOutput(boolean) - * @param dooutput the new value. + * @param dooutput + * the new value. */ public void setDoOutput(boolean dooutput); /** + * Set fixed length streaming mode + * * @see HttpURLConnection#setFixedLengthStreamingMode(int) * @param contentLength * The number of bytes which will be written to the OutputStream. - * */ public void setFixedLengthStreamingMode(int contentLength); /** + * Get output stream + * * @see HttpURLConnection#getOutputStream() * @return an output stream that writes to this connection. - * @throws IOException + * @throws java.io.IOException */ public OutputStream getOutputStream() throws IOException; /** + * Set chunked streaming mode + * * @see HttpURLConnection#setChunkedStreamingMode(int) * @param chunklen * The number of bytes to write in each chunk. If chunklen is @@ -228,26 +293,32 @@ public void setChunkedStreamingMode(int chunklen); /** + * Get request method + * * @see HttpURLConnection#getRequestMethod() * @return the HTTP request method */ public String getRequestMethod(); /** + * Whether we use a proxy + * * @see HttpURLConnection#usingProxy() * @return a boolean indicating if the connection is using a proxy. */ public boolean usingProxy(); /** + * Connect + * * @see HttpURLConnection#connect() - * @throws IOException + * @throws java.io.IOException */ public void connect() throws IOException; /** * Configure the connection so that it can be used for https communication. - * + * * @param km * the keymanager managing the key material used to authenticate * the local SSLSocket to its peer @@ -257,21 +328,22 @@ * whether credentials presented by a peer should be accepted. * @param random * the source of randomness for this generator or null. See - * {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} - * - * @throws NoSuchAlgorithmException - * @throws KeyManagementException + * {@link javax.net.ssl.SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} + * @throws java.security.NoSuchAlgorithmException + * @throws java.security.KeyManagementException */ public void configure(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException; /** - * Set the {@link HostnameVerifier} used during https communication + * Set the {@link javax.net.ssl.HostnameVerifier} used during https + * communication * * @param hostnameverifier - * @throws NoSuchAlgorithmException - * @throws KeyManagementException + * a {@link javax.net.ssl.HostnameVerifier} object. + * @throws java.security.NoSuchAlgorithmException + * @throws java.security.KeyManagementException */ public void setHostnameVerifier(HostnameVerifier hostnameverifier) throws NoSuchAlgorithmException, KeyManagementException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,15 +47,20 @@ import java.net.URL; /** - * A factory returning instances of {@link JDKHttpConnection} + * A factory returning instances of + * {@link org.eclipse.jgit.transport.http.JDKHttpConnection} * * @since 3.3 */ public class JDKHttpConnectionFactory implements HttpConnectionFactory { + /** {@inheritDoc} */ + @Override public HttpConnection create(URL url) throws IOException { return new JDKHttpConnection(url); } + /** {@inheritDoc} */ + @Override public HttpConnection create(URL url, Proxy proxy) throws IOException { return new JDKHttpConnection(url, proxy); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,8 +63,9 @@ import javax.net.ssl.TrustManager; /** - * A {@link HttpConnection} which simply delegates every call to a - * {@link HttpURLConnection}. This is the default implementation used by JGit + * A {@link org.eclipse.jgit.transport.http.HttpConnection} which simply + * delegates every call to a {@link java.net.HttpURLConnection}. This is the + * default implementation used by JGit * * @since 3.3 */ @@ -72,9 +73,12 @@ HttpURLConnection wrappedUrlConnection; /** + * Constructor for JDKHttpConnection. + * * @param url - * @throws MalformedURLException - * @throws IOException + * a {@link java.net.URL} object. + * @throws java.net.MalformedURLException + * @throws java.io.IOException */ protected JDKHttpConnection(URL url) throws MalformedURLException, @@ -83,10 +87,14 @@ } /** + * Constructor for JDKHttpConnection. + * * @param url + * a {@link java.net.URL} object. * @param proxy - * @throws MalformedURLException - * @throws IOException + * a {@link java.net.Proxy} object. + * @throws java.net.MalformedURLException + * @throws java.io.IOException */ protected JDKHttpConnection(URL url, Proxy proxy) throws MalformedURLException, IOException { @@ -94,95 +102,141 @@ .openConnection(proxy); } + /** {@inheritDoc} */ + @Override public int getResponseCode() throws IOException { return wrappedUrlConnection.getResponseCode(); } + /** {@inheritDoc} */ + @Override public URL getURL() { return wrappedUrlConnection.getURL(); } + /** {@inheritDoc} */ + @Override public String getResponseMessage() throws IOException { return wrappedUrlConnection.getResponseMessage(); } + /** {@inheritDoc} */ + @Override public Map> getHeaderFields() { return wrappedUrlConnection.getHeaderFields(); } + /** {@inheritDoc} */ + @Override public void setRequestProperty(String key, String value) { wrappedUrlConnection.setRequestProperty(key, value); } + /** {@inheritDoc} */ + @Override public void setRequestMethod(String method) throws ProtocolException { wrappedUrlConnection.setRequestMethod(method); } + /** {@inheritDoc} */ + @Override public void setUseCaches(boolean usecaches) { wrappedUrlConnection.setUseCaches(usecaches); } + /** {@inheritDoc} */ + @Override public void setConnectTimeout(int timeout) { wrappedUrlConnection.setConnectTimeout(timeout); } + /** {@inheritDoc} */ + @Override public void setReadTimeout(int timeout) { wrappedUrlConnection.setReadTimeout(timeout); } + /** {@inheritDoc} */ + @Override public String getContentType() { return wrappedUrlConnection.getContentType(); } + /** {@inheritDoc} */ + @Override public InputStream getInputStream() throws IOException { return wrappedUrlConnection.getInputStream(); } + /** {@inheritDoc} */ + @Override public String getHeaderField(String name) { return wrappedUrlConnection.getHeaderField(name); } + /** {@inheritDoc} */ + @Override public int getContentLength() { return wrappedUrlConnection.getContentLength(); } + /** {@inheritDoc} */ + @Override public void setInstanceFollowRedirects(boolean followRedirects) { wrappedUrlConnection.setInstanceFollowRedirects(followRedirects); } + /** {@inheritDoc} */ + @Override public void setDoOutput(boolean dooutput) { wrappedUrlConnection.setDoOutput(dooutput); } + /** {@inheritDoc} */ + @Override public void setFixedLengthStreamingMode(int contentLength) { wrappedUrlConnection.setFixedLengthStreamingMode(contentLength); } + /** {@inheritDoc} */ + @Override public OutputStream getOutputStream() throws IOException { return wrappedUrlConnection.getOutputStream(); } + /** {@inheritDoc} */ + @Override public void setChunkedStreamingMode(int chunklen) { wrappedUrlConnection.setChunkedStreamingMode(chunklen); } + /** {@inheritDoc} */ + @Override public String getRequestMethod() { return wrappedUrlConnection.getRequestMethod(); } + /** {@inheritDoc} */ + @Override public boolean usingProxy() { return wrappedUrlConnection.usingProxy(); } + /** {@inheritDoc} */ + @Override public void connect() throws IOException { wrappedUrlConnection.connect(); } + /** {@inheritDoc} */ + @Override public void setHostnameVerifier(HostnameVerifier hostnameverifier) { ((HttpsURLConnection) wrappedUrlConnection) .setHostnameVerifier(hostnameverifier); } + /** {@inheritDoc} */ + @Override public void configure(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,18 +43,20 @@ package org.eclipse.jgit.transport; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Random; @@ -149,9 +151,12 @@ * * @param conn * the connection that failed. + * @param ignoreTypes + * authentication types to be ignored. * @return new authentication method to try. */ - static HttpAuthMethod scanResponse(final HttpConnection conn) { + static HttpAuthMethod scanResponse(final HttpConnection conn, + Collection ignoreTypes) { final Map> headers = conn.getHeaderFields(); HttpAuthMethod authentication = Type.NONE.method(EMPTY_STRING); @@ -164,7 +169,14 @@ SCHEMA_NAME_SEPARATOR, 2); try { - Type methodType = Type.valueOf(valuePart[0].toUpperCase()); + Type methodType = Type.valueOf( + valuePart[0].toUpperCase(Locale.ROOT)); + + if ((ignoreTypes != null) + && (ignoreTypes.contains(methodType))) { + continue; + } + if (authentication.getType().compareTo(methodType) >= 0) { continue; } @@ -192,6 +204,12 @@ protected final Type type; + /** + * Constructor for HttpAuthMethod. + * + * @param type + * authentication method type + */ protected HttpAuthMethod(Type type) { this.type = type; } @@ -219,7 +237,8 @@ if (credentialsProvider.supports(u, p) && credentialsProvider.get(uri, u, p)) { username = u.getValue(); - password = new String(p.getValue()); + char[] v = p.getValue(); + password = (v == null) ? null : new String(p.getValue()); p.clear(); } else return false; @@ -296,7 +315,7 @@ @Override void configureRequest(final HttpConnection conn) throws IOException { String ident = user + ":" + pass; //$NON-NLS-1$ - String enc = Base64.encodeBytes(ident.getBytes("UTF-8")); //$NON-NLS-1$ + String enc = Base64.encodeBytes(ident.getBytes(UTF_8)); conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName() + " " + enc); //$NON-NLS-1$ } @@ -335,7 +354,7 @@ @SuppressWarnings("boxing") @Override void configureRequest(final HttpConnection conn) throws IOException { - final Map r = new LinkedHashMap(); + final Map r = new LinkedHashMap<>(); final String realm = params.get("realm"); //$NON-NLS-1$ final String nonce = params.get("nonce"); //$NON-NLS-1$ @@ -410,25 +429,17 @@ } private static String H(String data) { - try { - MessageDigest md = newMD5(); - md.update(data.getBytes("UTF-8")); //$NON-NLS-1$ - return LHEX(md.digest()); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not available", e); //$NON-NLS-1$ - } + MessageDigest md = newMD5(); + md.update(data.getBytes(UTF_8)); + return LHEX(md.digest()); } private static String KD(String secret, String data) { - try { - MessageDigest md = newMD5(); - md.update(secret.getBytes("UTF-8")); //$NON-NLS-1$ - md.update((byte) ':'); - md.update(data.getBytes("UTF-8")); //$NON-NLS-1$ - return LHEX(md.digest()); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not available", e); //$NON-NLS-1$ - } + MessageDigest md = newMD5(); + md.update(secret.getBytes(UTF_8)); + md.update((byte) ':'); + md.update(data.getBytes(UTF_8)); + return LHEX(md.digest()); } private static MessageDigest newMD5() { @@ -454,7 +465,7 @@ } private static Map parse(String auth) { - Map p = new HashMap(); + Map p = new HashMap<>(); int next = 0; while (next < auth.length()) { if (next < auth.length() && auth.charAt(next) == ',') { @@ -529,7 +540,7 @@ GSSManager gssManager = GSS_MANAGER_FACTORY.newInstance(conn .getURL()); String host = conn.getURL().getHost(); - String peerName = "HTTP@" + host.toLowerCase(); //$NON-NLS-1$ + String peerName = "HTTP@" + host.toLowerCase(Locale.ROOT); //$NON-NLS-1$ try { GSSName gssName = gssManager.createName(peerName, GSSName.NT_HOSTBASED_SERVICE); @@ -544,9 +555,7 @@ conn.setRequestProperty(HDR_AUTHORIZATION, getType().getSchemeName() + " " + Base64.encodeBytes(token)); //$NON-NLS-1$ } catch (GSSException e) { - IOException ioe = new IOException(); - ioe.initCause(e); - throw ioe; + throw new IOException(e); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2008, 2010, Google Inc. + * Copyright (C) 2017, Thomas Wolf + * 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; +import java.net.URISyntaxException; +import java.text.MessageFormat; +import java.util.Set; +import java.util.function.Supplier; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.StringUtils; +import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A representation of the "http.*" config values in a git + * {@link org.eclipse.jgit.lib.Config}. git provides for setting values for + * specific URLs through "http.<url>.*" subsections. git always considers + * only the initial original URL for such settings, not any redirected URL. + * + * @since 4.9 + */ +public class HttpConfig { + + private static final Logger LOG = LoggerFactory.getLogger(HttpConfig.class); + + private static final String FTP = "ftp"; //$NON-NLS-1$ + + /** git config section key for http settings. */ + public static final String HTTP = "http"; //$NON-NLS-1$ + + /** git config key for the "followRedirects" setting. */ + public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$ + + /** git config key for the "maxRedirects" setting. */ + public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$ + + /** git config key for the "postBuffer" setting. */ + public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$ + + /** git config key for the "sslVerify" setting. */ + public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$ + + private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$ + + private static final int DEFAULT_MAX_REDIRECTS = 5; + + private static final int MAX_REDIRECTS = (new Supplier() { + + @Override + public Integer get() { + String rawValue = SystemReader.getInstance() + .getProperty(MAX_REDIRECT_SYSTEM_PROPERTY); + Integer value = Integer.valueOf(DEFAULT_MAX_REDIRECTS); + if (rawValue != null) { + try { + value = Integer.valueOf(Integer.parseUnsignedInt(rawValue)); + } catch (NumberFormatException e) { + LOG.warn(MessageFormat.format( + JGitText.get().invalidSystemProperty, + MAX_REDIRECT_SYSTEM_PROPERTY, rawValue, value)); + } + } + return value; + } + }).get().intValue(); + + /** + * Config values for http.followRedirect. + */ + public enum HttpRedirectMode implements Config.ConfigEnum { + + /** Always follow redirects (up to the http.maxRedirects limit). */ + TRUE("true"), //$NON-NLS-1$ + /** + * Only follow redirects on the initial GET request. This is the + * default. + */ + INITIAL("initial"), //$NON-NLS-1$ + /** Never follow redirects. */ + FALSE("false"); //$NON-NLS-1$ + + private final String configValue; + + private HttpRedirectMode(String configValue) { + this.configValue = configValue; + } + + @Override + public String toConfigValue() { + return configValue; + } + + @Override + public boolean matchConfigValue(String s) { + return configValue.equals(s); + } + } + + private int postBuffer; + + private boolean sslVerify; + + private HttpRedirectMode followRedirects; + + private int maxRedirects; + + /** + * Get the "http.postBuffer" setting + * + * @return the value of the "http.postBuffer" setting + */ + public int getPostBuffer() { + return postBuffer; + } + + /** + * Get the "http.sslVerify" setting + * + * @return the value of the "http.sslVerify" setting + */ + public boolean isSslVerify() { + return sslVerify; + } + + /** + * Get the "http.followRedirects" setting + * + * @return the value of the "http.followRedirects" setting + */ + public HttpRedirectMode getFollowRedirects() { + return followRedirects; + } + + /** + * Get the "http.maxRedirects" setting + * + * @return the value of the "http.maxRedirects" setting + */ + public int getMaxRedirects() { + return maxRedirects; + } + + /** + * Creates a new {@link org.eclipse.jgit.transport.HttpConfig} tailored to + * the given {@link org.eclipse.jgit.transport.URIish}. + * + * @param config + * to read the {@link org.eclipse.jgit.transport.HttpConfig} from + * @param uri + * to get the configuration values for + */ + public HttpConfig(Config config, URIish uri) { + init(config, uri); + } + + /** + * Creates a {@link org.eclipse.jgit.transport.HttpConfig} that reads values + * solely from the user config. + * + * @param uri + * to get the configuration values for + */ + public HttpConfig(URIish uri) { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + try { + userConfig.load(); + } catch (IOException | ConfigInvalidException e) { + // Log it and then work with default values. + LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid, + userConfig.getFile().getAbsolutePath(), e)); + init(new Config(), uri); + return; + } + init(userConfig, uri); + } + + private void init(Config config, URIish uri) { + // Set defaults from the section first + int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY, + 1 * 1024 * 1024); + boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true); + HttpRedirectMode followRedirectsMode = config.getEnum( + HttpRedirectMode.values(), HTTP, null, + FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL); + int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY, + MAX_REDIRECTS); + if (redirectLimit < 0) { + redirectLimit = MAX_REDIRECTS; + } + String match = findMatch(config.getSubsections(HTTP), uri); + if (match != null) { + // Override with more specific items + postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY, + postBufferSize); + sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY, + sslVerifyFlag); + followRedirectsMode = config.getEnum(HttpRedirectMode.values(), + HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode); + int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY, + redirectLimit); + if (newMaxRedirects >= 0) { + redirectLimit = newMaxRedirects; + } + } + postBuffer = postBufferSize; + sslVerify = sslVerifyFlag; + followRedirects = followRedirectsMode; + maxRedirects = redirectLimit; + } + + /** + * Determines the best match from a set of subsection names (representing + * prefix URLs) for the given {@link URIish}. + * + * @param names + * to match against the {@code uri} + * @param uri + * to find a match for + * @return the best matching subsection name, or {@code null} if no + * subsection matches + */ + private String findMatch(Set names, URIish uri) { + String bestMatch = null; + int bestMatchLength = -1; + boolean withUser = false; + String uPath = uri.getPath(); + boolean hasPath = !StringUtils.isEmptyOrNull(uPath); + if (hasPath) { + uPath = normalize(uPath); + if (uPath == null) { + // Normalization failed; warning was logged. + return null; + } + } + for (String s : names) { + try { + URIish candidate = new URIish(s); + // Scheme and host must match case-insensitively + if (!compare(uri.getScheme(), candidate.getScheme()) + || !compare(uri.getHost(), candidate.getHost())) { + continue; + } + // Ports must match after default ports have been substituted + if (defaultedPort(uri.getPort(), + uri.getScheme()) != defaultedPort(candidate.getPort(), + candidate.getScheme())) { + continue; + } + // User: if present in candidate, must match + boolean hasUser = false; + if (candidate.getUser() != null) { + if (!candidate.getUser().equals(uri.getUser())) { + continue; + } + hasUser = true; + } + // Path: prefix match, longer is better + String cPath = candidate.getPath(); + int matchLength = -1; + if (StringUtils.isEmptyOrNull(cPath)) { + matchLength = 0; + } else { + if (!hasPath) { + continue; + } + // Paths can match only on segments + matchLength = segmentCompare(uPath, cPath); + if (matchLength < 0) { + continue; + } + } + // A longer path match is always preferred even over a user + // match. If the path matches are equal, a match with user wins + // over a match without user. + if (matchLength > bestMatchLength || !withUser && hasUser + && matchLength >= 0 && matchLength == bestMatchLength) { + bestMatch = s; + bestMatchLength = matchLength; + withUser = hasUser; + } + } catch (URISyntaxException e) { + LOG.warn(MessageFormat + .format(JGitText.get().httpConfigInvalidURL, s)); + } + } + return bestMatch; + } + + private boolean compare(String a, String b) { + if (a == null) { + return b == null; + } + return a.equalsIgnoreCase(b); + } + + private int defaultedPort(int port, String scheme) { + if (port >= 0) { + return port; + } + if (FTP.equalsIgnoreCase(scheme)) { + return 21; + } else if (HTTP.equalsIgnoreCase(scheme)) { + return 80; + } else { + return 443; // https + } + } + + static int segmentCompare(String uriPath, String m) { + // Precondition: !uriPath.isEmpty() && !m.isEmpty(),and u must already + // be normalized + String matchPath = normalize(m); + if (matchPath == null || !uriPath.startsWith(matchPath)) { + return -1; + } + // We can match only on a segment boundary: either both paths are equal, + // or if matchPath does not end in '/', there is a '/' in uriPath right + // after the match. + int uLength = uriPath.length(); + int mLength = matchPath.length(); + if (mLength == uLength || matchPath.charAt(mLength - 1) == '/' + || mLength < uLength && uriPath.charAt(mLength) == '/') { + return mLength; + } + return -1; + } + + static String normalize(String path) { + // C-git resolves . and .. segments + int i = 0; + int length = path.length(); + StringBuilder builder = new StringBuilder(length); + builder.append('/'); + if (length > 0 && path.charAt(0) == '/') { + i = 1; + } + while (i < length) { + int slash = path.indexOf('/', i); + if (slash < 0) { + slash = length; + } + if (slash == i || slash == i + 1 && path.charAt(i) == '.') { + // Skip /. or also double slashes + } else if (slash == i + 2 && path.charAt(i) == '.' + && path.charAt(i + 1) == '.') { + // Remove previous segment if we have "/.." + int l = builder.length() - 2; // Skip terminating slash. + while (l >= 0 && builder.charAt(l) != '/') { + l--; + } + if (l < 0) { + LOG.warn(MessageFormat.format( + JGitText.get().httpConfigCannotNormalizeURL, path)); + return null; + } + builder.setLength(l + 1); + } else { + // Include the slash, if any + builder.append(path, i, Math.min(length, slash + 1)); + } + i = slash + 1; + } + if (builder.length() > 1 && builder.charAt(builder.length() - 1) == '/' + && length > 0 && path.charAt(length - 1) != '/') { + // . or .. normalization left a trailing slash when the original + // path had none at the end + builder.setLength(builder.length() - 1); + } + return builder.toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,7 +62,11 @@ protected static HttpConnectionFactory connectionFactory = new JDKHttpConnectionFactory(); /** - * @return the {@link HttpConnectionFactory} used to create new connections + * Get the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} + * used to create new connections + * + * @return the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} + * used to create new connections * @since 3.3 */ public static HttpConnectionFactory getConnectionFactory() { @@ -70,10 +74,11 @@ } /** - * Set the {@link HttpConnectionFactory} to be used to create new - * connections + * Set the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} to + * be used to create new connections * * @param cf + * connection factory * @since 3.3 */ public static void setConnectionFactory(HttpConnectionFactory cf) { @@ -98,7 +103,7 @@ /** * Create a minimal HTTP transport instance not tied to a single repository. * - * @param uri + * @param uri a {@link org.eclipse.jgit.transport.URIish} object. */ protected HttpTransport(URIish uri) { super(uri); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InsecureCipherFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InsecureCipherFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InsecureCipherFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InsecureCipherFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016, 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.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +/** + * DO NOT USE Factory to create any cipher. + *

      + * This is a hack for {@link WalkEncryption} to create any cipher configured by + * the end-user. Using this class allows JGit to violate ErrorProne's security + * recommendations (InsecureCryptoUsage), which is not secure. + */ +class InsecureCipherFactory { + static Cipher create(String algo) + throws NoSuchAlgorithmException, NoSuchPaddingException { + return Cipher.getInstance(algo); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,6 +57,21 @@ class InternalFetchConnection extends BasePackFetchConnection { private Thread worker; + /** + * Constructor for InternalFetchConnection. + * + * @param transport + * a {@link org.eclipse.jgit.transport.PackTransport} + * @param uploadPackFactory + * a + * {@link org.eclipse.jgit.transport.resolver.UploadPackFactory} + * @param req + * request + * @param remote + * the remote {@link org.eclipse.jgit.lib.Repository} + * @throws org.eclipse.jgit.errors.TransportException + * if any. + */ public InternalFetchConnection(PackTransport transport, final UploadPackFactory uploadPackFactory, final C req, final Repository remote) throws TransportException { @@ -87,6 +102,7 @@ } worker = new Thread("JGit-Upload-Pack") { //$NON-NLS-1$ + @Override public void run() { try { final UploadPack rp = uploadPackFactory.create(req, remote); @@ -124,6 +140,7 @@ readAdvertisedRefs(); } + /** {@inheritDoc} */ @Override public void close() { super.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,6 +57,21 @@ class InternalPushConnection extends BasePackPushConnection { private Thread worker; + /** + * Constructor for InternalPushConnection. + * + * @param transport + * a {@link org.eclipse.jgit.transport.PackTransport} + * @param receivePackFactory + * a + * {@link org.eclipse.jgit.transport.resolver.ReceivePackFactory} + * @param req + * a request + * @param remote + * the {@link org.eclipse.jgit.lib.Repository} + * @throws org.eclipse.jgit.errors.TransportException + * if any. + */ public InternalPushConnection(PackTransport transport, final ReceivePackFactory receivePackFactory, final C req, final Repository remote) throws TransportException { @@ -79,6 +94,7 @@ } worker = new Thread("JGit-Receive-Pack") { //$NON-NLS-1$ + @Override public void run() { try { final ReceivePack rp = receivePackFactory.create(req, remote); @@ -114,6 +130,7 @@ readAdvertisedRefs(); } + /** {@inheritDoc} */ @Override public void close() { super.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016, Mark Ingram * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2009, Google, Inc. @@ -52,19 +53,26 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.ConnectException; import java.net.UnknownHostException; +import java.text.MessageFormat; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.FS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.jcraft.jsch.ConfigRepository; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; -import com.jcraft.jsch.UserInfo; /** * The base session factory that loads known hosts and private keys from @@ -75,16 +83,29 @@ * used by C Git. *

      * The factory does not provide UI behavior. Override the method - * {@link #configure(org.eclipse.jgit.transport.OpenSshConfig.Host, Session)} - * to supply appropriate {@link UserInfo} to the session. + * {@link #configure(org.eclipse.jgit.transport.OpenSshConfig.Host, Session)} to + * supply appropriate {@link com.jcraft.jsch.UserInfo} to the session. */ public abstract class JschConfigSessionFactory extends SshSessionFactory { - private final Map byIdentityFile = new HashMap(); + + private static final Logger LOG = LoggerFactory + .getLogger(JschConfigSessionFactory.class); + + /** + * We use different Jsch instances for hosts that have an IdentityFile + * configured in ~/.ssh/config. Jsch by default would cache decrypted keys + * only per session, which results in repeated password prompts. Using + * different Jsch instances, we can cache the keys on these instances so + * that they will be re-used for successive sessions, and thus the user is + * prompted for a key password only once while Eclipse runs. + */ + private final Map byIdentityFile = new HashMap<>(); private JSch defaultJSch; private OpenSshConfig config; + /** {@inheritDoc} */ @Override public synchronized RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) @@ -100,7 +121,6 @@ config = OpenSshConfig.get(fs); final OpenSshConfig.Host hc = config.lookup(host); - host = hc.getHostName(); if (port <= 0) port = hc.getPort(); if (user == null) @@ -152,10 +172,13 @@ } catch (JSchException je) { final Throwable c = je.getCause(); - if (c instanceof UnknownHostException) - throw new TransportException(uri, JGitText.get().unknownHost); - if (c instanceof ConnectException) - throw new TransportException(uri, c.getMessage()); + if (c instanceof UnknownHostException) { + throw new TransportException(uri, JGitText.get().unknownHost, + je); + } + if (c instanceof ConnectException) { + throw new TransportException(uri, c.getMessage(), je); + } throw new TransportException(uri, je.getMessage(), je); } @@ -169,10 +192,18 @@ return e.getCause() == null && e.getMessage().equals("Auth cancel"); //$NON-NLS-1$ } - private Session createSession(CredentialsProvider credentialsProvider, + // Package visibility for tests + Session createSession(CredentialsProvider credentialsProvider, FS fs, String user, final String pass, String host, int port, final OpenSshConfig.Host hc) throws JSchException { final Session session = createSession(hc, user, host, port, fs); + // Jsch will have overridden the explicit user by the one from the SSH + // config file... + setUserName(session, user); + // Jsch will also have overridden the port. + if (port > 0 && port != session.getPort()) { + session.setPort(port); + } // We retry already in getSession() method. JSch must not retry // on its own. session.setConfig("MaxAuthTries", "1"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -195,6 +226,28 @@ return session; } + private void setUserName(Session session, String userName) { + // Jsch 0.1.54 picks up the user name from the ssh config, even if an + // explicit user name was given! We must correct that if ~/.ssh/config + // has a different user name. + if (userName == null || userName.isEmpty() + || userName.equals(session.getUserName())) { + return; + } + try { + Class[] parameterTypes = { String.class }; + Method method = Session.class.getDeclaredMethod("setUserName", //$NON-NLS-1$ + parameterTypes); + method.setAccessible(true); + method.invoke(session, userName); + } catch (NullPointerException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + LOG.error(MessageFormat.format(JGitText.get().sshUserNameError, + userName, session.getUserName()), e); + } + } + /** * Create a new remote session for the requested address. * @@ -210,7 +263,7 @@ * the file system abstraction which will be necessary to * perform certain file system operations. * @return new session instance, but otherwise unconfigured. - * @throws JSchException + * @throws com.jcraft.jsch.JSchException * the session could not be created. */ protected Session createSession(final OpenSshConfig.Host hc, @@ -220,8 +273,22 @@ } /** + * Provide additional configuration for the JSch instance. This method could + * be overridden to supply a preferred + * {@link com.jcraft.jsch.IdentityRepository}. + * + * @param jsch + * jsch instance + * @since 4.5 + */ + protected void configureJSch(JSch jsch) { + // No additional configuration required. + } + + /** * Provide additional configuration for the session based on the host - * information. This method could be used to supply {@link UserInfo}. + * information. This method could be used to supply + * {@link com.jcraft.jsch.UserInfo}. * * @param hc * host configuration @@ -239,12 +306,16 @@ * the file system abstraction which will be necessary to * perform certain file system operations. * @return the JSch instance to use. - * @throws JSchException + * @throws com.jcraft.jsch.JSchException * the user configuration could not be created. */ protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException { if (defaultJSch == null) { defaultJSch = createDefaultJSch(fs); + if (defaultJSch.getConfigRepository() == null) { + defaultJSch.setConfigRepository( + new JschBugFixingConfigRepository(config)); + } for (Object name : defaultJSch.getIdentityNames()) byIdentityFile.put((String) name, defaultJSch); } @@ -257,6 +328,10 @@ JSch jsch = byIdentityFile.get(identityKey); if (jsch == null) { jsch = new JSch(); + configureJSch(jsch); + if (jsch.getConfigRepository() == null) { + jsch.setConfigRepository(defaultJSch.getConfigRepository()); + } jsch.setHostKeyRepository(defaultJSch.getHostKeyRepository()); jsch.addIdentity(identityKey); byIdentityFile.put(identityKey, jsch); @@ -265,15 +340,18 @@ } /** + * Create default instance of jsch + * * @param fs - * the file system abstraction which will be necessary to - * perform certain file system operations. + * the file system abstraction which will be necessary to perform + * certain file system operations. * @return the new default JSch implementation. - * @throws JSchException + * @throws com.jcraft.jsch.JSchException * known host keys cannot be loaded. */ protected JSch createDefaultJSch(FS fs) throws JSchException { final JSch jsch = new JSch(); + configureJSch(jsch); knownHosts(jsch, fs); identities(jsch, fs); return jsch; @@ -284,13 +362,8 @@ if (home == null) return; final File known_hosts = new File(new File(home, ".ssh"), "known_hosts"); //$NON-NLS-1$ //$NON-NLS-2$ - try { - final FileInputStream in = new FileInputStream(known_hosts); - try { - sch.setKnownHosts(in); - } finally { - in.close(); - } + try (FileInputStream in = new FileInputStream(known_hosts)) { + sch.setKnownHosts(in); } catch (FileNotFoundException none) { // Oh well. They don't have a known hosts in home. } catch (IOException err) { @@ -319,4 +392,101 @@ } } } + + private static class JschBugFixingConfigRepository + implements ConfigRepository { + + private final ConfigRepository base; + + public JschBugFixingConfigRepository(ConfigRepository base) { + this.base = base; + } + + @Override + public Config getConfig(String host) { + return new JschBugFixingConfig(base.getConfig(host)); + } + + /** + * A {@link com.jcraft.jsch.ConfigRepository.Config} that transforms + * some values from the config file into the format Jsch 0.1.54 expects. + * This is a work-around for bugs in Jsch. + *

      + * Additionally, this config hides the IdentityFile config entries from + * Jsch; we manage those ourselves. Otherwise Jsch would cache passwords + * (or rather, decrypted keys) only for a single session, resulting in + * multiple password prompts for user operations that use several Jsch + * sessions. + */ + private static class JschBugFixingConfig implements Config { + + private static final String[] NO_IDENTITIES = {}; + + private final Config real; + + public JschBugFixingConfig(Config delegate) { + real = delegate; + } + + @Override + public String getHostname() { + return real.getHostname(); + } + + @Override + public String getUser() { + return real.getUser(); + } + + @Override + public int getPort() { + return real.getPort(); + } + + @Override + public String getValue(String key) { + String k = key.toUpperCase(Locale.ROOT); + if ("IDENTITYFILE".equals(k)) { //$NON-NLS-1$ + return null; + } + String result = real.getValue(key); + if (result != null) { + if ("SERVERALIVEINTERVAL".equals(k) //$NON-NLS-1$ + || "CONNECTTIMEOUT".equals(k)) { //$NON-NLS-1$ + // These values are in seconds. Jsch 0.1.54 passes them + // on as is to java.net.Socket.setSoTimeout(), which + // expects milliseconds. So convert here to + // milliseconds. + try { + int timeout = Integer.parseInt(result); + result = Long.toString( + TimeUnit.SECONDS.toMillis(timeout)); + } catch (NumberFormatException e) { + // Ignore + } + } + } + return result; + } + + @Override + public String[] getValues(String key) { + String k = key.toUpperCase(Locale.ROOT); + if ("IDENTITYFILE".equals(k)) { //$NON-NLS-1$ + return NO_IDENTITIES; + } + return real.getValues(key); + } + } + } + + /** + * Set the {@link OpenSshConfig} to use. Intended for use in tests. + * + * @param config + * to use + */ + void setConfig(OpenSshConfig config) { + this.config = config; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,15 +48,14 @@ package org.eclipse.jgit.transport; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.io.StreamCopyThread; +import org.eclipse.jgit.util.io.IsolatedOutputStream; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; @@ -67,12 +66,12 @@ * Run remote commands using Jsch. *

      * This class is the default session implementation using Jsch. Note that - * {@link JschConfigSessionFactory} is used to create the actual session passed - * to the constructor. + * {@link org.eclipse.jgit.transport.JschConfigSessionFactory} is used to create + * the actual session passed to the constructor. */ public class JschSession implements RemoteSession { - private final Session sock; - private final URIish uri; + final Session sock; + final URIish uri; /** * Create a new session object by passing the real Jsch session and the URI @@ -88,22 +87,27 @@ this.uri = uri; } + /** {@inheritDoc} */ + @Override public Process exec(String command, int timeout) throws IOException { return new JschProcess(command, timeout); } + /** {@inheritDoc} */ + @Override public void disconnect() { if (sock.isConnected()) sock.disconnect(); } /** - * A kludge to allow {@link TransportSftp} to get an Sftp channel from Jsch. - * Ideally, this method would be generic, which would require implementing - * generic Sftp channel operations in the RemoteSession class. + * A kludge to allow {@link org.eclipse.jgit.transport.TransportSftp} to get + * an Sftp channel from Jsch. Ideally, this method would be generic, which + * would require implementing generic Sftp channel operations in the + * RemoteSession class. * * @return a channel suitable for Sftp operations. - * @throws JSchException + * @throws com.jcraft.jsch.JSchException * on problems getting the channel. */ public Channel getSftpChannel() throws JSchException { @@ -119,7 +123,7 @@ private class JschProcess extends Process { private ChannelExec channel; - private final int timeout; + final int timeout; private InputStream inputStream; @@ -141,7 +145,7 @@ * @throws IOException * on problems opening streams */ - private JschProcess(final String commandName, int tms) + JschProcess(final String commandName, int tms) throws TransportException, IOException { timeout = tms; try { @@ -149,14 +153,27 @@ channel.setCommand(commandName); setupStreams(); channel.connect(timeout > 0 ? timeout * 1000 : 0); - if (!channel.isConnected()) + if (!channel.isConnected()) { + closeOutputStream(); throw new TransportException(uri, JGitText.get().connectionFailed); + } } catch (JSchException e) { + closeOutputStream(); throw new TransportException(uri, e.getMessage(), e); } } + private void closeOutputStream() { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ioe) { + // ignore + } + } + } + private void setupStreams() throws IOException { inputStream = channel.getInputStream(); @@ -165,33 +182,12 @@ // that we spawn a background thread to shuttle data through a pipe, // as we can issue an interrupted write out of that. Its slower, so // we only use this route if there is a timeout. - final OutputStream out = channel.getOutputStream(); + OutputStream out = channel.getOutputStream(); if (timeout <= 0) { outputStream = out; } else { - final PipedInputStream pipeIn = new PipedInputStream(); - final StreamCopyThread copier = new StreamCopyThread(pipeIn, - out); - final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) { - @Override - public void flush() throws IOException { - super.flush(); - copier.flush(); - } - - @Override - public void close() throws IOException { - super.close(); - try { - copier.join(timeout * 1000); - } catch (InterruptedException e) { - // Just wake early, the thread will terminate - // anyway. - } - } - }; - copier.start(); - outputStream = pipeOut; + IsolatedOutputStream i = new IsolatedOutputStream(out); + outputStream = new BufferedOutputStream(i, 16 * 1024); } errStream = channel.getErrStream(); @@ -227,6 +223,7 @@ public void destroy() { if (channel.isConnected()) channel.disconnect(); + closeOutputStream(); } @Override @@ -236,4 +233,4 @@ return exitValue(); } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2009, 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; - -/** - * Simple Map helper for {@link PackParser}. - * - * @param - * type of the value instance. - */ -final class LongMap { - private static final float LOAD_FACTOR = 0.75f; - - private Node[] table; - - /** Number of entries currently in the map. */ - private int size; - - /** Next {@link #size} to trigger a {@link #grow()}. */ - private int growAt; - - LongMap() { - table = createArray(64); - growAt = (int) (table.length * LOAD_FACTOR); - } - - boolean containsKey(final long key) { - return get(key) != null; - } - - V get(final long key) { - for (Node n = table[index(key)]; n != null; n = n.next) { - if (n.key == key) - return n.value; - } - return null; - } - - V remove(final long key) { - Node n = table[index(key)]; - Node prior = null; - while (n != null) { - if (n.key == key) { - if (prior == null) - table[index(key)] = n.next; - else - prior.next = n.next; - size--; - return n.value; - } - prior = n; - n = n.next; - } - return null; - } - - V put(final long key, final V value) { - for (Node n = table[index(key)]; n != null; n = n.next) { - if (n.key == key) { - final V o = n.value; - n.value = value; - return o; - } - } - - if (++size == growAt) - grow(); - insert(new Node(key, value)); - return null; - } - - private void insert(final Node n) { - final int idx = index(n.key); - n.next = table[idx]; - table[idx] = n; - } - - private void grow() { - final Node[] oldTable = table; - final int oldSize = table.length; - - table = createArray(oldSize << 1); - growAt = (int) (table.length * LOAD_FACTOR); - for (int i = 0; i < oldSize; i++) { - Node e = oldTable[i]; - while (e != null) { - final Node n = e.next; - insert(e); - e = n; - } - } - } - - private final int index(final long key) { - int h = ((int) key) >>> 1; - h ^= (h >>> 20) ^ (h >>> 12); - return h & (table.length - 1); - } - - @SuppressWarnings("unchecked") - private static final Node[] createArray(final int sz) { - return new Node[sz]; - } - - private static class Node { - final long key; - - V value; - - Node next; - - Node(final long k, final V v) { - key = k; - value = v; - } - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,7 +54,9 @@ NetRC netrc = new NetRC(); - /** */ + /** + *

      Constructor for NetRCCredentialsProvider.

      + */ public NetRCCredentialsProvider() { } @@ -65,6 +67,7 @@ CredentialsProvider.setDefault(new NetRCCredentialsProvider()); } + /** {@inheritDoc} */ @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { @@ -78,6 +81,7 @@ return true; } + /** {@inheritDoc} */ @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { @@ -105,12 +109,12 @@ throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); //$NON-NLS-1$ } - return true; + return !isAnyNull(items); } + /** {@inheritDoc} */ @Override public boolean isInteractive() { return false; } - } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ import java.io.IOException; import java.util.Collection; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; @@ -124,7 +125,7 @@ private long lastModified; - private Map hosts = new HashMap(); + private Map hosts = new HashMap<>(); private static final TreeMap STATE = new TreeMap() { private static final long serialVersionUID = -4285910831814853334L; @@ -142,7 +143,9 @@ COMMAND, MACHINE, LOGIN, PASSWORD, DEFAULT, ACCOUNT, MACDEF } - /** */ + /** + *

      Constructor for NetRC.

      + */ public NetRC() { netrc = getDefaultFile(); if (netrc != null) @@ -150,6 +153,8 @@ } /** + *

      Constructor for NetRC.

      + * * @param netrc * the .netrc file */ @@ -175,6 +180,7 @@ * Get entry by host name * * @param host + * the host name * @return entry associated with host name or null */ public NetRCEntry getEntry(String host) { @@ -193,6 +199,8 @@ } /** + * Get all entries collected from .netrc file + * * @return all entries collected from .netrc file */ public Collection getEntries() { @@ -230,7 +238,7 @@ matcher.reset(line); while (matcher.find()) { - String command = matcher.group().toLowerCase(); + String command = matcher.group().toLowerCase(Locale.ROOT); if (command.startsWith("#")) { //$NON-NLS-1$ matcher.reset(""); //$NON-NLS-1$ continue; @@ -316,4 +324,4 @@ } } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,6 +55,8 @@ public interface NonceGenerator { /** + * Create nonce to be signed by the pusher + * * @param db * The repository which should be used to obtain a unique String * such that the pusher cannot forge nonces by pushing to another @@ -62,12 +64,14 @@ * @param timestamp * The current time in seconds. * @return The nonce to be signed by the pusher - * @throws IllegalStateException + * @throws java.lang.IllegalStateException */ public String createNonce(Repository db, long timestamp) throws IllegalStateException; /** + * Verify trustworthiness of the received nonce. + * * @param received * The nonce which was received from the server * @param sent @@ -76,7 +80,6 @@ * The repository which should be used to obtain a unique String * such that the pusher cannot forge nonces by pushing to another * repository at the same time as well and reusing the nonce. - * * @param allowSlop * If the receiving backend is is able to generate slop. This is * the case for serving via http protocol using more than one @@ -85,7 +88,6 @@ * @param slop * If `allowSlop` is true, this specifies the number of seconds * which we allow as slop. - * * @return a NonceStatus indicating the trustworthiness of the received * nonce. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,6 @@ import java.io.OutputStream; -import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.ProgressMonitor; /** @@ -55,19 +54,20 @@ */ public interface ObjectCountCallback { /** - * Invoked when the {@link PackWriter} has counted the objects to be - * written to pack. + * Invoked when the + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} has counted the + * objects to be written to pack. *

      - * An {@code ObjectCountCallback} can use this information to decide - * whether the - * {@link PackWriter#writePack(ProgressMonitor, ProgressMonitor, OutputStream)} + * An {@code ObjectCountCallback} can use this information to decide whether + * the + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#writePack(ProgressMonitor, ProgressMonitor, OutputStream)} * operation should be aborted. *

      * This callback will be called exactly once. * * @param objectCount * the count of the objects. - * @throws WriteAbortedException + * @throws org.eclipse.jgit.transport.WriteAbortedException * to indicate that the write operation should be aborted. */ void setObjectCount(long objectCount) throws WriteAbortedException; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2014, Google Inc. + * Copyright (C) 2008, 2017, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -46,32 +46,92 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Set; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.StringUtils; +import org.eclipse.jgit.util.SystemReader; + +import com.jcraft.jsch.ConfigRepository; /** - * Simple configuration parser for the OpenSSH ~/.ssh/config file. + * Fairly complete configuration parser for the OpenSSH ~/.ssh/config file. + *

      + * JSch does have its own config file parser + * {@link com.jcraft.jsch.OpenSSHConfig} since version 0.1.50, but it has a + * number of problems: + *

        + *
      • it splits lines of the format "keyword = value" wrongly: you'd end up + * with the value "= value". + *
      • its "Host" keyword is not case insensitive. + *
      • it doesn't handle quoted values. + *
      • JSch's OpenSSHConfig doesn't monitor for config file changes. + *
      + *

      + * Therefore implement our own parser to read an OpenSSH configuration file. It + * makes the critical options available to + * {@link org.eclipse.jgit.transport.SshSessionFactory} via + * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} objects returned by + * {@link #lookup(String)}, and implements a fully conforming + * {@link com.jcraft.jsch.ConfigRepository} providing + * {@link com.jcraft.jsch.ConfigRepository.Config}s via + * {@link #getConfig(String)}. + *

      + *

      + * Limitations compared to the full OpenSSH 7.5 parser: + *

      + *
        + *
      • This parser does not handle Match or Include keywords. + *
      • This parser does not do host name canonicalization (Jsch ignores it + * anyway). + *
      + *

      + * Note that OpenSSH's readconf.c is a validating parser; Jsch's + * ConfigRepository OTOH treats all option values as plain strings, so any + * validation must happen in Jsch outside of the parser. Thus this parser does + * not validate option values, except for a few options when constructing a + * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} object. + *

      + *

      + * This config does %-substitutions for the following tokens: + *

      + *
        + *
      • %% - single % + *
      • %C - short-hand for %l%h%p%r. See %p and %r below; the replacement may be + * done partially only and may leave %p or %r or both unreplaced. + *
      • %d - home directory path + *
      • %h - remote host name + *
      • %L - local host name without domain + *
      • %l - FQDN of the local host + *
      • %n - host name as specified in {@link #lookup(String)} + *
      • %p - port number; replaced only if set in the config + *
      • %r - remote user name; replaced only if set in the config + *
      • %u - local user name + *
      *

      - * Since JSch does not (currently) have the ability to parse an OpenSSH - * configuration file this is a simple parser to read that file and make the - * critical options available to {@link SshSessionFactory}. + * If the config doesn't set the port or the remote user name, %p and %r remain + * un-substituted. It's the caller's responsibility to replace them with values + * obtained from the connection URI. %i is not handled; Java has no concept of a + * "user ID". + *

      */ -public class OpenSshConfig { +public class OpenSshConfig implements ConfigRepository { + /** IANA assigned port number for SSH. */ static final int SSH_PORT = 22; @@ -105,16 +165,31 @@ /** The .ssh/config file we read and monitor for updates. */ private final File configFile; - /** Modification time of {@link #configFile} when {@link #hosts} loaded. */ + /** Modification time of {@link #configFile} when it was last loaded. */ private long lastModified; - /** Cached entries read out of the configuration file. */ - private Map hosts; + /** + * Encapsulates entries read out of the configuration file, and + * {@link Host}s created from that. + */ + private static class State { + Map entries = new LinkedHashMap<>(); + Map hosts = new HashMap<>(); + + @Override + @SuppressWarnings("nls") + public String toString() { + return "State [entries=" + entries + ", hosts=" + hosts + "]"; + } + } + + /** State read from the config file, plus {@link Host}s created from it. */ + private State state; OpenSshConfig(final File h, final File cfg) { home = h; configFile = cfg; - hosts = Collections.emptyMap(); + state = new State(); } /** @@ -127,75 +202,81 @@ * @return r configuration for the requested name. Never null. */ public Host lookup(final String hostName) { - final Map cache = refresh(); - Host h = cache.get(hostName); - if (h == null) - h = new Host(); - if (h.patternsApplied) + final State cache = refresh(); + Host h = cache.hosts.get(hostName); + if (h != null) { return h; - - for (final Map.Entry e : cache.entrySet()) { - if (!isHostPattern(e.getKey())) - continue; - if (!isHostMatch(e.getKey(), hostName)) - continue; - h.copyFrom(e.getValue()); } - - if (h.hostName == null) - h.hostName = hostName; - if (h.user == null) - h.user = OpenSshConfig.userName(); - if (h.port == 0) - h.port = OpenSshConfig.SSH_PORT; - if (h.connectionAttempts == 0) - h.connectionAttempts = 1; - h.patternsApplied = true; + HostEntry fullConfig = new HostEntry(); + // Initialize with default entries at the top of the file, before the + // first Host block. + fullConfig.merge(cache.entries.get(HostEntry.DEFAULT_NAME)); + for (final Map.Entry e : cache.entries.entrySet()) { + String key = e.getKey(); + if (isHostMatch(key, hostName)) { + fullConfig.merge(e.getValue()); + } + } + fullConfig.substitute(hostName, home); + h = new Host(fullConfig, hostName, home); + cache.hosts.put(hostName, h); return h; } - private synchronized Map refresh() { + private synchronized State refresh() { final long mtime = configFile.lastModified(); if (mtime != lastModified) { - try { - final FileInputStream in = new FileInputStream(configFile); - try { - hosts = parse(in); - } finally { - in.close(); - } - } catch (FileNotFoundException none) { - hosts = Collections.emptyMap(); - } catch (IOException err) { - hosts = Collections.emptyMap(); + State newState = new State(); + try (FileInputStream in = new FileInputStream(configFile)) { + newState.entries = parse(in); + } catch (IOException none) { + // Ignore -- we'll set and return an empty state } lastModified = mtime; + state = newState; } - return hosts; + return state; } - private Map parse(final InputStream in) throws IOException { - final Map m = new LinkedHashMap(); + private Map parse(final InputStream in) + throws IOException { + final Map m = new LinkedHashMap<>(); final BufferedReader br = new BufferedReader(new InputStreamReader(in)); - final List current = new ArrayList(4); + final List current = new ArrayList<>(4); String line; + // The man page doesn't say so, but the OpenSSH parser (readconf.c) + // starts out in active mode and thus always applies any lines that + // occur before the first host block. We gather those options in a + // HostEntry for DEFAULT_NAME. + HostEntry defaults = new HostEntry(); + current.add(defaults); + m.put(HostEntry.DEFAULT_NAME, defaults); + while ((line = br.readLine()) != null) { line = line.trim(); - if (line.length() == 0 || line.startsWith("#")) //$NON-NLS-1$ + if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$ continue; - - final String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$ - final String keyword = parts[0].trim(); - final String argValue = parts[1].trim(); + } + String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$ + // Although the ssh-config man page doesn't say so, the OpenSSH + // parser does allow quoted keywords. + String keyword = dequote(parts[0].trim()); + // man 5 ssh-config says lines had the format "keyword arguments", + // with no indication that arguments were optional. However, let's + // not crap out on missing arguments. See bug 444319. + String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase("Host", keyword)) { //$NON-NLS-1$ current.clear(); - for (final String pattern : argValue.split("[ \t]")) { //$NON-NLS-1$ - final String name = dequote(pattern); - Host c = m.get(name); + for (String name : HostEntry.parseList(argValue)) { + if (name == null || name.isEmpty()) { + // null should not occur, but better be safe than sorry. + continue; + } + HostEntry c = m.get(name); if (c == null) { - c = new Host(); + c = new HostEntry(); m.put(name, c); } current.add(c); @@ -206,57 +287,18 @@ if (current.isEmpty()) { // We received an option outside of a Host block. We // don't know who this should match against, so skip. - // continue; } - if (StringUtils.equalsIgnoreCase("HostName", keyword)) { //$NON-NLS-1$ - for (final Host c : current) - if (c.hostName == null) - c.hostName = dequote(argValue); - } else if (StringUtils.equalsIgnoreCase("User", keyword)) { //$NON-NLS-1$ - for (final Host c : current) - if (c.user == null) - c.user = dequote(argValue); - } else if (StringUtils.equalsIgnoreCase("Port", keyword)) { //$NON-NLS-1$ - try { - final int port = Integer.parseInt(dequote(argValue)); - for (final Host c : current) - if (c.port == 0) - c.port = port; - } catch (NumberFormatException nfe) { - // Bad port number. Don't set it. - } - } else if (StringUtils.equalsIgnoreCase("IdentityFile", keyword)) { //$NON-NLS-1$ - for (final Host c : current) - if (c.identityFile == null) - c.identityFile = toFile(dequote(argValue)); - } else if (StringUtils.equalsIgnoreCase( - "PreferredAuthentications", keyword)) { //$NON-NLS-1$ - for (final Host c : current) - if (c.preferredAuthentications == null) - c.preferredAuthentications = nows(dequote(argValue)); - } else if (StringUtils.equalsIgnoreCase("BatchMode", keyword)) { //$NON-NLS-1$ - for (final Host c : current) - if (c.batchMode == null) - c.batchMode = yesno(dequote(argValue)); - } else if (StringUtils.equalsIgnoreCase( - "StrictHostKeyChecking", keyword)) { //$NON-NLS-1$ - String value = dequote(argValue); - for (final Host c : current) - if (c.strictHostKeyChecking == null) - c.strictHostKeyChecking = value; - } else if (StringUtils.equalsIgnoreCase( - "ConnectionAttempts", keyword)) { //$NON-NLS-1$ - try { - final int connectionAttempts = Integer.parseInt(dequote(argValue)); - if (connectionAttempts > 0) { - for (final Host c : current) - if (c.connectionAttempts == 0) - c.connectionAttempts = connectionAttempts; - } - } catch (NumberFormatException nfe) { - // ignore bad values + if (HostEntry.isListKey(keyword)) { + List args = HostEntry.parseList(argValue); + for (HostEntry entry : current) { + entry.setValue(keyword, args); + } + } else if (!argValue.isEmpty()) { + argValue = dequote(argValue); + for (HostEntry entry : current) { + entry.setValue(keyword, argValue); } } } @@ -264,23 +306,35 @@ return m; } - private static boolean isHostPattern(final String s) { - return s.indexOf('*') >= 0 || s.indexOf('?') >= 0; + private static boolean isHostMatch(final String pattern, + final String name) { + if (pattern.startsWith("!")) { //$NON-NLS-1$ + return !patternMatchesHost(pattern.substring(1), name); + } else { + return patternMatchesHost(pattern, name); + } } - private static boolean isHostMatch(final String pattern, final String name) { - final FileNameMatcher fn; - try { - fn = new FileNameMatcher(pattern, null); - } catch (InvalidPatternException e) { - return false; + private static boolean patternMatchesHost(final String pattern, + final String name) { + if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) { + final FileNameMatcher fn; + try { + fn = new FileNameMatcher(pattern, null); + } catch (InvalidPatternException e) { + return false; + } + fn.append(name); + return fn.isMatch(); + } else { + // Not a pattern but a full host name + return pattern.equals(name); } - fn.append(name); - return fn.isMatch(); } private static String dequote(final String value) { - if (value.startsWith("\"") && value.endsWith("\"")) //$NON-NLS-1$ //$NON-NLS-2$ + if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$ + && value.length() > 1) return value.substring(1, value.length() - 1); return value; } @@ -300,23 +354,453 @@ return Boolean.FALSE; } - private File toFile(final String path) { - if (path.startsWith("~/")) //$NON-NLS-1$ + private static File toFile(String path, File home) { + if (path.startsWith("~/")) { //$NON-NLS-1$ return new File(home, path.substring(2)); + } File ret = new File(path); - if (ret.isAbsolute()) + if (ret.isAbsolute()) { return ret; + } return new File(home, path); } + private static int positive(final String value) { + if (value != null) { + try { + return Integer.parseUnsignedInt(value); + } catch (NumberFormatException e) { + // Ignore + } + } + return -1; + } + static String userName() { return AccessController.doPrivileged(new PrivilegedAction() { + @Override public String run() { - return System.getProperty("user.name"); //$NON-NLS-1$ + return SystemReader.getInstance() + .getProperty(Constants.OS_USER_NAME_KEY); } }); } + private static class HostEntry implements ConfigRepository.Config { + + /** + * "Host name" of the HostEntry for the default options before the first + * host block in a config file. + */ + public static final String DEFAULT_NAME = ""; //$NON-NLS-1$ + + // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys + // to ssh-config keys. + private static final Map KEY_MAP = new HashMap<>(); + + static { + KEY_MAP.put("kex", "KexAlgorithms"); //$NON-NLS-1$//$NON-NLS-2$ + KEY_MAP.put("server_host_key", "HostKeyAlgorithms"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("cipher.c2s", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("cipher.s2c", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("mac.c2s", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("mac.s2c", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("compression.s2c", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("compression.c2s", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$ + KEY_MAP.put("MaxAuthTries", "NumberOfPasswordPrompts"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Keys that can be specified multiple times, building up a list. (I.e., + * those are the keys that do not follow the general rule of "first + * occurrence wins".) + */ + private static final Set MULTI_KEYS = new HashSet<>(); + + static { + MULTI_KEYS.add("CERTIFICATEFILE"); //$NON-NLS-1$ + MULTI_KEYS.add("IDENTITYFILE"); //$NON-NLS-1$ + MULTI_KEYS.add("LOCALFORWARD"); //$NON-NLS-1$ + MULTI_KEYS.add("REMOTEFORWARD"); //$NON-NLS-1$ + MULTI_KEYS.add("SENDENV"); //$NON-NLS-1$ + } + + /** + * Keys that take a whitespace-separated list of elements as argument. + * Because the dequote-handling is different, we must handle those in + * the parser. There are a few other keys that take comma-separated + * lists as arguments, but for the parser those are single arguments + * that must be quoted if they contain whitespace, and taking them apart + * is the responsibility of the user of those keys. + */ + private static final Set LIST_KEYS = new HashSet<>(); + + static { + LIST_KEYS.add("CANONICALDOMAINS"); //$NON-NLS-1$ + LIST_KEYS.add("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$ + LIST_KEYS.add("SENDENV"); //$NON-NLS-1$ + LIST_KEYS.add("USERKNOWNHOSTSFILE"); //$NON-NLS-1$ + } + + private Map options; + + private Map> multiOptions; + + private Map> listOptions; + + @Override + public String getHostname() { + return getValue("HOSTNAME"); //$NON-NLS-1$ + } + + @Override + public String getUser() { + return getValue("USER"); //$NON-NLS-1$ + } + + @Override + public int getPort() { + return positive(getValue("PORT")); //$NON-NLS-1$ + } + + private static String mapKey(String key) { + String k = KEY_MAP.get(key); + if (k == null) { + k = key; + } + return k.toUpperCase(Locale.ROOT); + } + + private String findValue(String key) { + String k = mapKey(key); + String result = options != null ? options.get(k) : null; + if (result == null) { + // Also check the list and multi options. Modern OpenSSH treats + // UserKnownHostsFile and GlobalKnownHostsFile as list-valued, + // and so does this parser. Jsch 0.1.54 in general doesn't know + // about list-valued options (it _does_ know multi-valued + // options, though), and will ask for a single value for such + // options. + // + // Let's be lenient and return at least the first value from + // a list-valued or multi-valued key for which Jsch asks for a + // single value. + List values = listOptions != null ? listOptions.get(k) + : null; + if (values == null) { + values = multiOptions != null ? multiOptions.get(k) : null; + } + if (values != null && !values.isEmpty()) { + result = values.get(0); + } + } + return result; + } + + @Override + public String getValue(String key) { + // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue() for this + // special case. + if (key.equals("compression.s2c") //$NON-NLS-1$ + || key.equals("compression.c2s")) { //$NON-NLS-1$ + String foo = findValue(key); + if (foo == null || foo.equals("no")) { //$NON-NLS-1$ + return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$ + } + return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$ + } + return findValue(key); + } + + @Override + public String[] getValues(String key) { + String k = mapKey(key); + List values = listOptions != null ? listOptions.get(k) + : null; + if (values == null) { + values = multiOptions != null ? multiOptions.get(k) : null; + } + if (values == null || values.isEmpty()) { + return new String[0]; + } + return values.toArray(new String[values.size()]); + } + + public void setValue(String key, String value) { + String k = key.toUpperCase(Locale.ROOT); + if (MULTI_KEYS.contains(k)) { + if (multiOptions == null) { + multiOptions = new HashMap<>(); + } + List values = multiOptions.get(k); + if (values == null) { + values = new ArrayList<>(4); + multiOptions.put(k, values); + } + values.add(value); + } else { + if (options == null) { + options = new HashMap<>(); + } + if (!options.containsKey(k)) { + options.put(k, value); + } + } + } + + public void setValue(String key, List values) { + if (values.isEmpty()) { + // Can occur only on a missing argument: ignore. + return; + } + String k = key.toUpperCase(Locale.ROOT); + // Check multi-valued keys first; because of the replacement + // strategy, they must take precedence over list-valued keys + // which always follow the "first occurrence wins" strategy. + // + // Note that SendEnv is a multi-valued list-valued key. (It's + // rather immaterial for JGit, though.) + if (MULTI_KEYS.contains(k)) { + if (multiOptions == null) { + multiOptions = new HashMap<>(2 * MULTI_KEYS.size()); + } + List items = multiOptions.get(k); + if (items == null) { + items = new ArrayList<>(values); + multiOptions.put(k, items); + } else { + items.addAll(values); + } + } else { + if (listOptions == null) { + listOptions = new HashMap<>(2 * LIST_KEYS.size()); + } + if (!listOptions.containsKey(k)) { + listOptions.put(k, values); + } + } + } + + public static boolean isListKey(String key) { + return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT)); + } + + /** + * Splits the argument into a list of whitespace-separated elements. + * Elements containing whitespace must be quoted and will be de-quoted. + * + * @param argument + * argument part of the configuration line as read from the + * config file + * @return a {@link List} of elements, possibly empty and possibly + * containing empty elements + */ + public static List parseList(String argument) { + List result = new ArrayList<>(4); + int start = 0; + int length = argument.length(); + while (start < length) { + // Skip whitespace + if (Character.isSpaceChar(argument.charAt(start))) { + start++; + continue; + } + if (argument.charAt(start) == '"') { + int stop = argument.indexOf('"', ++start); + if (stop < start) { + // No closing double quote: skip + break; + } + result.add(argument.substring(start, stop)); + start = stop + 1; + } else { + int stop = start + 1; + while (stop < length + && !Character.isSpaceChar(argument.charAt(stop))) { + stop++; + } + result.add(argument.substring(start, stop)); + start = stop + 1; + } + } + return result; + } + + protected void merge(HostEntry entry) { + if (entry == null) { + // Can occur if we could not read the config file + return; + } + if (entry.options != null) { + if (options == null) { + options = new HashMap<>(); + } + for (Map.Entry item : entry.options + .entrySet()) { + if (!options.containsKey(item.getKey())) { + options.put(item.getKey(), item.getValue()); + } + } + } + if (entry.listOptions != null) { + if (listOptions == null) { + listOptions = new HashMap<>(2 * LIST_KEYS.size()); + } + for (Map.Entry> item : entry.listOptions + .entrySet()) { + if (!listOptions.containsKey(item.getKey())) { + listOptions.put(item.getKey(), item.getValue()); + } + } + + } + if (entry.multiOptions != null) { + if (multiOptions == null) { + multiOptions = new HashMap<>(2 * MULTI_KEYS.size()); + } + for (Map.Entry> item : entry.multiOptions + .entrySet()) { + List values = multiOptions.get(item.getKey()); + if (values == null) { + values = new ArrayList<>(item.getValue()); + multiOptions.put(item.getKey(), values); + } else { + values.addAll(item.getValue()); + } + } + } + } + + private class Replacer { + private final Map replacements = new HashMap<>(); + + public Replacer(String originalHostName, File home) { + replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$ + replacements.put(Character.valueOf('d'), home.getPath()); + // Needs special treatment... + String host = getValue("HOSTNAME"); //$NON-NLS-1$ + replacements.put(Character.valueOf('h'), originalHostName); + if (host != null && host.indexOf('%') >= 0) { + host = substitute(host, "h"); //$NON-NLS-1$ + options.put("HOSTNAME", host); //$NON-NLS-1$ + } + if (host != null) { + replacements.put(Character.valueOf('h'), host); + } + String localhost = SystemReader.getInstance().getHostname(); + replacements.put(Character.valueOf('l'), localhost); + int period = localhost.indexOf('.'); + if (period > 0) { + localhost = localhost.substring(0, period); + } + replacements.put(Character.valueOf('L'), localhost); + replacements.put(Character.valueOf('n'), originalHostName); + replacements.put(Character.valueOf('p'), getValue("PORT")); //$NON-NLS-1$ + replacements.put(Character.valueOf('r'), getValue("USER")); //$NON-NLS-1$ + replacements.put(Character.valueOf('u'), userName()); + replacements.put(Character.valueOf('C'), + substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public String substitute(String input, String allowed) { + if (input == null || input.length() <= 1 + || input.indexOf('%') < 0) { + return input; + } + StringBuilder builder = new StringBuilder(); + int start = 0; + int length = input.length(); + while (start < length) { + int percent = input.indexOf('%', start); + if (percent < 0 || percent + 1 >= length) { + builder.append(input.substring(start)); + break; + } + String replacement = null; + char ch = input.charAt(percent + 1); + if (ch == '%' || allowed.indexOf(ch) >= 0) { + replacement = replacements.get(Character.valueOf(ch)); + } + if (replacement == null) { + builder.append(input.substring(start, percent + 2)); + } else { + builder.append(input.substring(start, percent)) + .append(replacement); + } + start = percent + 2; + } + return builder.toString(); + } + } + + private List substitute(List values, String allowed, + Replacer r) { + List result = new ArrayList<>(values.size()); + for (String value : values) { + result.add(r.substitute(value, allowed)); + } + return result; + } + + private List replaceTilde(List values, File home) { + List result = new ArrayList<>(values.size()); + for (String value : values) { + result.add(toFile(value, home).getPath()); + } + return result; + } + + protected void substitute(String originalHostName, File home) { + Replacer r = new Replacer(originalHostName, home); + if (multiOptions != null) { + List values = multiOptions.get("IDENTITYFILE"); //$NON-NLS-1$ + if (values != null) { + values = substitute(values, "dhlru", r); //$NON-NLS-1$ + values = replaceTilde(values, home); + multiOptions.put("IDENTITYFILE", values); //$NON-NLS-1$ + } + values = multiOptions.get("CERTIFICATEFILE"); //$NON-NLS-1$ + if (values != null) { + values = substitute(values, "dhlru", r); //$NON-NLS-1$ + values = replaceTilde(values, home); + multiOptions.put("CERTIFICATEFILE", values); //$NON-NLS-1$ + } + } + if (listOptions != null) { + List values = listOptions.get("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$ + if (values != null) { + values = replaceTilde(values, home); + listOptions.put("GLOBALKNOWNHOSTSFILE", values); //$NON-NLS-1$ + } + values = listOptions.get("USERKNOWNHOSTSFILE"); //$NON-NLS-1$ + if (values != null) { + values = replaceTilde(values, home); + listOptions.put("USERKNOWNHOSTSFILE", values); //$NON-NLS-1$ + } + } + if (options != null) { + // HOSTNAME already done in Replacer constructor + String value = options.get("IDENTITYAGENT"); //$NON-NLS-1$ + if (value != null) { + value = r.substitute(value, "dhlru"); //$NON-NLS-1$ + value = toFile(value, home).getPath(); + options.put("IDENTITYAGENT", value); //$NON-NLS-1$ + } + } + // Match is not implemented and would need to be done elsewhere + // anyway. ControlPath, LocalCommand, ProxyCommand, and + // RemoteCommand are not used by Jsch. + } + + @Override + @SuppressWarnings("nls") + public String toString() { + return "HostEntry [options=" + options + ", multiOptions=" + + multiOptions + ", listOptions=" + listOptions + "]"; + } + } + /** * Configuration of one "Host" block in the configuration file. *

      @@ -329,8 +813,6 @@ * already merged into this block. */ public static class Host { - boolean patternsApplied; - String hostName; int port; @@ -347,23 +829,18 @@ int connectionAttempts; - void copyFrom(final Host src) { - if (hostName == null) - hostName = src.hostName; - if (port == 0) - port = src.port; - if (identityFile == null) - identityFile = src.identityFile; - if (user == null) - user = src.user; - if (preferredAuthentications == null) - preferredAuthentications = src.preferredAuthentications; - if (batchMode == null) - batchMode = src.batchMode; - if (strictHostKeyChecking == null) - strictHostKeyChecking = src.strictHostKeyChecking; - if (connectionAttempts == 0) - connectionAttempts = src.connectionAttempts; + private Config config; + + /** + * Creates a new uninitialized {@link Host}. + */ + public Host() { + // For API backwards compatibility with pre-4.9 JGit + } + + Host(Config config, String hostName, File homeDir) { + this.config = config; + complete(hostName, homeDir); } /** @@ -431,5 +908,78 @@ public int getConnectionAttempts() { return connectionAttempts; } + + + private void complete(String initialHostName, File homeDir) { + // Try to set values from the options. + hostName = config.getHostname(); + user = config.getUser(); + port = config.getPort(); + connectionAttempts = positive( + config.getValue("ConnectionAttempts")); //$NON-NLS-1$ + strictHostKeyChecking = config.getValue("StrictHostKeyChecking"); //$NON-NLS-1$ + String value = config.getValue("BatchMode"); //$NON-NLS-1$ + if (value != null) { + batchMode = yesno(value); + } + value = config.getValue("PreferredAuthentications"); //$NON-NLS-1$ + if (value != null) { + preferredAuthentications = nows(value); + } + // Fill in defaults if still not set + if (hostName == null) { + hostName = initialHostName; + } + if (user == null) { + user = OpenSshConfig.userName(); + } + if (port <= 0) { + port = OpenSshConfig.SSH_PORT; + } + if (connectionAttempts <= 0) { + connectionAttempts = 1; + } + String[] identityFiles = config.getValues("IdentityFile"); //$NON-NLS-1$ + if (identityFiles != null && identityFiles.length > 0) { + identityFile = toFile(identityFiles[0], homeDir); + } + } + + Config getConfig() { + return config; + } + + @Override + @SuppressWarnings("nls") + public String toString() { + return "Host [hostName=" + hostName + ", port=" + port + + ", identityFile=" + identityFile + ", user=" + user + + ", preferredAuthentications=" + preferredAuthentications + + ", batchMode=" + batchMode + ", strictHostKeyChecking=" + + strictHostKeyChecking + ", connectionAttempts=" + + connectionAttempts + ", config=" + config + "]"; + } + } + + /** + * {@inheritDoc} + *

      + * Retrieves the full {@link com.jcraft.jsch.ConfigRepository.Config Config} + * for the given host name. Should be called only by Jsch and tests. + * + * @since 4.9 + */ + @Override + public Config getConfig(String hostName) { + Host host = lookup(hostName); + return host.getConfig(); + } + + /** {@inheritDoc} */ + @Override + @SuppressWarnings("nls") + public String toString() { + return "OpenSshConfig [home=" + home + ", configFile=" + configFile + + ", lastModified=" + lastModified + ", state=" + state + "]"; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,7 +64,7 @@ URIish uri; - final SortedMap updates = new TreeMap(); + final SortedMap updates = new TreeMap<>(); StringBuilder messageBuffer; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,7 @@ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectIdOwnerMap; /** @@ -59,6 +60,8 @@ private int crc; + private int type = Constants.OBJ_BAD; + PackedObjectInfo(final long headerOffset, final int packedCRC, final AnyObjectId id) { super(id); @@ -77,6 +80,8 @@ } /** + * Get offset in pack when object has been already written + * * @return offset in pack when object has been already written, or 0 if it * has not been written yet */ @@ -95,6 +100,8 @@ } /** + * Get the 32 bit CRC checksum for the packed data. + * * @return the 32 bit CRC checksum for the packed data. */ public int getCRC() { @@ -112,4 +119,26 @@ public void setCRC(final int crc) { this.crc = crc; } + + /** + * Get the object type. + * + * @return the object type. The default type is OBJ_BAD, which is considered + * as unknown or invalid type. + * @since 4.9 + */ + public int getType() { + return type; + } + + /** + * Record the object type if applicable. + * + * @param type + * the object type. + * @since 4.9 + */ + public void setType(int type) { + this.type = type; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,6 +55,8 @@ import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Read Git style pkt-line formatting from an input stream. @@ -67,6 +69,8 @@ * against the underlying InputStream. */ public class PacketLineIn { + private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class); + /** Magic return from {@link #readString()} when a flush packet is found. */ public static final String END = new StringBuilder(0).toString(); /* must not string pool */ @@ -83,19 +87,32 @@ ACK_READY; } + private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF]; private final InputStream in; + private long limit; - private final byte[] lineBuffer; + /** + * Create a new packet line reader. + * + * @param in + * the input stream to consume. + */ + public PacketLineIn(InputStream in) { + this(in, 0); + } /** * Create a new packet line reader. * - * @param i + * @param in * the input stream to consume. + * @param limit + * bytes to read from the input; unlimited if set to 0. + * @since 4.7 */ - public PacketLineIn(final InputStream i) { - in = i; - lineBuffer = new byte[SideBandOutputStream.SMALL_BUF]; + public PacketLineIn(InputStream in, long limit) { + this.in = in; + this.limit = limit; } AckNackResult readACK(final MutableObjectId returnedId) throws IOException { @@ -131,17 +148,21 @@ * * @return the string. {@link #END} if the string was the magic flush * packet. - * @throws IOException + * @throws java.io.IOException * the stream cannot be read. */ public String readString() throws IOException { int len = readLength(); - if (len == 0) + if (len == 0) { + log.debug("git< 0000"); //$NON-NLS-1$ return END; + } len -= 4; // length header (4 bytes) - if (len == 0) + if (len == 0) { + log.debug("git< "); //$NON-NLS-1$ return ""; //$NON-NLS-1$ + } byte[] raw; if (len <= lineBuffer.length) @@ -152,7 +173,10 @@ IO.readFully(in, raw, 0, len); if (raw[len - 1] == '\n') len--; - return RawParseUtils.decode(Constants.CHARSET, raw, 0, len); + + String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len); + log.debug("git< " + s); //$NON-NLS-1$ + return s; } /** @@ -162,13 +186,15 @@ * * @return the string. {@link #END} if the string was the magic flush * packet. - * @throws IOException + * @throws java.io.IOException * the stream cannot be read. */ public String readStringRaw() throws IOException { int len = readLength(); - if (len == 0) + if (len == 0) { + log.debug("git< 0000"); //$NON-NLS-1$ return END; + } len -= 4; // length header (4 bytes) @@ -179,20 +205,66 @@ raw = new byte[len]; IO.readFully(in, raw, 0, len); - return RawParseUtils.decode(Constants.CHARSET, raw, 0, len); + + String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len); + log.debug("git< " + s); //$NON-NLS-1$ + return s; + } + + void discardUntilEnd() throws IOException { + for (;;) { + int n = readLength(); + if (n == 0) { + break; + } + IO.skipFully(in, n - 4); + } } int readLength() throws IOException { IO.readFully(in, lineBuffer, 0, 4); + int len; try { - final int len = RawParseUtils.parseHexInt16(lineBuffer, 0); - if (len != 0 && len < 4) - throw new ArrayIndexOutOfBoundsException(); - return len; + len = RawParseUtils.parseHexInt16(lineBuffer, 0); } catch (ArrayIndexOutOfBoundsException err) { - throw new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader, - "" + (char) lineBuffer[0] + (char) lineBuffer[1] //$NON-NLS-1$ - + (char) lineBuffer[2] + (char) lineBuffer[3])); + throw invalidHeader(); + } + + if (len == 0) { + return 0; + } else if (len < 4) { + throw invalidHeader(); } + + if (limit != 0) { + int n = len - 4; + if (limit < n) { + limit = -1; + try { + IO.skipFully(in, n); + } catch (IOException e) { + // Ignore failure discarding packet over limit. + } + throw new InputOverLimitIOException(); + } + // if set limit must not be 0 (means unlimited). + limit = n < limit ? limit - n : -1; + } + return len; + } + + private IOException invalidHeader() { + return new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader, + "" + (char) lineBuffer[0] + (char) lineBuffer[1] //$NON-NLS-1$ + + (char) lineBuffer[2] + (char) lineBuffer[3])); + } + + /** + * IOException thrown by read when the configured input limit is exceeded. + * + * @since 4.7 + */ + public static class InputOverLimitIOException extends IOException { + private static final long serialVersionUID = 1L; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,6 +49,9 @@ import java.io.OutputStream; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Write Git style pkt-line formatting to an output stream. @@ -61,6 +64,8 @@ * against the underlying OutputStream. */ public class PacketLineOut { + private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class); + private final OutputStream out; private final byte[] lenbuffer; @@ -95,7 +100,7 @@ * * @param s * string to write. - * @throws IOException + * @throws java.io.IOException * the packet could not be written, the stream is corrupted as * the packet may have been only partially written. */ @@ -109,14 +114,36 @@ * @param packet * the packet to write; the length of the packet is equal to the * size of the byte array. - * @throws IOException + * @throws java.io.IOException + * the packet could not be written, the stream is corrupted as + * the packet may have been only partially written. + */ + public void writePacket(byte[] packet) throws IOException { + writePacket(packet, 0, packet.length); + } + + /** + * Write a binary packet to the stream. + * + * @param buf + * the packet to write + * @param pos + * first index within {@code buf}. + * @param len + * number of bytes to write. + * @throws java.io.IOException * the packet could not be written, the stream is corrupted as * the packet may have been only partially written. + * @since 4.5 */ - public void writePacket(final byte[] packet) throws IOException { - formatLength(packet.length + 4); + public void writePacket(byte[] buf, int pos, int len) throws IOException { + formatLength(len + 4); out.write(lenbuffer, 0, 4); - out.write(packet); + out.write(buf, pos, len); + if (log.isDebugEnabled()) { + String s = RawParseUtils.decode(Constants.CHARSET, buf, pos, len); + log.debug("git> " + s); //$NON-NLS-1$ + } } /** @@ -128,13 +155,14 @@ * Implicitly performs a flush on the underlying OutputStream to ensure the * peer will receive all data written thus far. * - * @throws IOException + * @throws java.io.IOException * the end marker could not be written, the stream is corrupted * as the end marker may have been only partially written. */ public void end() throws IOException { formatLength(0); out.write(lenbuffer, 0, 4); + log.debug("git> 0000"); //$NON-NLS-1$ if (flushOnEnd) flush(); } @@ -145,7 +173,7 @@ * Performs a flush on the underlying OutputStream to ensure the peer will * receive all data written thus far. * - * @throws IOException + * @throws java.io.IOException * the underlying stream failed to flush. */ public void flush() throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,6 +66,7 @@ import org.eclipse.jgit.internal.storage.pack.BinaryDelta; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchingProgressMonitor; +import org.eclipse.jgit.lib.BlobObjectChecker; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.MutableObjectId; @@ -75,24 +76,27 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; import org.eclipse.jgit.lib.ObjectIdSubclassMap; -import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectStream; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.LongMap; import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.sha1.SHA1; /** - * Parses a pack stream and imports it for an {@link ObjectInserter}. + * Parses a pack stream and imports it for an + * {@link org.eclipse.jgit.lib.ObjectInserter}. *

      * Applications can acquire an instance of a parser from ObjectInserter's - * {@link ObjectInserter#newPackParser(InputStream)} method. + * {@link org.eclipse.jgit.lib.ObjectInserter#newPackParser(InputStream)} + * method. *

      - * Implementations of {@link ObjectInserter} should subclass this type and - * provide their own logic for the various {@code on*()} event methods declared - * to be abstract. + * Implementations of {@link org.eclipse.jgit.lib.ObjectInserter} should + * subclass this type and provide their own logic for the various {@code on*()} + * event methods declared to be abstract. */ public abstract class PackParser { /** Size of the internal stream buffer. */ @@ -116,20 +120,19 @@ private byte[] hdrBuf; - private final MessageDigest objectDigest; - + private final SHA1 objectHasher = SHA1.newInstance(); private final MutableObjectId tempObjectId; private InputStream in; - private byte[] buf; + byte[] buf; /** Position in the input stream of {@code buf[0]}. */ private long bBase; private int bOffset; - private int bAvail; + int bAvail; private ObjectChecker objCheck; @@ -143,7 +146,7 @@ private boolean expectDataAfterPackFooter; - private long objectCount; + private long expectedObjectCount; private PackedObjectInfo[] entries; @@ -173,8 +176,8 @@ private LongMap baseByPos; - /** Blobs whose contents need to be double-checked after indexing. */ - private BlockList deferredCheckBlobs; + /** Objects need to be double-checked for collision after indexing. */ + private BlockList collisionCheckObjs; private MessageDigest packDigest; @@ -186,6 +189,9 @@ /** Git object size limit */ private long maxObjectSizeLimit; + private final ReceivedPackStatistics.Builder stats = + new ReceivedPackStatistics.Builder(); + /** * Initialize a pack parser. * @@ -203,13 +209,16 @@ buf = new byte[BUFFER_SIZE]; tempBuffer = new byte[BUFFER_SIZE]; hdrBuf = new byte[64]; - objectDigest = Constants.newMessageDigest(); tempObjectId = new MutableObjectId(); packDigest = Constants.newMessageDigest(); checkObjectCollisions = true; } - /** @return true if a thin pack (missing base objects) is permitted. */ + /** + * Whether a thin pack (missing base objects) is permitted. + * + * @return {@code true} if a thin pack (missing base objects) is permitted. + */ public boolean isAllowThin() { return allowThin; } @@ -228,6 +237,8 @@ } /** + * Whether received objects are verified to prevent collisions. + * * @return if true received objects are verified to prevent collisions. * @since 4.1 */ @@ -272,7 +283,7 @@ */ public void setNeedNewObjectIds(boolean b) { if (b) - newObjectIds = new ObjectIdSubclassMap(); + newObjectIds = new ObjectIdSubclassMap<>(); else newObjectIds = null; } @@ -296,7 +307,11 @@ this.needBaseObjectIds = b; } - /** @return true if the EOF should be read from the input after the footer. */ + /** + * Whether the EOF should be read from the input after the footer. + * + * @return true if the EOF should be read from the input after the footer. + */ public boolean isCheckEofAfterPackFooter() { return checkEofAfterPackFooter; } @@ -311,12 +326,18 @@ checkEofAfterPackFooter = b; } - /** @return true if there is data expected after the pack footer. */ + /** + * Whether there is data expected after the pack footer. + * + * @return true if there is data expected after the pack footer. + */ public boolean isExpectDataAfterPackFooter() { return expectDataAfterPackFooter; } /** + * Set if there is additional data in InputStream after pack. + * * @param e * true if there is additional data in InputStream after pack. * This requires the InputStream to support the mark and reset @@ -326,18 +347,26 @@ expectDataAfterPackFooter = e; } - /** @return the new objects that were sent by the user */ + /** + * Get the new objects that were sent by the user + * + * @return the new objects that were sent by the user + */ public ObjectIdSubclassMap getNewObjectIds() { if (newObjectIds != null) return newObjectIds; - return new ObjectIdSubclassMap(); + return new ObjectIdSubclassMap<>(); } - /** @return set of objects the incoming pack assumed for delta purposes */ + /** + * Get set of objects the incoming pack assumed for delta purposes + * + * @return set of objects the incoming pack assumed for delta purposes + */ public ObjectIdSubclassMap getBaseObjectIds() { if (baseObjectIds != null) return baseObjectIds; - return new ObjectIdSubclassMap(); + return new ObjectIdSubclassMap<>(); } /** @@ -374,7 +403,11 @@ setObjectChecker(on ? new ObjectChecker() : null); } - /** @return the message to record with the pack lock. */ + /** + * Get the message to record with the pack lock. + * + * @return the message to record with the pack lock. + */ public String getLockMessage() { return lockMessage; } @@ -416,7 +449,7 @@ return entryCount; } - /*** + /** * Get the information about the requested object. *

      * The object information is only available after @@ -455,8 +488,8 @@ } /** - * Get the size of the parsed pack. - * + * Get the size of the newly created pack. + *

      * This will also include the pack index size if an index was created. This * method should only be called after pack parsing is finished. * @@ -469,14 +502,26 @@ } /** + * Returns the statistics of the parsed pack. + *

      + * This should only be called after pack parsing is finished. + * + * @return {@link org.eclipse.jgit.transport.ReceivedPackStatistics} + * @since 4.6 + */ + public ReceivedPackStatistics getReceivedPackStatistics() { + return stats.build(); + } + + /** * Parse the pack stream. * * @param progress * callback to provide progress feedback during parsing. If null, - * {@link NullProgressMonitor} will be used. + * {@link org.eclipse.jgit.lib.NullProgressMonitor} will be used. * @return the pack lock, if one was requested by setting * {@link #setLockMessage(String)}. - * @throws IOException + * @throws java.io.IOException * the stream is malformed, or contains corrupt objects. * @since 3.0 */ @@ -489,13 +534,13 @@ * * @param receiving * receives progress feedback during the initial receiving - * objects phase. If null, {@link NullProgressMonitor} will be - * used. + * objects phase. If null, + * {@link org.eclipse.jgit.lib.NullProgressMonitor} will be used. * @param resolving * receives progress feedback during the resolving objects phase. * @return the pack lock, if one was requested by setting * {@link #setLockMessage(String)}. - * @throws IOException + * @throws java.io.IOException * the stream is malformed, or contains corrupt objects. * @since 3.0 */ @@ -511,15 +556,15 @@ try { readPackHeader(); - entries = new PackedObjectInfo[(int) objectCount]; - baseById = new ObjectIdOwnerMap(); - baseByPos = new LongMap(); - deferredCheckBlobs = new BlockList(); + entries = new PackedObjectInfo[(int) expectedObjectCount]; + baseById = new ObjectIdOwnerMap<>(); + baseByPos = new LongMap<>(); + collisionCheckObjs = new BlockList<>(); receiving.beginTask(JGitText.get().receivingObjects, - (int) objectCount); + (int) expectedObjectCount); try { - for (int done = 0; done < objectCount; done++) { + for (int done = 0; done < expectedObjectCount; done++) { indexOneObject(); receiving.update(1); if (receiving.isCancelled()) @@ -531,32 +576,12 @@ receiving.endTask(); } - if (!deferredCheckBlobs.isEmpty()) - doDeferredCheckBlobs(); - if (deltaCount > 0) { - if (resolving instanceof BatchingProgressMonitor) { - ((BatchingProgressMonitor) resolving).setDelayStart( - 1000, - TimeUnit.MILLISECONDS); - } - resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount); - resolveDeltas(resolving); - if (entryCount < objectCount) { - if (!isAllowThin()) { - throw new IOException(MessageFormat.format( - JGitText.get().packHasUnresolvedDeltas, - Long.valueOf(objectCount - entryCount))); - } - - resolveDeltasWithExternalBases(resolving); + if (!collisionCheckObjs.isEmpty()) { + checkObjectCollision(); + } - if (entryCount < objectCount) { - throw new IOException(MessageFormat.format( - JGitText.get().packHasUnresolvedDeltas, - Long.valueOf(objectCount - entryCount))); - } - } - resolving.endTask(); + if (deltaCount > 0) { + processDeltas(resolving); } packDigest = null; @@ -579,6 +604,31 @@ return null; // By default there is no locking. } + private void processDeltas(ProgressMonitor resolving) throws IOException { + if (resolving instanceof BatchingProgressMonitor) { + ((BatchingProgressMonitor) resolving).setDelayStart(1000, + TimeUnit.MILLISECONDS); + } + resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount); + resolveDeltas(resolving); + if (entryCount < expectedObjectCount) { + if (!isAllowThin()) { + throw new IOException(MessageFormat.format( + JGitText.get().packHasUnresolvedDeltas, + Long.valueOf(expectedObjectCount - entryCount))); + } + + resolveDeltasWithExternalBases(resolving); + + if (entryCount < expectedObjectCount) { + throw new IOException(MessageFormat.format( + JGitText.get().packHasUnresolvedDeltas, + Long.valueOf(expectedObjectCount - entryCount))); + } + } + resolving.endTask(); + } + private void resolveDeltas(final ProgressMonitor progress) throws IOException { final int last = entryCount; @@ -626,6 +676,7 @@ private void resolveDeltas(DeltaVisit visit, final int type, ObjectTypeAndSize info, ProgressMonitor progress) throws IOException { + stats.addDeltaObject(type); do { progress.update(1); info = openDatabase(visit.delta, info); @@ -651,18 +702,23 @@ JGitText.get().corruptionDetectedReReadingAt, Long.valueOf(visit.delta.position))); + SHA1 objectDigest = objectHasher.reset(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(visit.data.length)); objectDigest.update((byte) 0); objectDigest.update(visit.data); - tempObjectId.fromRaw(objectDigest.digest(), 0); + objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, visit.data); + if (isCheckObjectCollisions() && readCurs.has(tempObjectId)) { + checkObjectCollision(tempObjectId, type, visit.data); + } PackedObjectInfo oe; oe = newInfo(tempObjectId, visit.delta, visit.parent.id); oe.setOffset(visit.delta.position); + oe.setType(type); onInflatedObjectData(oe, type, visit.data); addObjectAndTrack(oe); visit.id = oe; @@ -674,7 +730,7 @@ private final void checkIfTooLarge(int typeCode, long size) throws IOException { - if (0 < maxObjectSizeLimit && maxObjectSizeLimit < size) + if (0 < maxObjectSizeLimit && maxObjectSizeLimit < size) { switch (typeCode) { case Constants.OBJ_COMMIT: case Constants.OBJ_TREE: @@ -684,13 +740,17 @@ case Constants.OBJ_OFS_DELTA: case Constants.OBJ_REF_DELTA: - throw new TooLargeObjectInPackException(maxObjectSizeLimit); + throw new TooLargeObjectInPackException(size, maxObjectSizeLimit); default: throw new IOException(MessageFormat.format( JGitText.get().unknownObjectType, Integer.valueOf(typeCode))); } + } + if (size > Integer.MAX_VALUE - 8) { + throw new TooLargeObjectInPackException(size, Integer.MAX_VALUE - 8); + } } /** @@ -706,7 +766,7 @@ * @param info * the info object to populate. * @return {@code info}, after populating. - * @throws IOException + * @throws java.io.IOException * the size cannot be read. */ protected ObjectTypeAndSize readObjectHeader(ObjectTypeAndSize info) @@ -810,9 +870,9 @@ growEntries(baseById.size()); if (needBaseObjectIds) - baseObjectIds = new ObjectIdSubclassMap(); + baseObjectIds = new ObjectIdSubclassMap<>(); - final List missing = new ArrayList(64); + final List missing = new ArrayList<>(64); for (final DeltaChain baseId : baseById) { if (baseId.head == null) continue; @@ -833,10 +893,9 @@ visit.id = baseId; final int typeCode = ldr.getType(); final PackedObjectInfo oe = newInfo(baseId, null, null); - + oe.setType(typeCode); if (onAppendBase(typeCode, visit.data, oe)) entries[entryCount++] = oe; - visit.nextChild = firstChildOf(oe); resolveDeltas(visit.next(), typeCode, new ObjectTypeAndSize(), progress); @@ -857,7 +916,7 @@ private void growEntries(int extraObjects) { final PackedObjectInfo[] ne; - ne = new PackedObjectInfo[(int) objectCount + extraObjects]; + ne = new PackedObjectInfo[(int) expectedObjectCount + extraObjects]; System.arraycopy(entries, 0, ne, 0, entryCount); entries = ne; } @@ -880,9 +939,9 @@ if (vers != 2 && vers != 3) throw new IOException(MessageFormat.format( JGitText.get().unsupportedPackVersion, Long.valueOf(vers))); - objectCount = NB.decodeUInt32(buf, p + 8); + final long objectCount = NB.decodeUInt32(buf, p + 8); use(hdrln); - + setExpectedObjectCount(objectCount); onPackHeader(objectCount); } @@ -919,6 +978,7 @@ // Cleanup all resources associated with our input parsing. private void endInput() { + stats.setNumBytesRead(streamPosition()); in = null; } @@ -947,12 +1007,14 @@ case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: + stats.addWholeObject(typeCode); onBeginWholeObject(streamPosition, typeCode, sz); onObjectHeader(Source.INPUT, hdrBuf, 0, hdrPtr); whole(streamPosition, typeCode, sz); break; case Constants.OBJ_OFS_DELTA: { + stats.addOffsetDelta(); c = readFrom(Source.INPUT); hdrBuf[hdrPtr++] = (byte) c; long ofs = c & 127; @@ -975,6 +1037,7 @@ } case Constants.OBJ_REF_DELTA: { + stats.addRefDelta(); c = fill(Source.INPUT, 20); final ObjectId base = ObjectId.fromRaw(buf, c); System.arraycopy(buf, c, hdrBuf, hdrPtr, 20); @@ -1004,115 +1067,147 @@ private void whole(final long pos, final int type, final long sz) throws IOException { + SHA1 objectDigest = objectHasher.reset(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(sz)); objectDigest.update((byte) 0); final byte[] data; - boolean checkContentLater = false; if (type == Constants.OBJ_BLOB) { byte[] readBuffer = buffer(); InputStream inf = inflate(Source.INPUT, sz); + BlobObjectChecker checker = null; + if (objCheck != null) { + checker = objCheck.newBlobObjectChecker(); + } + if (checker == null) { + checker = BlobObjectChecker.NULL_CHECKER; + } long cnt = 0; while (cnt < sz) { int r = inf.read(readBuffer); if (r <= 0) break; objectDigest.update(readBuffer, 0, r); + checker.update(readBuffer, 0, r); cnt += r; } inf.close(); - tempObjectId.fromRaw(objectDigest.digest(), 0); - checkContentLater = isCheckObjectCollisions() - && readCurs.has(tempObjectId); + objectDigest.digest(tempObjectId); + checker.endBlob(tempObjectId); data = null; - } else { data = inflateAndReturn(Source.INPUT, sz); objectDigest.update(data); - tempObjectId.fromRaw(objectDigest.digest(), 0); + objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, data); } PackedObjectInfo obj = newInfo(tempObjectId, null, null); obj.setOffset(pos); + obj.setType(type); onEndWholeObject(obj); if (data != null) onInflatedObjectData(obj, type, data); addObjectAndTrack(obj); - if (checkContentLater) - deferredCheckBlobs.add(obj); + + if (isCheckObjectCollisions()) { + collisionCheckObjs.add(obj); + } } - private void verifySafeObject(final AnyObjectId id, final int type, - final byte[] data) throws IOException { + /** + * Verify the integrity of the object. + * + * @param id + * identity of the object to be checked. + * @param type + * the type of the object. + * @param data + * raw content of the object. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * @since 4.9 + */ + protected void verifySafeObject(final AnyObjectId id, final int type, + final byte[] data) throws CorruptObjectException { if (objCheck != null) { try { - objCheck.check(type, data); + objCheck.check(id, type, data); } catch (CorruptObjectException e) { - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().invalidObject, - Constants.typeString(type), - readCurs.abbreviate(id, 10).name(), - e.getMessage()), e); + if (e.getErrorType() != null) { + throw e; + } + throw new CorruptObjectException( + MessageFormat.format(JGitText.get().invalidObject, + Constants.typeString(type), id.name(), + e.getMessage()), + e); } } + } - 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. + private void checkObjectCollision() throws IOException { + for (PackedObjectInfo obj : collisionCheckObjs) { + if (!readCurs.has(obj)) { + continue; } + checkObjectCollision(obj); } } - private void doDeferredCheckBlobs() throws IOException { + private void checkObjectCollision(PackedObjectInfo obj) + throws IOException { + ObjectTypeAndSize info = openDatabase(obj, new ObjectTypeAndSize()); final byte[] readBuffer = buffer(); final byte[] curBuffer = new byte[readBuffer.length]; - ObjectTypeAndSize info = new ObjectTypeAndSize(); - - for (PackedObjectInfo obj : deferredCheckBlobs) { - info = openDatabase(obj, info); - - if (info.type != Constants.OBJ_BLOB) + long sz = info.size; + InputStream pck = null; + try (ObjectStream cur = readCurs.open(obj, info.type).openStream()) { + if (cur.getSize() != sz) { throw new IOException(MessageFormat.format( - JGitText.get().unknownObjectType, - Integer.valueOf(info.type))); - - ObjectStream cur = readCurs.open(obj, info.type).openStream(); - try { - long sz = info.size; - if (cur.getSize() != sz) - throw new IOException(MessageFormat.format( - JGitText.get().collisionOn, obj.name())); - InputStream pck = inflate(Source.DATABASE, sz); - while (0 < sz) { - int n = (int) Math.min(readBuffer.length, sz); - IO.readFully(cur, curBuffer, 0, n); - IO.readFully(pck, readBuffer, 0, n); - for (int i = 0; i < n; i++) { - if (curBuffer[i] != readBuffer[i]) - throw new IOException(MessageFormat.format(JGitText - .get().collisionOn, obj.name())); + JGitText.get().collisionOn, obj.name())); + } + pck = inflate(Source.DATABASE, sz); + while (0 < sz) { + int n = (int) Math.min(readBuffer.length, sz); + IO.readFully(cur, curBuffer, 0, n); + IO.readFully(pck, readBuffer, 0, n); + for (int i = 0; i < n; i++) { + if (curBuffer[i] != readBuffer[i]) { + throw new IOException(MessageFormat.format(JGitText + .get().collisionOn, obj.name())); } - sz -= n; } + sz -= n; + } + } 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. + } finally { + if (pck != null) { pck.close(); - } finally { - cur.close(); } } } + private void checkObjectCollision(AnyObjectId obj, int type, byte[] data) + throws IOException { + try { + final ObjectLoader ldr = readCurs.open(obj, type); + final byte[] existingData = ldr.getCachedBytes(data.length); + if (!Arrays.equals(data, existingData)) { + throw new IOException(MessageFormat.format( + JGitText.get().collisionOn, obj.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. + } + } + /** @return current position of the input stream being parsed. */ private long streamPosition() { return bBase + bOffset; @@ -1141,13 +1236,13 @@ } // Consume cnt bytes from the buffer. - private void use(final int cnt) { + void use(final int cnt) { bOffset += cnt; bAvail -= cnt; } // Ensure at least need bytes are available in in {@link #buf}. - private int fill(final Source src, final int need) throws IOException { + int fill(final Source src, final int need) throws IOException { while (bAvail < need) { int next = bOffset + bAvail; int free = buf.length - next; @@ -1198,7 +1293,11 @@ bOffset = 0; } - /** @return a temporary byte array for use by the caller. */ + /** + * Get a temporary byte array for use by the caller. + * + * @return a temporary byte array for use by the caller. + */ protected byte[] buffer() { return tempBuffer; } @@ -1226,6 +1325,23 @@ } /** + * Set the expected number of objects in the pack stream. + *

      + * The object count in the pack header is not always correct for some Dfs + * pack files. e.g. INSERT pack always assume 1 object in the header since + * the actual object count is unknown when the pack is written. + *

      + * If external implementation wants to overwrite the expectedObjectCount, + * they should call this method during {@link #onPackHeader(long)}. + * + * @param expectedObjectCount a long. + * @since 4.9 + */ + protected void setExpectedObjectCount(long expectedObjectCount) { + this.expectedObjectCount = expectedObjectCount; + } + + /** * Store bytes received from the raw stream. *

      * This method is invoked during {@link #parse(ProgressMonitor)} as data is @@ -1243,7 +1359,7 @@ * first offset within the buffer that is valid. * @param len * number of bytes in the buffer that are valid. - * @throws IOException + * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onStoreStream(byte[] raw, int pos, int len) @@ -1263,7 +1379,7 @@ * first offset within buffer that is valid. * @param len * number of bytes in buffer that are valid. - * @throws IOException + * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onObjectHeader(Source src, byte[] raw, int pos, @@ -1286,7 +1402,7 @@ * first offset within buffer that is valid. * @param len * number of bytes in buffer that are valid. - * @throws IOException + * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onObjectData(Source src, byte[] raw, int pos, @@ -1301,7 +1417,7 @@ * the type of the object. * @param data * inflated data for the object. - * @throws IOException + * @throws java.io.IOException * the object cannot be archived. */ protected abstract void onInflatedObjectData(PackedObjectInfo obj, @@ -1312,7 +1428,7 @@ * * @param objCnt * number of objects expected in the stream. - * @throws IOException + * @throws java.io.IOException * the implementation refuses to work with this many objects. */ protected abstract void onPackHeader(long objCnt) throws IOException; @@ -1323,7 +1439,7 @@ * @param hash * the trailing 20 bytes of the pack, this is a SHA-1 checksum of * all of the pack data. - * @throws IOException + * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onPackFooter(byte[] hash) throws IOException; @@ -1346,7 +1462,7 @@ * @return true if the {@code info} should be included in the object list * returned by {@link #getSortedObjectList(Comparator)}, false if it * should not be included. - * @throws IOException + * @throws java.io.IOException * the base could not be included into the pack. */ protected abstract boolean onAppendBase(int typeCode, byte[] data, @@ -1359,7 +1475,7 @@ * external from the pack. The event is called after all of those deltas * have been resolved. * - * @throws IOException + * @throws java.io.IOException * the pack cannot be archived. */ protected abstract void onEndThinPack() throws IOException; @@ -1376,7 +1492,7 @@ * @param info * object to populate with type and size. * @return the {@code info} object. - * @throws IOException + * @throws java.io.IOException * the database cannot reposition to this location. */ protected abstract ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, @@ -1394,7 +1510,7 @@ * @param info * object to populate with type and size. * @return the {@code info} object. - * @throws IOException + * @throws java.io.IOException * the database cannot reposition to this location. */ protected abstract ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, @@ -1411,7 +1527,7 @@ * ideal target number of bytes to read. Actual read length may * be shorter. * @return number of bytes stored. - * @throws IOException + * @throws java.io.IOException * the database cannot be accessed. */ protected abstract int readDatabase(byte[] dst, int pos, int cnt) @@ -1441,13 +1557,15 @@ * @param streamPosition * position of this object in the incoming stream. * @param type - * type of the object; one of {@link Constants#OBJ_COMMIT}, - * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB}, or - * {@link Constants#OBJ_TAG}. + * type of the object; one of + * {@link org.eclipse.jgit.lib.Constants#OBJ_COMMIT}, + * {@link org.eclipse.jgit.lib.Constants#OBJ_TREE}, + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}, or + * {@link org.eclipse.jgit.lib.Constants#OBJ_TAG}. * @param inflatedSize * size of the object when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. - * @throws IOException + * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginWholeObject(long streamPosition, int type, @@ -1458,7 +1576,7 @@ * *@param info * object information. - * @throws IOException + * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onEndWholeObject(PackedObjectInfo info) @@ -1477,7 +1595,7 @@ * @param inflatedSize * size of the delta when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. - * @throws IOException + * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginOfsDelta(long deltaStreamPosition, @@ -1495,7 +1613,7 @@ * @param inflatedSize * size of the delta when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. - * @throws IOException + * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginRefDelta(long deltaStreamPosition, @@ -1506,7 +1624,7 @@ * *@return object information that must be populated with at least the * offset. - * @throws IOException + * @throws java.io.IOException * the object cannot be recorded. */ protected UnresolvedDelta onEndDelta() throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,8 @@ import java.util.List; /** - * {@link PostReceiveHook} that delegates to a list of other hooks. + * {@link org.eclipse.jgit.transport.PostReceiveHook} that delegates to a list + * of other hooks. *

      * Hooks are run in the order passed to the constructor. */ @@ -77,6 +78,8 @@ return new PostReceiveHookChain(newHooks, i); } + /** {@inheritDoc} */ + @Override public void onPostReceive(ReceivePack rp, Collection commands) { for (int i = 0; i < count; i++) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,11 +46,13 @@ import java.util.Collection; /** - * Hook invoked by {@link ReceivePack} after all updates are executed. + * Hook invoked by {@link org.eclipse.jgit.transport.ReceivePack} after all + * updates are executed. *

      * The hook is called after all commands have been processed. Only commands with - * a status of {@link ReceiveCommand.Result#OK} are passed into the hook. To get - * all commands within the hook, see {@link ReceivePack#getAllCommands()}. + * a status of {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK} are + * passed into the hook. To get all commands within the hook, see + * {@link org.eclipse.jgit.transport.ReceivePack#getAllCommands()}. *

      * Any post-receive hook implementation should not update the status of a * command, as the command has already completed or failed, and the status has @@ -62,6 +64,7 @@ public interface PostReceiveHook { /** A simple no-op hook. */ public static final PostReceiveHook NULL = new PostReceiveHook() { + @Override public void onPostReceive(final ReceivePack rp, final Collection commands) { // Do nothing. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,8 @@ import org.eclipse.jgit.storage.pack.PackStatistics; /** - * {@link PostUploadHook} that delegates to a list of other hooks. + * {@link org.eclipse.jgit.transport.PostUploadHook} that delegates to a list of + * other hooks. *

      * Hooks are run in the order passed to the constructor. * @@ -78,6 +79,8 @@ return new PostUploadHookChain(newHooks, i); } + /** {@inheritDoc} */ + @Override public void onPostUpload(PackStatistics stats) { for (int i = 0; i < count; i++) hooks[i].onPostUpload(stats); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,11 +42,11 @@ 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. + * Hook invoked by {@link org.eclipse.jgit.transport.UploadPack} after the pack + * has been uploaded. *

      * Implementors of the interface are responsible for associating the current * thread to a particular connection, if they need to also include connection @@ -58,6 +58,7 @@ public interface PostUploadHook { /** A simple no-op hook. */ public static final PostUploadHook NULL = new PostUploadHook() { + @Override public void onPostUpload(PackStatistics stats) { // Do nothing. } @@ -67,8 +68,9 @@ * Notifies the hook that a pack has been sent. * * @param stats - * the statistics gathered by {@link PackWriter} for the uploaded - * pack + * the statistics gathered by + * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} for + * the uploaded pack */ public void onPostUpload(PackStatistics stats); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,8 @@ import java.util.List; /** - * {@link PreReceiveHook} that delegates to a list of other hooks. + * {@link org.eclipse.jgit.transport.PreReceiveHook} that delegates to a list of + * other hooks. *

      * Hooks are run in the order passed to the constructor. */ @@ -76,6 +77,8 @@ return new PreReceiveHookChain(newHooks, i); } + /** {@inheritDoc} */ + @Override public void onPreReceive(ReceivePack rp, Collection commands) { for (int i = 0; i < count; i++) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,8 @@ import java.util.Collection; /** - * Hook invoked by {@link ReceivePack} before any updates are executed. + * Hook invoked by {@link org.eclipse.jgit.transport.ReceivePack} before any + * updates are executed. *

      * The hook is called with any commands that are deemed valid after parsing them * from the client and applying the standard receive configuration options to @@ -57,27 +58,29 @@ *

    * This means the hook will not receive a non-fast-forward update command if * denyNonFastForwards is set to true in the configuration file. To get all - * commands within the hook, see {@link ReceivePack#getAllCommands()}. + * commands within the hook, see + * {@link org.eclipse.jgit.transport.ReceivePack#getAllCommands()}. *

    * As the hook is invoked prior to the commands being executed, the hook may * choose to block any command by setting its result status with - * {@link ReceiveCommand#setResult(ReceiveCommand.Result)}. + * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(ReceiveCommand.Result)}. *

    * The hook may also choose to perform the command itself (or merely pretend * that it has performed the command), by setting the result status to - * {@link ReceiveCommand.Result#OK}. + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}. *

    * Hooks should run quickly, as they block the caller thread and the client * process from completing. *

    * Hooks may send optional messages back to the client via methods on - * {@link ReceivePack}. Implementors should be aware that not all network - * transports support this output, so some (or all) messages may simply be - * discarded. These messages should be advisory only. + * {@link org.eclipse.jgit.transport.ReceivePack}. Implementors should be aware + * that not all network transports support this output, so some (or all) + * messages may simply be discarded. These messages should be advisory only. */ public interface PreReceiveHook { /** A simple no-op hook. */ public static final PreReceiveHook NULL = new PreReceiveHook() { + @Override public void onPreReceive(final ReceivePack rp, final Collection commands) { // Do nothing. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,8 @@ import org.eclipse.jgit.lib.ObjectId; /** - * {@link PreUploadHook} that delegates to a list of other hooks. + * {@link org.eclipse.jgit.transport.PreUploadHook} that delegates to a list of + * other hooks. *

    * Hooks are run in the order passed to the constructor. If running a method on * one hook throws an exception, execution of remaining hook methods is aborted. @@ -79,6 +80,8 @@ return new PreUploadHookChain(newHooks, i); } + /** {@inheritDoc} */ + @Override public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) throws ServiceMayNotContinueException { @@ -86,6 +89,8 @@ hooks[i].onBeginNegotiateRound(up, wants, cntOffered); } + /** {@inheritDoc} */ + @Override public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) @@ -94,6 +99,8 @@ hooks[i].onEndNegotiateRound(up, wants, cntCommon, cntNotFound, ready); } + /** {@inheritDoc} */ + @Override public void onSendPack(UploadPack up, Collection wants, Collection haves) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,9 +48,11 @@ import org.eclipse.jgit.lib.ObjectId; /** - * Hook invoked by {@link UploadPack} before during critical phases. + * Hook invoked by {@link org.eclipse.jgit.transport.UploadPack} before during + * critical phases. *

    - * If any hook function throws {@link ServiceMayNotContinueException} then + * If any hook function throws + * {@link org.eclipse.jgit.transport.ServiceMayNotContinueException} then * processing stops immediately and the exception is thrown up the call stack. * Most phases of UploadPack will try to report the exception's message text to * the end-user over the client's protocol connection. @@ -58,12 +60,14 @@ public interface PreUploadHook { /** A simple no-op hook. */ public static final PreUploadHook NULL = new PreUploadHook() { + @Override public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) throws ServiceMayNotContinueException { // Do nothing. } + @Override public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) @@ -71,6 +75,7 @@ // Do nothing. } + @Override public void onSendPack(UploadPack up, Collection wants, Collection haves) @@ -88,7 +93,7 @@ * the list of wanted objects. * @param cntOffered * number of objects the client has offered. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onBeginNegotiateRound(UploadPack up, @@ -112,7 +117,7 @@ * @param ready * true if a pack is ready to be sent (the commit graph was * successfully cut). - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onEndNegotiateRound(UploadPack up, @@ -133,7 +138,7 @@ * the list of common objects. Empty on an initial clone request. * These may be RevObject or RevCommit if the processed parsed * them. Implementors should not rely on the values being parsed. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onSendPack(UploadPack up, Collection wants, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,151 @@ +/* + * 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 java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.TimeUnit; + +/** + * A simple spinner connected to an {@code OutputStream}. + *

    + * This is class is not thread-safe. The update method may only be used from a + * single thread. Updates are sent only as frequently as {@link #update()} is + * invoked by the caller, and are capped at no more than 2 times per second by + * requiring at least 500 milliseconds between updates. + * + * @since 4.2 + */ +public class ProgressSpinner { + private static final long MIN_REFRESH_MILLIS = 500; + private static final char[] STATES = new char[] { '-', '\\', '|', '/' }; + + private final OutputStream out; + private String msg; + private int state; + private boolean write; + private boolean shown; + private long nextUpdateMillis; + + /** + * Initialize a new spinner. + * + * @param out + * where to send output to. + */ + public ProgressSpinner(OutputStream out) { + this.out = out; + this.write = true; + } + + /** + * Begin a time consuming task. + * + * @param title + * description of the task, suitable for human viewing. + * @param delay + * delay to wait before displaying anything at all. + * @param delayUnits + * unit for {@code delay}. + */ + public void beginTask(String title, long delay, TimeUnit delayUnits) { + msg = title; + state = 0; + shown = false; + + long now = System.currentTimeMillis(); + if (delay > 0) { + nextUpdateMillis = now + delayUnits.toMillis(delay); + } else { + send(now); + } + } + + /** + * Update the spinner if it is showing. + */ + public void update() { + long now = System.currentTimeMillis(); + if (now >= nextUpdateMillis) { + send(now); + state = (state + 1) % STATES.length; + } + } + + private void send(long now) { + StringBuilder buf = new StringBuilder(msg.length() + 16); + buf.append('\r').append(msg).append("... ("); //$NON-NLS-1$ + buf.append(STATES[state]); + buf.append(") "); //$NON-NLS-1$ + shown = true; + write(buf.toString()); + nextUpdateMillis = now + MIN_REFRESH_MILLIS; + } + + /** + * Denote the current task completed. + * + * @param result + * text to print after the task's title + * {@code "$title ... $result"}. + */ + public void endTask(String result) { + if (shown) { + write('\r' + msg + "... " + result + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void write(String s) { + if (write) { + try { + out.write(s.getBytes(UTF_8)); + out.flush(); + } catch (IOException e) { + write = false; + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,6 @@ 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; @@ -59,18 +58,19 @@ /** * Identity in a push certificate. *

    - * This is similar to a {@link PersonIdent} in that it contains a name, - * timestamp, and timezone offset, but differs in the following ways: + * This is similar to a {@link org.eclipse.jgit.lib.PersonIdent} in that it + * contains a name, timestamp, and timezone offset, but differs in the following + * ways: *

      *
    • It is always parsed from a UTF-8 string, rather than a raw commit - * buffer.
    • + * buffer. *
    • 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.
    • - *
    • 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.
    • + * 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. + *
    • 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.
    • *
    • *
    * @@ -80,18 +80,18 @@ /** * Parse an identity from a string. *

    - * 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. + * 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. *

    - * If either the timestamp or timezone offsets are missing, mimics {@link - * RawParseUtils#parsePersonIdent(String)} behavior and sets them both to - * zero. + * 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. + * @return a {@link org.eclipse.jgit.transport.PushCertificateIdent} object. */ public static PushCertificateIdent parse(String str) { MutableInteger p = new MutableInteger(); @@ -182,15 +182,21 @@ return raw; } - /** @return the OpenPGP User ID, which may be any string. */ + /** + * Get the OpenPGP User ID, which may be any string. + * + * @return the OpenPGP User ID, which may be any string. + */ public String getUserId() { return userId; } /** + * Get the name portion of the User ID. + * * @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. + * parsed by {@link #getEmailAddress()}, returns the full User ID + * with spaces trimmed. */ public String getName() { int nameEnd = userId.indexOf('<'); @@ -209,6 +215,8 @@ } /** + * Get the email portion of the User ID + * * @return the email portion of the User ID, if one was successfully parsed * from {@link #getUserId()}, or null. */ @@ -224,19 +232,28 @@ return userId.substring(emailBegin + 1, emailEnd); } - /** @return the timestamp of the identity. */ + /** + * Get the timestamp of the identity. + * + * @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. + * Get this person's declared time zone + * + * @return this person's declared time zone; null if the timezone is + * unknown. */ public TimeZone getTimeZone() { return PersonIdent.getTimeZone(tzOffset); } /** + * Get this person's declared time zone as minutes east of UTC. + * * @return this person's declared time zone as minutes east of UTC. If the * timezone is to the west of UTC it is negative. */ @@ -244,17 +261,20 @@ return tzOffset; } + /** {@inheritDoc} */ @Override public boolean equals(Object o) { return (o instanceof PushCertificateIdent) && raw.equals(((PushCertificateIdent) o).raw); } + /** {@inheritDoc} */ @Override public int hashCode() { return raw.hashCode(); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java 2019-09-03 12:37:49.000000000 +0000 @@ -132,6 +132,8 @@ } /** + * Get the certificate version string. + * * @return the certificate version string. * @since 4.1 */ @@ -140,6 +142,8 @@ } /** + * Get the raw line that signed the cert, as a string. + * * @return the raw line that signed the cert, as a string. * @since 4.0 */ @@ -148,6 +152,8 @@ } /** + * Get identity of the pusher who signed the cert. + * * @return identity of the pusher who signed the cert. * @since 4.1 */ @@ -156,6 +162,8 @@ } /** + * Get URL of the repository the push was originally sent to. + * * @return URL of the repository the push was originally sent to. * @since 4.0 */ @@ -164,6 +172,8 @@ } /** + * Get the raw nonce value that was presented by the pusher. + * * @return the raw nonce value that was presented by the pusher. * @since 4.1 */ @@ -172,6 +182,8 @@ } /** + * Get verification status of the nonce embedded in the certificate. + * * @return verification status of the nonce embedded in the certificate. * @since 4.0 */ @@ -180,6 +192,9 @@ } /** + * Get the list of commands as one string to be feed into the signature + * verifier. + * * @return the list of commands as one string to be feed into the signature * verifier. * @since 4.1 @@ -189,9 +204,11 @@ } /** + * Get the raw signature + * * @return the raw signature, consisting of the lines received between the - * lines {@code "----BEGIN GPG SIGNATURE-----\n"} and - * {@code "----END GPG SIGNATURE-----\n}", inclusive. + * lines {@code "----BEGIN GPG SIGNATURE-----\n"} and + * {@code "----END GPG SIGNATURE-----\n}", inclusive. * @since 4.0 */ public String getSignature() { @@ -199,6 +216,8 @@ } /** + * Get text payload of the certificate for the signature verifier. + * * @return text payload of the certificate for the signature verifier. * @since 4.1 */ @@ -207,8 +226,11 @@ } /** + * Get original text payload plus signature + * * @return original text payload plus signature; the final output will be - * valid as input to {@link PushCertificateParser#fromString(String)}. + * valid as input to + * {@link org.eclipse.jgit.transport.PushCertificateParser#fromString(String)}. * @since 4.1 */ public String toTextWithSignature() { @@ -233,11 +255,13 @@ return sb; } + /** {@inheritDoc} */ @Override public int hashCode() { return signature.hashCode(); } + /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (!(o instanceof PushCertificate)) { @@ -268,6 +292,7 @@ return true; } + /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + '[' diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -133,11 +133,12 @@ /** * Parse a push certificate from a reader. *

    - * Differences from the {@link PacketLineIn} receiver methods: + * Differences from the {@link org.eclipse.jgit.transport.PacketLineIn} + * receiver methods: *

      *
    • Does not use pkt-line framing.
    • *
    • Reads an entire cert in one call rather than depending on a loop in - * the caller.
    • + * the caller. *
    • Does not assume a {@code "push-cert-end"} line.
    • *
    * @@ -145,9 +146,9 @@ * 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 + * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. - * @throws IOException + * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ @@ -163,9 +164,9 @@ * @param str * input string. * @return the parsed certificate. - * @throws PackProtocolException + * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. - * @throws IOException + * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ @@ -207,6 +208,8 @@ private final List commands = new ArrayList<>(); /** + *

    Constructor for PushCertificateParser.

    + * * @param into * destination repository for the push. * @param cfg @@ -240,9 +243,9 @@ * 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 + * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. - * @throws IOException + * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ @@ -267,8 +270,11 @@ } /** - * @return the parsed certificate, or null if push certificates are disabled. - * @throws IOException + * Build the parsed certificate + * + * @return the parsed certificate, or null if push certificates are + * disabled. + * @throws java.io.IOException * if the push certificate has missing or invalid fields. * @since 4.1 */ @@ -285,6 +291,9 @@ } /** + * Whether the repository is configured to use signed pushes in this + * context. + * * @return if the repository is configured to use signed pushes in this * context. * @since 4.0 @@ -294,6 +303,9 @@ } /** + * Get the whole string for the nonce to be included into the capability + * advertisement + * * @return the whole string for the nonce to be included into the capability * advertisement, or null if push certificates are disabled. * @since 4.0 @@ -348,7 +360,7 @@ * {@code NonceGenerator} will allow for some time skew caused by * clients disconnected and reconnecting in the stateless smart * HTTP protocol. - * @throws IOException + * @throws java.io.IOException * if the certificate from the client is badly malformed or the * client disconnects before sending the entire certificate. * @since 4.0 @@ -410,7 +422,7 @@ * * @param pckIn * where we read the signature from. - * @throws IOException + * @throws java.io.IOException * if the signature is invalid. * @since 4.0 */ @@ -455,7 +467,7 @@ * @param line * the line read from the wire that produced this * command, with optional trailing newline already trimmed. - * @throws PackProtocolException + * @throws org.eclipse.jgit.errors.PackProtocolException * if the raw line cannot be parsed to a command. * @since 4.0 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,7 +65,6 @@ 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; @@ -107,11 +106,11 @@ Constants.R_REFS + "meta/push-certs"; //$NON-NLS-1$ private static class PendingCert { - private PushCertificate cert; - private PersonIdent ident; - private Collection matching; + PushCertificate cert; + PersonIdent ident; + Collection matching; - private PendingCert(PushCertificate cert, PersonIdent ident, + PendingCert(PushCertificate cert, PersonIdent ident, Collection matching) { this.cert = cert; this.ident = ident; @@ -121,8 +120,8 @@ private final Repository db; private final List pending; - private ObjectReader reader; - private RevCommit commit; + ObjectReader reader; + RevCommit commit; /** * Create a new store backed by the given repository. @@ -136,11 +135,14 @@ } /** + * {@inheritDoc} + *

    * Close resources opened by this store. *

    - * If {@link #get(String)} was called, closes the cached object reader created - * by that method. Does not close the underlying repository. + * If {@link #get(String)} was called, closes the cached object reader + * created by that method. Does not close the underlying repository. */ + @Override public void close() { if (reader != null) { reader.close(); @@ -160,7 +162,7 @@ * 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 + * @throws java.io.IOException * if a problem occurred reading the repository. */ public PushCertificate get(String refName) throws IOException { @@ -270,7 +272,7 @@ }; } - private void load() throws IOException { + void load() throws IOException { close(); reader = db.newObjectReader(); Ref ref = db.getRefDatabase().exactRef(REF_NAME); @@ -283,7 +285,7 @@ } } - private static PushCertificate read(TreeWalk tw) throws IOException { + static PushCertificate read(TreeWalk tw) throws IOException { if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) { return null; } @@ -298,20 +300,20 @@ /** * Put a certificate to be saved to the store. *

    - * 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. - *

    - * Pending certificates added to this method are not returned by {@link - * #get(String)} and {@link #getAll(String)} until after calling {@link - * #save()}. + * 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. + *

    + * 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()}. + * certificates are sorted by identity timestamp during + * {@link #save()}. */ public void put(PushCertificate cert, PersonIdent ident) { put(cert, ident, null); @@ -325,16 +327,16 @@ * list that exactly matches the old/new values mentioned in the push * certificate. *

    - * Pending certificates added to this method are not returned by {@link - * #get(String)} and {@link #getAll(String)} until after calling {@link - * #save()}. + * 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()}. + * 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. @@ -347,15 +349,15 @@ /** * Save pending certificates to the store. *

    - * One commit is created per certificate added with {@link - * #put(PushCertificate, PersonIdent)}, in order of identity timestamps, and - * a single ref update is performed. + * One commit is created per certificate added with + * {@link #put(PushCertificate, PersonIdent)}, in order of identity + * timestamps, and a single ref update is performed. *

    - * The pending list is cleared if and only the ref update fails, which allows - * for easy retries in case of lock failure. + * 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 + * @throws java.io.IOException * if there was an error reading from or writing to the * repository. */ @@ -384,18 +386,19 @@ /** * Save pending certificates to the store in an existing batch ref update. *

    - * 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. - *

    - * The cached ref value and pending list are not cleared. If the ref - * update succeeds, the caller is responsible for calling {@link #close()} - * and/or {@link #clear()}. + * 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. + *

    + * The cached ref value and pending list are not 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 + * @throws java.io.IOException * if there was an error reading from or writing to the * repository. */ @@ -448,13 +451,10 @@ } 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 DirCache.read(reader, commit.getTree()); } - return dc; + return DirCache.newInCore(); } private ObjectId saveCert(ObjectInserter inserter, DirCache dc, @@ -532,7 +532,7 @@ return TreeWalk.forPath(reader, pathName(refName), commit.getTree()); } - private static String pathName(String refName) { + static String pathName(String refName) { return refName + "@{cert}"; //$NON-NLS-1$ } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017, David Pursehouse + * 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.util.StringUtils; + +/** + * Push section of a Git configuration file. + * + * @since 4.9 + */ +public class PushConfig { + /** + * Config values for push.recurseSubmodules. + */ + public enum PushRecurseSubmodulesMode implements Config.ConfigEnum { + /** + * Verify that all submodule commits that changed in the revisions to be + * pushed are available on at least one remote of the submodule. + */ + CHECK("check"), //$NON-NLS-1$ + + /** + * All submodules that changed in the revisions to be pushed will be + * pushed. + */ + ON_DEMAND("on-demand"), //$NON-NLS-1$ + + /** Default behavior of ignoring submodules when pushing is retained. */ + NO("false"); //$NON-NLS-1$ + + private final String configValue; + + private PushRecurseSubmodulesMode(String configValue) { + this.configValue = configValue; + } + + @Override + public String toConfigValue() { + return configValue; + } + + @Override + public boolean matchConfigValue(String s) { + if (StringUtils.isEmptyOrNull(s)) { + return false; + } + s = s.replace('-', '_'); + return name().equalsIgnoreCase(s) + || configValue.equalsIgnoreCase(s); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,6 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** * Lists known refs from the remote and sends objects to the remote. @@ -60,9 +59,9 @@ * into the remote repository, as well as a way to modify the refs stored by the * remote repository. *

    - * Instances of a PushConnection must be created by a {@link Transport} that - * implements a specific object transfer protocol that both sides of the - * connection understand. + * Instances of a PushConnection must be created by a + * {@link org.eclipse.jgit.transport.Transport} that implements a specific + * object transfer protocol that both sides of the connection understand. *

    * PushConnection instances are not thread safe and may be accessed by only one * thread at a time. @@ -79,13 +78,14 @@ *

    *

    * Only one call per connection is allowed. Subsequent calls will result in - * {@link TransportException}. + * {@link org.eclipse.jgit.errors.TransportException}. *

    *

    * Implementation may use local repository to send a minimum set of objects * needed by remote repository in efficient way. - * {@link Transport#isPushThin()} should be honored if applicable. - * refUpdates should be filled with information about status of each update. + * {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be + * honored if applicable. refUpdates should be filled with information about + * status of each update. *

    * * @param monitor @@ -97,12 +97,16 @@ * map of remote refnames to remote refs update * specifications/statuses. Can't be empty. This indicate what * refs caller want to update on remote side. Only refs updates - * with {@link Status#NOT_ATTEMPTED} should passed. - * Implementation must ensure that and appropriate status with - * optional message should be set during call. No refUpdate with - * {@link Status#AWAITING_REPORT} or {@link Status#NOT_ATTEMPTED} + * with + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} + * should passed. Implementation must ensure that and appropriate + * status with optional message should be set during call. No + * refUpdate with + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#AWAITING_REPORT} + * or + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * can be leaved by implementation after return from this call. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * critical protocol error, or error on remote side, or * connection was already used for push - new connection must be @@ -121,13 +125,14 @@ *

    *

    * Only one call per connection is allowed. Subsequent calls will result in - * {@link TransportException}. + * {@link org.eclipse.jgit.errors.TransportException}. *

    *

    * Implementation may use local repository to send a minimum set of objects * needed by remote repository in efficient way. - * {@link Transport#isPushThin()} should be honored if applicable. - * refUpdates should be filled with information about status of each update. + * {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be + * honored if applicable. refUpdates should be filled with information about + * status of each update. *

    * * @param monitor @@ -139,14 +144,18 @@ * map of remote refnames to remote refs update * specifications/statuses. Can't be empty. This indicate what * refs caller want to update on remote side. Only refs updates - * with {@link Status#NOT_ATTEMPTED} should passed. - * Implementation must ensure that and appropriate status with - * optional message should be set during call. No refUpdate with - * {@link Status#AWAITING_REPORT} or {@link Status#NOT_ATTEMPTED} + * with + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} + * should passed. Implementation must ensure that and appropriate + * status with optional message should be set during call. No + * refUpdate with + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#AWAITING_REPORT} + * or + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * can be leaved by implementation after return from this call. * @param out * output stream to write sideband messages to - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * critical protocol error, or error on remote side, or * connection was already used for push - new connection must be diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,9 @@ import java.io.OutputStream; import java.text.MessageFormat; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.jgit.errors.MissingObjectException; @@ -86,6 +88,9 @@ /** an outputstream to write messages to */ private final OutputStream out; + /** A list of option strings associated with this push */ + private List pushOptions; + /** * Create process for specified transport and refs updates specification. * @@ -119,8 +124,9 @@ throws TransportException { this.walker = new RevWalk(transport.local); this.transport = transport; - this.toPush = new HashMap(); + this.toPush = new HashMap<>(); this.out = out; + this.pushOptions = transport.getPushOptions(); for (final RemoteRefUpdate rru : toPush) { if (this.toPush.put(rru.getRemoteName(), rru) != null) throw new TransportException(MessageFormat.format( @@ -183,11 +189,17 @@ private Map prepareRemoteUpdates() throws TransportException { - final Map result = new HashMap(); + boolean atomic = transport.isPushAtomic(); + final Map result = new HashMap<>(); for (final RemoteRefUpdate rru : toPush.values()) { final Ref advertisedRef = connection.getRef(rru.getRemoteName()); - final ObjectId advertisedOld = (advertisedRef == null ? ObjectId - .zeroId() : advertisedRef.getObjectId()); + ObjectId advertisedOld = null; + if (advertisedRef != null) { + advertisedOld = advertisedRef.getObjectId(); + } + if (advertisedOld == null) { + advertisedOld = ObjectId.zeroId(); + } if (rru.getNewObjectId().equals(advertisedOld)) { if (rru.isDelete()) { @@ -205,8 +217,14 @@ if (rru.isExpectingOldObjectId() && !rru.getExpectedOldObjectId().equals(advertisedOld)) { rru.setStatus(Status.REJECTED_REMOTE_CHANGED); + if (atomic) { + return rejectAll(); + } continue; } + if (!rru.isExpectingOldObjectId()) { + rru.setExpectedOldObjectId(advertisedOld); + } // create ref (hasn't existed on remote side) and delete ref // are always fast-forward commands, feasible at this level @@ -236,14 +254,28 @@ JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x); } rru.setFastForward(fastForward); - if (!fastForward && !rru.isForceUpdate()) + if (!fastForward && !rru.isForceUpdate()) { rru.setStatus(Status.REJECTED_NONFASTFORWARD); - else + if (atomic) { + return rejectAll(); + } + } else { result.put(rru.getRemoteName(), rru); + } } return result; } + private Map rejectAll() { + for (RemoteRefUpdate rru : toPush.values()) { + if (rru.getStatus() == Status.NOT_ATTEMPTED) { + rru.setStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); + rru.setMessage(JGitText.get().transactionAborted); + } + } + return Collections.emptyMap(); + } + private void modifyUpdatesForDryRun() { for (final RemoteRefUpdate rru : toPush.values()) if (rru.getStatus() == Status.NOT_ATTEMPTED) @@ -267,4 +299,14 @@ } } } + + /** + * Gets the list of option strings associated with this push. + * + * @return pushOptions + * @since 4.5 + */ + public List getPushOptions() { + return pushOptions; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,8 @@ /** * Result of push operation to the remote repository. Holding information of - * {@link OperationResult} and remote refs updates status. + * {@link org.eclipse.jgit.transport.OperationResult} and remote refs updates + * status. * * @see Transport#push(org.eclipse.jgit.lib.ProgressMonitor, Collection) */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,17 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -58,10 +64,11 @@ import org.eclipse.jgit.revwalk.RevWalk; /** - * A command being processed by {@link BaseReceivePack}. + * A command being processed by + * {@link org.eclipse.jgit.transport.BaseReceivePack}. *

    * This command instance roughly translates to the server side representation of - * the {@link RemoteRefUpdate} created by the client. + * the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client. */ public class ReceiveCommand { /** Type of operation requested. */ @@ -127,6 +134,31 @@ } /** + * Filter a collection of commands according to result. + * + * @param in + * commands to filter. + * @param want + * desired status to filter by. + * @return a copy of the command list containing only those commands with + * the desired status. + * @since 4.2 + */ + public static List filter(Iterable in, + Result want) { + List r; + if (in instanceof Collection) + r = new ArrayList<>(((Collection) in).size()); + else + r = new ArrayList<>(); + for (ReceiveCommand cmd : in) { + if (cmd.getResult() == want) + r.add(cmd); + } + return r; + } + + /** * Filter a list of commands according to result. * * @param commands @@ -138,115 +170,544 @@ * @since 2.0 */ public static List filter(List commands, - final Result want) { - List r = new ArrayList(commands.size()); - for (final ReceiveCommand cmd : commands) { - if (cmd.getResult() == want) - r.add(cmd); + Result want) { + return filter((Iterable) commands, want); + } + + /** + * Set unprocessed commands as failed due to transaction aborted. + *

    + * If a command is still + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} it + * will be set to + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}. + * + * @param commands + * commands to mark as failed. + * @since 4.2 + */ + public static void abort(Iterable commands) { + for (ReceiveCommand c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, + JGitText.get().transactionAborted); + } } - return r; + } + + /** + * Check whether a command failed due to transaction aborted. + * + * @param cmd + * command. + * @return whether the command failed due to transaction aborted, as in + * {@link #abort(Iterable)}. + * @since 4.9 + */ + public static boolean isTransactionAborted(ReceiveCommand cmd) { + return cmd.getResult() == REJECTED_OTHER_REASON + && cmd.getMessage().equals(JGitText.get().transactionAborted); + } + + /** + * Create a command to switch a reference from object to symbolic. + * + * @param oldId + * expected oldId. May be {@code zeroId} to create. + * @param newTarget + * new target; must begin with {@code "refs/"}. + * @param name + * name of the reference to make symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand link(@NonNull ObjectId oldId, + @NonNull String newTarget, @NonNull String name) { + return new ReceiveCommand(oldId, newTarget, name); + } + + /** + * Create a command to switch a symbolic reference's target. + * + * @param oldTarget + * expected old target. May be null to create. + * @param newTarget + * new target; must begin with {@code "refs/"}. + * @param name + * name of the reference to make symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand link(@Nullable String oldTarget, + @NonNull String newTarget, @NonNull String name) { + return new ReceiveCommand(oldTarget, newTarget, name); + } + + /** + * Create a command to switch a reference from symbolic to object. + * + * @param oldTarget + * expected old target. + * @param newId + * new object identifier. May be {@code zeroId()} to delete. + * @param name + * name of the reference to convert from symbolic. + * @return command instance. + * @since 4.10 + */ + public static ReceiveCommand unlink(@NonNull String oldTarget, + @NonNull ObjectId newId, @NonNull String name) { + return new ReceiveCommand(oldTarget, newId, name); } private final ObjectId oldId; + private final String oldSymref; + private final ObjectId newId; + private final String newSymref; + private final String name; private Type type; + private boolean typeIsCorrect; + private Ref ref; private Result status = Result.NOT_ATTEMPTED; private String message; - private boolean typeIsCorrect; + private boolean customRefLog; + + private String refLogMessage; + + private boolean refLogIncludeResult; + + private Boolean forceRefLog; /** - * Create a new command for {@link BaseReceivePack}. + * Create a new command for + * {@link org.eclipse.jgit.transport.BaseReceivePack}. * * @param oldId - * the old object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref creation. + * the expected old object id; must not be null. Use + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref creation. * @param newId * the new object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref deletion. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref deletion. * @param name * name of the ref being affected. */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } this.oldId = oldId; + this.oldSymref = null; this.newId = newId; + this.newSymref = null; this.name = name; type = Type.UPDATE; - if (ObjectId.zeroId().equals(oldId)) + if (ObjectId.zeroId().equals(oldId)) { type = Type.CREATE; - if (ObjectId.zeroId().equals(newId)) + } + if (ObjectId.zeroId().equals(newId)) { type = Type.DELETE; + } } /** - * Create a new command for {@link BaseReceivePack}. + * Create a new command for + * {@link org.eclipse.jgit.transport.BaseReceivePack}. * * @param oldId * the old object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref creation. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref creation. * @param newId * the new object id; must not be null. Use - * {@link ObjectId#zeroId()} to indicate a ref deletion. + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a + * ref deletion. * @param name * name of the ref being affected. * @param type - * type of the command. + * type of the command. Must be + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#CREATE} + * if {@code + * oldId} is zero, or + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#DELETE} + * if {@code newId} is zero. * @since 2.0 */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name, final Type type) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } this.oldId = oldId; + this.oldSymref = null; this.newId = newId; + this.newSymref = null; this.name = name; + switch (type) { + case CREATE: + if (!ObjectId.zeroId().equals(oldId)) { + throw new IllegalArgumentException( + JGitText.get().createRequiresZeroOldId); + } + break; + case DELETE: + if (!ObjectId.zeroId().equals(newId)) { + throw new IllegalArgumentException( + JGitText.get().deleteRequiresZeroNewId); + } + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + if (ObjectId.zeroId().equals(newId) + || ObjectId.zeroId().equals(oldId)) { + throw new IllegalArgumentException( + JGitText.get().updateRequiresOldIdAndNewId); + } + break; + default: + throw new IllegalStateException( + JGitText.get().enumValueNotSupported0); + } this.type = type; } - /** @return the old value the client thinks the ref has. */ + /** + * Create a command to switch a reference from object to symbolic. + * + * @param oldId + * the old object id; must not be null. Use + * {@link ObjectId#zeroId()} to indicate a ref creation. + * @param newSymref + * new target, must begin with {@code "refs/"}. Use {@code null} + * to indicate a ref deletion. + * @param name + * name of the reference to make symbolic. + * @since 4.10 + */ + private ReceiveCommand(ObjectId oldId, String newSymref, String name) { + if (oldId == null) { + throw new IllegalArgumentException( + JGitText.get().oldIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = oldId; + this.oldSymref = null; + this.newId = ObjectId.zeroId(); + this.newSymref = newSymref; + this.name = name; + if (AnyObjectId.equals(ObjectId.zeroId(), oldId)) { + type = Type.CREATE; + } else if (newSymref != null) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Create a command to switch a reference from symbolic to object. + * + * @param oldSymref + * expected old target. Use {@code null} to indicate a ref + * creation. + * @param newId + * the new object id; must not be null. Use + * {@link ObjectId#zeroId()} to indicate a ref deletion. + * @param name + * name of the reference to convert from symbolic. + * @since 4.10 + */ + private ReceiveCommand(String oldSymref, ObjectId newId, String name) { + if (newId == null) { + throw new IllegalArgumentException( + JGitText.get().newIdMustNotBeNull); + } + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = ObjectId.zeroId(); + this.oldSymref = oldSymref; + this.newId = newId; + this.newSymref = null; + this.name = name; + if (oldSymref == null) { + type = Type.CREATE; + } else if (!AnyObjectId.equals(ObjectId.zeroId(), newId)) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Create a command to switch a symbolic reference's target. + * + * @param oldTarget + * expected old target. Use {@code null} to indicate a ref + * creation. + * @param newTarget + * new target. Use {@code null} to indicate a ref deletion. + * @param name + * name of the reference to make symbolic. + * @since 4.10 + */ + private ReceiveCommand(@Nullable String oldTarget, String newTarget, String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().nameMustNotBeNullOrEmpty); + } + this.oldId = ObjectId.zeroId(); + this.oldSymref = oldTarget; + this.newId = ObjectId.zeroId(); + this.newSymref = newTarget; + this.name = name; + if (oldTarget == null) { + if (newTarget == null) { + throw new IllegalArgumentException( + JGitText.get().bothRefTargetsMustNotBeNull); + } + type = Type.CREATE; + } else if (newTarget != null) { + type = Type.UPDATE; + } else { + type = Type.DELETE; + } + typeIsCorrect = true; + } + + /** + * Get the old value the client thinks the ref has. + * + * @return the old value the client thinks the ref has. + */ public ObjectId getOldId() { return oldId; } - /** @return the requested new value for this ref. */ + /** + * Get expected old target for a symbolic reference. + * + * @return expected old target for a symbolic reference. + * @since 4.10 + */ + @Nullable + public String getOldSymref() { + return oldSymref; + } + + /** + * Get the requested new value for this ref. + * + * @return the requested new value for this ref. + */ public ObjectId getNewId() { return newId; } - /** @return the name of the ref being updated. */ + /** + * Get requested new target for a symbolic reference. + * + * @return requested new target for a symbolic reference. + * @since 4.10 + */ + @Nullable + public String getNewSymref() { + return newSymref; + } + + /** + * Get the name of the ref being updated. + * + * @return the name of the ref being updated. + */ public String getRefName() { return name; } - /** @return the type of this command; see {@link Type}. */ + /** + * Get the type of this command; see {@link Type}. + * + * @return the type of this command; see {@link Type}. + */ public Type getType() { return type; } - /** @return the ref, if this was advertised by the connection. */ + /** + * Get the ref, if this was advertised by the connection. + * + * @return the ref, if this was advertised by the connection. + */ public Ref getRef() { return ref; } - /** @return the current status code of this command. */ + /** + * Get the current status code of this command. + * + * @return the current status code of this command. + */ public Result getResult() { return status; } - /** @return the message associated with a failure status. */ + /** + * Get the message associated with a failure status. + * + * @return the message associated with a failure status. + */ public String getMessage() { return message; } /** + * Set the message to include in the reflog. + *

    + * Overrides the default set by {@code setRefLogMessage} on any containing + * {@link org.eclipse.jgit.lib.BatchRefUpdate}. + * + * @param msg + * the message to describe this change. If null and appendStatus is + * false, the reflog will not be updated. + * @param appendStatus + * true if the status of the ref change (fast-forward or + * forced-update) should be appended to the user supplied message. + * @since 4.9 + */ + public void setRefLogMessage(String msg, boolean appendStatus) { + customRefLog = true; + if (msg == null && !appendStatus) { + disableRefLog(); + } else if (msg == null && appendStatus) { + refLogMessage = ""; //$NON-NLS-1$ + refLogIncludeResult = true; + } else { + refLogMessage = msg; + refLogIncludeResult = appendStatus; + } + } + + /** + * Don't record this update in the ref's associated reflog. + *

    + * Equivalent to {@code setRefLogMessage(null, false)}. + * + * @since 4.9 + */ + public void disableRefLog() { + customRefLog = true; + refLogMessage = null; + refLogIncludeResult = false; + } + + /** + * Force writing a reflog for the updated ref. + * + * @param force whether to force. + * @since 4.9 + */ + public void setForceRefLog(boolean force) { + forceRefLog = Boolean.valueOf(force); + } + + /** + * Check whether this command has a custom reflog message setting that should + * override defaults in any containing + * {@link org.eclipse.jgit.lib.BatchRefUpdate}. + *

    + * Does not take into account whether {@code #setForceRefLog(boolean)} has + * been called. + * + * @return whether a custom reflog is set. + * @since 4.9 + */ + public boolean hasCustomRefLog() { + return customRefLog; + } + + /** + * Check whether log has been disabled by {@link #disableRefLog()}. + * + * @return true if disabled. + * @since 4.9 + */ + public boolean isRefLogDisabled() { + return refLogMessage == null; + } + + /** + * Get the message to include in the reflog. + * + * @return message the caller wants to include in the reflog; null if the + * update should not be logged. + * @since 4.9 + */ + @Nullable + public String getRefLogMessage() { + return refLogMessage; + } + + /** + * Check whether the reflog message should include the result of the update, + * such as fast-forward or force-update. + * + * @return true if the message should include the result. + * @since 4.9 + */ + public boolean isRefLogIncludingResult() { + return refLogIncludeResult; + } + + /** + * Check whether the reflog should be written regardless of repo defaults. + * + * @return whether force writing is enabled; {@code null} if + * {@code #setForceRefLog(boolean)} was never called. + * @since 4.9 + */ + @Nullable + public Boolean isForceRefLog() { + return forceRefLog; + } + + /** * Set the status of this command. * * @param s @@ -275,12 +736,13 @@ * If the command's current type is UPDATE, a merge test will be performed * using the supplied RevWalk to determine if {@link #getOldId()} is fully * merged into {@link #getNewId()}. If some commits are not merged the - * update type is changed to {@link Type#UPDATE_NONFASTFORWARD}. + * update type is changed to + * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD}. * * @param walk * an instance to perform the merge test with. The caller must * allocate and release this object. - * @throws IOException + * @throws java.io.IOException * either oldId or newId is not accessible in the repository * used by the RevWalk. This usually indicates data corruption, * and the command cannot be processed. @@ -310,8 +772,20 @@ */ public void execute(final BaseReceivePack rp) { try { - final RefUpdate ru = rp.getRepository().updateRef(getRefName()); + String expTarget = getOldSymref(); + boolean detach = getNewSymref() != null + || (type == Type.DELETE && expTarget != null); + RefUpdate ru = rp.getRepository().updateRef(getRefName(), detach); + if (expTarget != null) { + if (!ru.getRef().isSymbolic() || !ru.getRef().getTarget() + .getName().equals(expTarget)) { + setResult(Result.LOCK_FAILURE); + return; + } + } + ru.setRefLogIdent(rp.getRefLogIdent()); + ru.setRefLogMessage(refLogMessage, refLogIncludeResult); switch (getType()) { case DELETE: if (!ObjectId.zeroId().equals(getOldId())) { @@ -330,9 +804,13 @@ case UPDATE_NONFASTFORWARD: ru.setForceUpdate(rp.isAllowNonFastForwards()); ru.setExpectedOldObjectId(getOldId()); - ru.setNewObjectId(getNewId()); ru.setRefLogMessage("push", true); //$NON-NLS-1$ - setResult(ru.update(rp.getRevWalk())); + if (getNewSymref() != null) { + setResult(ru.link(getNewSymref())); + } else { + ru.setNewObjectId(getNewId()); + setResult(ru.update(rp.getRevWalk())); + } break; } } catch (IOException err) { @@ -385,6 +863,14 @@ setResult(Result.REJECTED_CURRENT_BRANCH); break; + case REJECTED_MISSING_OBJECT: + setResult(Result.REJECTED_MISSING_OBJECT); + break; + + case REJECTED_OTHER_REASON: + setResult(Result.REJECTED_OTHER_REASON); + break; + default: setResult(Result.REJECTED_OTHER_REASON, r.name()); break; @@ -396,6 +882,7 @@ JGitText.get().lockError, err.getMessage())); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2016, 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.Constants; + +/** + * Statistics about {@link org.eclipse.jgit.transport.PackParser}. + * + * @since 4.6 + */ +public class ReceivedPackStatistics { + private long numBytesRead; + + private long numWholeCommit; + private long numWholeTree; + private long numWholeBlob; + private long numWholeTag; + private long numOfsDelta; + private long numRefDelta; + + private long numDeltaCommit; + private long numDeltaTree; + private long numDeltaBlob; + private long numDeltaTag; + + /** + * Get number of bytes read from the input stream + * + * @return number of bytes read from the input stream + */ + public long getNumBytesRead() { + return numBytesRead; + } + + /** + * Get number of whole commit objects in the pack + * + * @return number of whole commit objects in the pack + */ + public long getNumWholeCommit() { + return numWholeCommit; + } + + /** + * Get number of whole tree objects in the pack + * + * @return number of whole tree objects in the pack + */ + public long getNumWholeTree() { + return numWholeTree; + } + + /** + * Get number of whole blob objects in the pack + * + * @return number of whole blob objects in the pack + */ + public long getNumWholeBlob() { + return numWholeBlob; + } + + /** + * Get number of whole tag objects in the pack + * + * @return number of whole tag objects in the pack + */ + public long getNumWholeTag() { + return numWholeTag; + } + + /** + * Get number of offset delta objects in the pack + * + * @return number of offset delta objects in the pack + */ + public long getNumOfsDelta() { + return numOfsDelta; + } + + /** + * Get number of ref delta objects in the pack + * + * @return number of ref delta objects in the pack + */ + public long getNumRefDelta() { + return numRefDelta; + } + + /** + * Get number of delta commit objects in the pack + * + * @return number of delta commit objects in the pack + */ + public long getNumDeltaCommit() { + return numDeltaCommit; + } + + /** + * Get number of delta tree objects in the pack + * + * @return number of delta tree objects in the pack + */ + public long getNumDeltaTree() { + return numDeltaTree; + } + + /** + * Get number of delta blob objects in the pack + * + * @return number of delta blob objects in the pack + */ + public long getNumDeltaBlob() { + return numDeltaBlob; + } + + /** + * Get number of delta tag objects in the pack + * + * @return number of delta tag objects in the pack + */ + public long getNumDeltaTag() { + return numDeltaTag; + } + + /** A builder for {@link ReceivedPackStatistics}. */ + public static class Builder { + private long numBytesRead; + + private long numWholeCommit; + private long numWholeTree; + private long numWholeBlob; + private long numWholeTag; + private long numOfsDelta; + private long numRefDelta; + + private long numDeltaCommit; + private long numDeltaTree; + private long numDeltaBlob; + private long numDeltaTag; + + /** + * @param numBytesRead number of bytes read from the input stream + * @return this + */ + public Builder setNumBytesRead(long numBytesRead) { + this.numBytesRead = numBytesRead; + return this; + } + + /** + * Increment a whole object count. + * + * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG + * @return this + */ + public Builder addWholeObject(int type) { + switch (type) { + case Constants.OBJ_COMMIT: + numWholeCommit++; + break; + case Constants.OBJ_TREE: + numWholeTree++; + break; + case Constants.OBJ_BLOB: + numWholeBlob++; + break; + case Constants.OBJ_TAG: + numWholeTag++; + break; + default: + throw new IllegalArgumentException( + type + " cannot be a whole object"); //$NON-NLS-1$ + } + return this; + } + + /** @return this */ + public Builder addOffsetDelta() { + numOfsDelta++; + return this; + } + + /** @return this */ + public Builder addRefDelta() { + numRefDelta++; + return this; + } + + /** + * Increment a delta object count. + * + * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG + * @return this + */ + public Builder addDeltaObject(int type) { + switch (type) { + case Constants.OBJ_COMMIT: + numDeltaCommit++; + break; + case Constants.OBJ_TREE: + numDeltaTree++; + break; + case Constants.OBJ_BLOB: + numDeltaBlob++; + break; + case Constants.OBJ_TAG: + numDeltaTag++; + break; + default: + throw new IllegalArgumentException( + "delta should be a delta to a whole object. " + //$NON-NLS-1$ + type + " cannot be a whole object"); //$NON-NLS-1$ + } + return this; + } + + ReceivedPackStatistics build() { + ReceivedPackStatistics s = new ReceivedPackStatistics(); + s.numBytesRead = numBytesRead; + s.numWholeCommit = numWholeCommit; + s.numWholeTree = numWholeTree; + s.numWholeBlob = numWholeBlob; + s.numWholeTag = numWholeTag; + s.numOfsDelta = numOfsDelta; + s.numRefDelta = numRefDelta; + s.numDeltaCommit = numDeltaCommit; + s.numDeltaTree = numDeltaTree; + s.numDeltaBlob = numDeltaBlob; + s.numDeltaTag = numDeltaTag; + return s; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,14 +44,21 @@ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.UnpackException; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; @@ -71,6 +78,10 @@ private boolean echoCommandFailures; + /** Whether the client intends to use push options. */ + private boolean usePushOptions; + private List pushOptions; + /** * Create a new pack receive for an open repository. * @@ -83,7 +94,47 @@ postReceive = PostReceiveHook.NULL; } - /** @return the hook invoked before updates occur. */ + /** + * Gets an unmodifiable view of the option strings associated with the push. + * + * @return an unmodifiable view of pushOptions, or null (if pushOptions is). + * @since 4.5 + */ + @Nullable + public List getPushOptions() { + if (isAllowPushOptions() && usePushOptions) { + return Collections.unmodifiableList(pushOptions); + } + + // The client doesn't support push options. Return null to + // distinguish this from the case where the client declared support + // for push options and sent an empty list of them. + return null; + } + + /** + * Set the push options supplied by the client. + *

    + * Should only be called if reconstructing an instance without going through + * the normal {@link #recvCommands()} flow. + * + * @param options + * the list of options supplied by the client. The + * {@code ReceivePack} instance takes ownership of this list. + * Callers are encouraged to first create a copy if the list may + * be modified later. + * @since 4.5 + */ + public void setPushOptions(@Nullable List options) { + usePushOptions = options != null; + pushOptions = options; + } + + /** + * Get the hook invoked before updates occur. + * + * @return the hook invoked before updates occur. + */ public PreReceiveHook getPreReceiveHook() { return preReceive; } @@ -94,7 +145,8 @@ * Only valid commands (those which have no obvious errors according to the * received input and this instance's configuration) are passed into the * hook. The hook may mark a command with a result of any value other than - * {@link Result#NOT_ATTEMPTED} to block its execution. + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} to + * block its execution. *

    * The hook may be called with an empty command collection if the current * set is completely invalid. @@ -106,7 +158,11 @@ preReceive = h != null ? h : PreReceiveHook.NULL; } - /** @return the hook invoked after updates occur. */ + /** + * Get the hook invoked after updates occur. + * + * @return the hook invoked after updates occur. + */ public PostReceiveHook getPostReceiveHook() { return postReceive; } @@ -114,9 +170,10 @@ /** * Set the hook which is invoked after commands are executed. *

    - * Only successful commands (type is {@link Result#OK}) are passed into the - * hook. The hook may be called with an empty command collection if the - * current set all resulted in an error. + * Only successful commands (type is + * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}) are passed + * into the hook. The hook may be called with an empty command collection if + * the current set all resulted in an error. * * @param h * the hook instance; may be null to disable the hook. @@ -126,6 +183,9 @@ } /** + * Set whether this class will report command failures as warning messages + * before sending the command results. + * * @param echo * if true this class will report command failures as warning * messages before sending the command results. This is usually @@ -152,7 +212,7 @@ * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. - * @throws IOException + * @throws java.io.IOException */ public void receive(final InputStream input, final OutputStream output, final OutputStream messages) throws IOException { @@ -168,12 +228,28 @@ } } + /** {@inheritDoc} */ @Override protected void enableCapabilities() { reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS); + usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS); super.enableCapabilities(); } + @Override + void readPostCommands(PacketLineIn in) throws IOException { + if (usePushOptions) { + pushOptions = new ArrayList<>(4); + for (;;) { + String option = in.readString(); + if (option == PacketLineIn.END) { + break; + } + pushOptions.add(option); + } + } + } + private void service() throws IOException { if (isBiDirectionalPipe()) { sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); @@ -184,23 +260,19 @@ return; recvCommands(); if (hasCommands()) { - enableCapabilities(); - Throwable unpackError = null; if (needPack()) { try { receivePackAndCheckConnectivity(); - } catch (IOException err) { - unpackError = err; - } catch (RuntimeException err) { - unpackError = err; - } catch (Error err) { + } catch (IOException | RuntimeException | Error err) { unpackError = err; } } if (unpackError == null) { boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC); + setAtomic(atomic); + validateCommands(); if (atomic && anyRejects()) failPendingCommands(); @@ -215,6 +287,7 @@ if (reportStatus) { if (echoCommandFailures && msgOut != null) { sendStatusReport(false, unpackError, new Reporter() { + @Override void sendString(final String s) throws IOException { msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ } @@ -227,6 +300,7 @@ } } sendStatusReport(true, unpackError, new Reporter() { + @Override void sendString(final String s) throws IOException { pckOut.writeString(s + "\n"); //$NON-NLS-1$ } @@ -234,6 +308,7 @@ pckOut.end(); } else if (msgOut != null) { sendStatusReport(false, unpackError, new Reporter() { + @Override void sendString(final String s) throws IOException { msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ } @@ -251,9 +326,20 @@ throw new UnpackException(unpackError); } postReceive.onPostReceive(this, filterCommands(Result.OK)); + autoGc(); + } + } + + private void autoGc() { + Repository repo = getRepository(); + if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION, + ConfigConstants.CONFIG_KEY_AUTOGC, true)) { + return; } + repo.autoGC(NullProgressMonitor.INSTANCE); } + /** {@inheritDoc} */ @Override protected String getLockMessageProcessName() { return "jgit receive-pack"; //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,9 +43,16 @@ package org.eclipse.jgit.transport; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; @@ -60,12 +67,22 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.RefMap; -/** Support for the start of {@link UploadPack} and {@link ReceivePack}. */ +/** + * Support for the start of {@link org.eclipse.jgit.transport.UploadPack} and + * {@link org.eclipse.jgit.transport.ReceivePack}. + */ public abstract class RefAdvertiser { /** Advertiser which frames lines in a {@link PacketLineOut} format. */ public static class PacketLineOutRefAdvertiser extends RefAdvertiser { + private final CharsetEncoder utf8 = UTF_8.newEncoder(); private final PacketLineOut pckOut; + private byte[] binArr = new byte[256]; + private ByteBuffer binBuf = ByteBuffer.wrap(binArr); + + private char[] chArr = new char[256]; + private CharBuffer chBuf = CharBuffer.wrap(chArr); + /** * Create a new advertiser for the supplied stream. * @@ -77,6 +94,64 @@ } @Override + public void advertiseId(AnyObjectId id, String refName) + throws IOException { + id.copyTo(binArr, 0); + binArr[OBJECT_ID_STRING_LENGTH] = ' '; + binBuf.position(OBJECT_ID_STRING_LENGTH + 1); + append(refName); + if (first) { + first = false; + if (!capablities.isEmpty()) { + append('\0'); + for (String cap : capablities) { + append(' '); + append(cap); + } + } + } + append('\n'); + pckOut.writePacket(binArr, 0, binBuf.position()); + } + + private void append(String str) throws CharacterCodingException { + int n = str.length(); + if (n > chArr.length) { + chArr = new char[n + 256]; + chBuf = CharBuffer.wrap(chArr); + } + str.getChars(0, n, chArr, 0); + chBuf.position(0).limit(n); + utf8.reset(); + for (;;) { + CoderResult cr = utf8.encode(chBuf, binBuf, true); + if (cr.isOverflow()) { + grow(); + } else if (cr.isUnderflow()) { + break; + } else { + cr.throwException(); + } + } + } + + private void append(int b) { + if (!binBuf.hasRemaining()) { + grow(); + } + binBuf.put((byte) b); + } + + private void grow() { + int cnt = binBuf.position(); + byte[] tmp = new byte[binArr.length << 1]; + System.arraycopy(binArr, 0, tmp, 0, cnt); + binArr = tmp; + binBuf = ByteBuffer.wrap(binArr); + binBuf.position(cnt); + } + + @Override protected void writeOne(final CharSequence line) throws IOException { pckOut.writeString(line.toString()); } @@ -91,15 +166,15 @@ private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH]; - private final Set capablities = new LinkedHashSet(); + final Set capablities = new LinkedHashSet<>(); - private final Set sent = new HashSet(); + private final Set sent = new HashSet<>(); private Repository repository; private boolean derefTags; - private boolean first = true; + boolean first = true; /** * Initialize this advertiser with a repository for peeling tags. @@ -175,7 +250,6 @@ * The symbolic ref, e.g. "HEAD" * @param to * The real ref it points to, e.g. "refs/heads/master" - * * @since 3.6 */ public void addSymref(String from, String to) { @@ -190,7 +264,7 @@ * sorted before display if necessary, and therefore may appear * in any order. * @return set of ObjectIds that were advertised to the client. - * @throws IOException + * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ @@ -233,7 +307,7 @@ * * @param id * identity of the object that is assumed to exist. - * @throws IOException + * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ @@ -241,7 +315,11 @@ advertiseAnyOnce(id, ".have"); //$NON-NLS-1$ } - /** @return true if no advertisements have been sent yet. */ + /** + * Whether no advertisements have been sent yet. + * + * @return true if no advertisements have been sent yet. + */ public boolean isEmpty() { return first; } @@ -269,7 +347,7 @@ * @param refName * name of the reference to advertise the object as, can be any * string not including the NUL byte. - * @throws IOException + * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ @@ -300,7 +378,7 @@ * @param line * the advertisement line to be written. The line always ends * with LF. Never null or the empty string. - * @throws IOException + * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ @@ -309,7 +387,7 @@ /** * Mark the end of the advertisements. * - * @throws IOException + * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,15 +50,19 @@ /** * Filters the list of refs that are advertised to the client. *

    - * The filter is called by {@link ReceivePack} and {@link UploadPack} to ensure - * that the refs are filtered before they are advertised to the client. + * The filter is called by {@link org.eclipse.jgit.transport.ReceivePack} and + * {@link org.eclipse.jgit.transport.UploadPack} to ensure that the refs are + * filtered before they are advertised to the client. *

    * This can be used by applications to control visibility of certain refs based * on a custom set of rules. */ public interface RefFilter { - /** The default filter, allows all refs to be shown. */ + /** + * The default filter, allows all refs to be shown. + */ public static final RefFilter DEFAULT = new RefFilter() { + @Override public Map filter (final Map refs) { return refs; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 Two Sigma Open Source + * 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.Serializable; + +/** + * Describes the expected value for a ref being pushed. + * + * @since 4.7 + */ +public class RefLeaseSpec implements Serializable { + private static final long serialVersionUID = 1L; + + /** Name of the ref whose value we want to check. */ + private final String ref; + + /** Local commitish to get expected value from. */ + private final String expected; + + /** + *

    Constructor for RefLeaseSpec.

    + * + * @param ref + * ref being pushed + * @param expected + * the expected value of the ref + */ + public RefLeaseSpec(String ref, String expected) { + this.ref = ref; + this.expected = expected; + } + + /** + * Get the ref to protect. + * + * @return name of ref to check. + */ + public String getRef() { + return ref; + } + + /** + * Get the expected value of the ref, in the form + * of a local committish + * + * @return expected ref value. + */ + public String getExpected() { + return expected; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(getRef()); + r.append(':'); + r.append(getExpected()); + return r.toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java 2019-09-03 12:37:49.000000000 +0000 @@ -82,6 +82,30 @@ /** Is this specification actually a wildcard match? */ private boolean wildcard; + /** + * How strict to be about wildcards. + * + * @since 4.5 + */ + public enum WildcardMode { + /** + * Reject refspecs with an asterisk on the source side and not the + * destination side or vice versa. This is the mode used by FetchCommand + * and PushCommand to create a one-to-one mapping between source and + * destination refs. + */ + REQUIRE_MATCH, + /** + * Allow refspecs with an asterisk on only one side. This can create a + * many-to-one mapping between source and destination refs, so + * expandFromSource and expandFromDestination are not usable in this + * mode. + */ + ALLOW_MISMATCH + } + /** Whether a wildcard is allowed on one side but not the other. */ + private WildcardMode allowMismatchedWildcards; + /** Name of the ref(s) we would copy from. */ private String srcName; @@ -99,6 +123,7 @@ wildcard = false; srcName = Constants.HEAD; dstName = null; + allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH; } /** @@ -116,12 +141,24 @@ *
  • :refs/heads/master
  • * * + * If the wildcard mode allows mismatches, then these ref specs are also + * valid: + *
      + *
    • refs/heads/*
    • + *
    • refs/heads/*:refs/heads/master
    • + *
    + * * @param spec * string describing the specification. - * @throws IllegalArgumentException + * @param mode + * whether to allow a wildcard on one side without a wildcard on + * the other. + * @throws java.lang.IllegalArgumentException * the specification is invalid. + * @since 4.5 */ - public RefSpec(final String spec) { + public RefSpec(String spec, WildcardMode mode) { + this.allowMismatchedWildcards = mode; String s = spec; if (s.startsWith("+")) { //$NON-NLS-1$ force = true; @@ -131,8 +168,13 @@ final int c = s.lastIndexOf(':'); if (c == 0) { s = s.substring(1); - if (isWildcard(s)) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); + if (isWildcard(s)) { + wildcard = true; + if (mode == WildcardMode.REQUIRE_MATCH) { + throw new IllegalArgumentException(MessageFormat + .format(JGitText.get().invalidWildcards, spec)); + } + } dstName = checkValid(s); } else if (c > 0) { String src = s.substring(0, c); @@ -141,24 +183,55 @@ // Both contain wildcard wildcard = true; } else if (isWildcard(src) || isWildcard(dst)) { - // If either source or destination has wildcard, the other one - // must have as well. - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); + wildcard = true; + if (mode == WildcardMode.REQUIRE_MATCH) + throw new IllegalArgumentException(MessageFormat + .format(JGitText.get().invalidWildcards, spec)); } srcName = checkValid(src); dstName = checkValid(dst); } else { - if (isWildcard(s)) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); + if (isWildcard(s)) { + if (mode == WildcardMode.REQUIRE_MATCH) { + throw new IllegalArgumentException(MessageFormat + .format(JGitText.get().invalidWildcards, spec)); + } + wildcard = true; + } srcName = checkValid(s); } } + /** + * Parse a ref specification for use during transport operations. + *

    + * Specifications are typically one of the following forms: + *

      + *
    • refs/heads/master
    • + *
    • refs/heads/master:refs/remotes/origin/master
    • + *
    • refs/heads/*:refs/remotes/origin/*
    • + *
    • +refs/heads/master
    • + *
    • +refs/heads/master:refs/remotes/origin/master
    • + *
    • +refs/heads/*:refs/remotes/origin/*
    • + *
    • +refs/pull/*/head:refs/remotes/origin/pr/*
    • + *
    • :refs/heads/master
    • + *
    + * + * @param spec + * string describing the specification. + * @throws java.lang.IllegalArgumentException + * the specification is invalid. + */ + public RefSpec(final String spec) { + this(spec, WildcardMode.REQUIRE_MATCH); + } + private RefSpec(final RefSpec p) { force = p.isForceUpdate(); wildcard = p.isWildcard(); srcName = p.getSource(); dstName = p.getDestination(); + allowMismatchedWildcards = p.allowMismatchedWildcards; } /** @@ -215,7 +288,7 @@ * @param source * new value for source in the returned instance. * @return a new RefSpec with source as specified. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * There is already a destination configured, and the wildcard * status of the existing destination disagrees with the * wildcard status of the new source. @@ -254,7 +327,7 @@ * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * There is already a source configured, and the wildcard status * of the existing source disagrees with the wildcard status of * the new destination. @@ -277,7 +350,7 @@ * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * The wildcard status of the new source disagrees with the * wildcard status of the new destination. */ @@ -348,8 +421,15 @@ * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. + * @throws java.lang.IllegalStateException + * when the RefSpec was constructed with wildcard mode that + * doesn't require matching wildcards. */ public RefSpec expandFromSource(final String r) { + if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { + throw new IllegalStateException( + JGitText.get().invalidExpandWildcard); + } return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this; } @@ -373,6 +453,9 @@ * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. + * @throws java.lang.IllegalStateException + * when the RefSpec was constructed with wildcard mode that + * doesn't require matching wildcards. */ public RefSpec expandFromSource(final Ref r) { return expandFromSource(r.getName()); @@ -390,8 +473,15 @@ * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. + * @throws java.lang.IllegalStateException + * when the RefSpec was constructed with wildcard mode that + * doesn't require matching wildcards. */ public RefSpec expandFromDestination(final String r) { + if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { + throw new IllegalStateException( + JGitText.get().invalidExpandWildcard); + } return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this; } @@ -414,6 +504,9 @@ * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. + * @throws java.lang.IllegalStateException + * when the RefSpec was constructed with wildcard mode that + * doesn't require matching wildcards. */ public RefSpec expandFromDestination(final Ref r) { return expandFromDestination(r.getName()); @@ -422,7 +515,7 @@ private boolean match(final String name, final String s) { if (s == null) return false; - if (isWildcard()) { + if (isWildcard(s)) { int wildcardIndex = s.indexOf('*'); String prefix = s.substring(0, wildcardIndex); String suffix = s.substring(wildcardIndex + 1); @@ -453,18 +546,18 @@ return false; if (s.contains("//")) //$NON-NLS-1$ return false; + if (s.endsWith("/")) //$NON-NLS-1$ + return false; int i = s.indexOf('*'); if (i != -1) { if (s.indexOf('*', i + 1) > i) return false; - if (i > 0 && s.charAt(i - 1) != '/') - return false; - if (i < s.length() - 1 && s.charAt(i + 1) != '/') - return false; } return true; } + /** {@inheritDoc} */ + @Override public int hashCode() { int hc = 0; if (getSource() != null) @@ -474,6 +567,8 @@ return hc; } + /** {@inheritDoc} */ + @Override public boolean equals(final Object obj) { if (!(obj instanceof RefSpec)) return false; @@ -497,6 +592,8 @@ return a.equals(b); } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder r = new StringBuilder(); if (isForceUpdate()) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -109,16 +109,16 @@ * @return all remotes configurations existing in provided repository * configuration. Returned configurations are ordered * lexicographically by names. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * one of the URIs within the remote's configuration is invalid. */ public static List getAllRemoteConfigs(final Config rc) throws URISyntaxException { - final List names = new ArrayList(rc + final List names = new ArrayList<>(rc .getSubsections(SECTION)); Collections.sort(names); - final List result = new ArrayList(names + final List result = new ArrayList<>(names .size()); for (final String name : names) result.add(new RemoteConfig(rc, name)); @@ -157,7 +157,7 @@ * The configuration must already be loaded into memory. * @param remoteName * subsection key indicating the name of this remote. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * one of the URIs within the remote's configuration is invalid. */ public RemoteConfig(final Config rc, final String remoteName) @@ -169,39 +169,50 @@ vlst = rc.getStringList(SECTION, name, KEY_URL); Map insteadOf = getReplacements(rc, KEY_INSTEADOF); - uris = new ArrayList(vlst.length); - for (final String s : vlst) + uris = new ArrayList<>(vlst.length); + for (final String s : vlst) { uris.add(new URIish(replaceUri(s, insteadOf))); - - Map pushInsteadOf = getReplacements(rc, - KEY_PUSHINSTEADOF); - vlst = rc.getStringList(SECTION, name, KEY_PUSHURL); - pushURIs = new ArrayList(vlst.length); - for (final String s : vlst) - pushURIs.add(new URIish(replaceUri(s, pushInsteadOf))); - - vlst = rc.getStringList(SECTION, name, KEY_FETCH); - fetch = new ArrayList(vlst.length); - for (final String s : vlst) - fetch.add(new RefSpec(s)); - - vlst = rc.getStringList(SECTION, name, KEY_PUSH); - push = new ArrayList(vlst.length); - for (final String s : vlst) - push.add(new RefSpec(s)); - + } + String[] plst = rc.getStringList(SECTION, name, KEY_PUSHURL); + pushURIs = new ArrayList<>(plst.length); + for (final String s : plst) { + pushURIs.add(new URIish(s)); + } + if (pushURIs.isEmpty()) { + // Would default to the uris. If we have pushinsteadof, we must + // supply rewritten push uris. + Map pushInsteadOf = getReplacements(rc, + KEY_PUSHINSTEADOF); + if (!pushInsteadOf.isEmpty()) { + for (String s : vlst) { + String replaced = replaceUri(s, pushInsteadOf); + if (!s.equals(replaced)) { + pushURIs.add(new URIish(replaced)); + } + } + } + } + fetch = rc.getRefSpecs(SECTION, name, KEY_FETCH); + push = rc.getRefSpecs(SECTION, name, KEY_PUSH); val = rc.getString(SECTION, name, KEY_UPLOADPACK); - if (val == null) + if (val == null) { val = DEFAULT_UPLOAD_PACK; + } uploadpack = val; val = rc.getString(SECTION, name, KEY_RECEIVEPACK); - if (val == null) + if (val == null) { val = DEFAULT_RECEIVE_PACK; + } receivepack = val; - val = rc.getString(SECTION, name, KEY_TAGOPT); - tagopt = TagOpt.fromOption(val); + try { + val = rc.getString(SECTION, name, KEY_TAGOPT); + tagopt = TagOpt.fromOption(val); + } catch (IllegalArgumentException e) { + // C git silently ignores invalid tagopt values. + tagopt = TagOpt.AUTO_FOLLOW; + } mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR); timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0); } @@ -213,7 +224,7 @@ * the configuration file to store ourselves into. */ public void update(final Config rc) { - final List vlst = new ArrayList(); + final List vlst = new ArrayList<>(); vlst.clear(); for (final URIish u : getURIs()) @@ -272,7 +283,7 @@ private Map getReplacements(final Config config, final String keyName) { - final Map replacements = new HashMap(); + final Map replacements = new HashMap<>(); for (String url : config.getSubsections(KEY_URL)) for (String insteadOf : config.getStringList(KEY_URL, url, keyName)) replacements.put(insteadOf, url); @@ -514,6 +525,9 @@ } /** + * Whether pushing to the remote automatically deletes remote refs which + * don't exist on the source side. + * * @return true if pushing to the remote automatically deletes remote refs * which don't exist on the source side. */ @@ -531,7 +545,11 @@ mirror = m; } - /** @return timeout (in seconds) before aborting an IO operation. */ + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @return timeout (in seconds) before aborting an IO operation. + */ public int getTimeout() { return timeout; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,17 +55,18 @@ /** * Represent request and status of a remote ref update. Specification is - * provided by client, while status is handled by {@link PushProcess} class, - * being read-only for client. + * provided by client, while status is handled by + * {@link org.eclipse.jgit.transport.PushProcess} class, being read-only for + * client. *

    * Client can create instances of this class directly, basing on user - * specification and advertised refs ({@link Connection} or through - * {@link Transport} helper methods. Apply this specification on remote - * repository using - * {@link Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)} + * specification and advertised refs + * ({@link org.eclipse.jgit.transport.Connection} or through + * {@link org.eclipse.jgit.transport.Transport} helper methods. Apply this + * specification on remote repository using + * {@link org.eclipse.jgit.transport.Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)} * method. *

    - * */ public class RemoteRefUpdate { /** @@ -125,7 +126,7 @@ OK; } - private final ObjectId expectedOldObjectId; + private ObjectId expectedOldObjectId; private final ObjectId newObjectId; @@ -149,16 +150,19 @@ /** * Construct remote ref update request by providing an update specification. - * Object is created with default {@link Status#NOT_ATTEMPTED} status and no - * message. + * Object is created with default + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} + * status and no message. * * @param localDb * local repository to push from. * @param srcRef * source revision - any string resolvable by - * {@link Repository#resolve(String)}. This resolves to the new - * object that the caller want remote ref to be after update. Use - * null or {@link ObjectId#zeroId()} string for delete request. + * {@link org.eclipse.jgit.lib.Repository#resolve(String)}. This + * resolves to the new object that the caller want remote ref to + * be after update. Use null or + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} string for + * delete request. * @param remoteName * full name of a remote ref to update, e.g. "refs/heads/master" * (no wildcard, no short name). @@ -176,13 +180,14 @@ * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side - * advertise. Use {@link ObjectId#zeroId()} when expecting no - * remote ref with this name. - * @throws IOException + * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} + * when expecting no remote ref with this name. + * @throws java.io.IOException * when I/O error occurred during creating - * {@link TrackingRefUpdate} for local tracking branch or srcRef - * can't be resolved to any object. - * @throws IllegalArgumentException + * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for + * local tracking branch or srcRef can't be resolved to any + * object. + * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(final Repository localDb, final String srcRef, @@ -196,8 +201,9 @@ /** * Construct remote ref update request by providing an update specification. - * Object is created with default {@link Status#NOT_ATTEMPTED} status and no - * message. + * Object is created with default + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} + * status and no message. * * @param localDb * local repository to push from. @@ -220,13 +226,14 @@ * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side - * advertise. Use {@link ObjectId#zeroId()} when expecting no - * remote ref with this name. - * @throws IOException + * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} + * when expecting no remote ref with this name. + * @throws java.io.IOException * when I/O error occurred during creating - * {@link TrackingRefUpdate} for local tracking branch or srcRef - * can't be resolved to any object. - * @throws IllegalArgumentException + * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for + * local tracking branch or srcRef can't be resolved to any + * object. + * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(final Repository localDb, final Ref srcRef, @@ -240,8 +247,9 @@ /** * Construct remote ref update request by providing an update specification. - * Object is created with default {@link Status#NOT_ATTEMPTED} status and no - * message. + * Object is created with default + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} + * status and no message. * * @param localDb * local repository to push from. @@ -250,7 +258,8 @@ * be used instead. * @param srcId * The new object that the caller wants remote ref to be after - * update. Use null or {@link ObjectId#zeroId()} for delete + * update. Use null or + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} for delete * request. * @param remoteName * full name of a remote ref to update, e.g. "refs/heads/master" @@ -269,13 +278,14 @@ * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side - * advertise. Use {@link ObjectId#zeroId()} when expecting no - * remote ref with this name. - * @throws IOException + * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} + * when expecting no remote ref with this name. + * @throws java.io.IOException * when I/O error occurred during creating - * {@link TrackingRefUpdate} for local tracking branch or srcRef - * can't be resolved to any object. - * @throws IllegalArgumentException + * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for + * local tracking branch or srcRef can't be resolved to any + * object. + * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(final Repository localDb, final String srcRef, @@ -333,10 +343,11 @@ * configuration base. * @param newExpectedOldObjectId * new expected object id value. - * @throws IOException + * @throws java.io.IOException * when I/O error occurred during creating - * {@link TrackingRefUpdate} for local tracking branch or srcRef - * of base object no longer can be resolved to any object. + * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for + * local tracking branch or srcRef of base object no longer can + * be resolved to any object. */ public RemoteRefUpdate(final RemoteRefUpdate base, final ObjectId newExpectedOldObjectId) throws IOException { @@ -346,6 +357,8 @@ } /** + * Get expected old object id + * * @return expectedOldObjectId required to be advertised by remote side, as * set in constructor; may be null. */ @@ -354,6 +367,9 @@ } /** + * Whether some object is required to be advertised by remote side, as set + * in constructor + * * @return true if some object is required to be advertised by remote side, * as set in constructor; false otherwise. */ @@ -362,6 +378,8 @@ } /** + * Get new object id + * * @return newObjectId for remote ref, as set in constructor. */ public ObjectId getNewObjectId() { @@ -369,6 +387,8 @@ } /** + * Whether this update is a deleting update + * * @return true if this update is deleting update; false otherwise. */ public boolean isDelete() { @@ -376,6 +396,8 @@ } /** + * Get name of remote ref to update + * * @return name of remote ref to update, as set in constructor. */ public String getRemoteName() { @@ -383,6 +405,8 @@ } /** + * Get tracking branch update if localName was set in constructor. + * * @return local tracking branch update if localName was set in constructor. */ public TrackingRefUpdate getTrackingRefUpdate() { @@ -390,9 +414,12 @@ } /** + * Get source revision as specified by user (in constructor) + * * @return source revision as specified by user (in constructor), could be - * any string parseable by {@link Repository#resolve(String)}; can - * be null if specified that way in constructor - this stands for + * any string parseable by + * {@link org.eclipse.jgit.lib.Repository#resolve(String)}; can be + * null if specified that way in constructor - this stands for * delete request. */ public String getSrcRef() { @@ -400,6 +427,8 @@ } /** + * Whether user specified a local tracking branch for remote update + * * @return true if user specified a local tracking branch for remote update; * false otherwise. */ @@ -408,6 +437,8 @@ } /** + * Whether this update is forced regardless of old remote ref object + * * @return true if this update is forced regardless of old remote ref * object; false otherwise. */ @@ -416,6 +447,8 @@ } /** + * Get status of remote ref update operation. + * * @return status of remote ref update operation. */ public Status getStatus() { @@ -424,7 +457,8 @@ /** * Check whether update was fast-forward. Note that this result is - * meaningful only after successful update (when status is {@link Status#OK}). + * meaningful only after successful update (when status is + * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#OK}). * * @return true if update was fast-forward; false otherwise. */ @@ -433,6 +467,9 @@ } /** + * Get message describing reasons of status when needed/possible; may be + * null. + * * @return message describing reasons of status when needed/possible; may be * null. */ @@ -440,6 +477,10 @@ return message; } + void setExpectedOldObjectId(ObjectId id) { + expectedOldObjectId = id; + } + void setStatus(final Status status) { this.status = status; } @@ -457,7 +498,7 @@ * * @param walk * walker used for checking update properties. - * @throws IOException + * @throws java.io.IOException * when I/O error occurred during update */ protected void updateTrackingRef(final RevWalk walk) throws IOException { @@ -467,6 +508,7 @@ trackingRefUpdate.setResult(localUpdate.update(walk)); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,17 +65,18 @@ * Generate a new remote process to execute the given command. This function * should also start execution and may need to create the streams prior to * execution. + * * @param commandName * command to execute * @param timeout * timeout value, in seconds, for command execution * @return a new remote process - * @throws IOException + * @throws java.io.IOException * may be thrown in several cases. For example, on problems * opening input or output streams or on problems connecting or * communicating with the remote host. For the latter two cases, * a TransportException may be thrown (a subclass of - * IOException). + * java.io.IOException). */ public Process exec(String commandName, int timeout) throws IOException; @@ -83,4 +84,4 @@ * Disconnect the remote session */ public void disconnect(); -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RequestNotYetReadException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RequestNotYetReadException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/RequestNotYetReadException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/RequestNotYetReadException.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,12 +51,16 @@ public class RequestNotYetReadException extends IllegalStateException { private static final long serialVersionUID = 1L; - /** Initialize with no message. */ + /** + * Initialize with no message. + */ public RequestNotYetReadException() { // Do not set a message. } /** + *

    Constructor for RequestNotYetReadException.

    + * * @param msg * a message explaining the state. This message should not * be shown to an end-user. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,10 +70,12 @@ private final Collection exportBase; - /** Initialize an empty file based resolver. */ + /** + * Initialize an empty file based resolver. + */ public FileResolver() { - exports = new ConcurrentHashMap(); - exportBase = new CopyOnWriteArrayList(); + exports = new ConcurrentHashMap<>(); + exportBase = new CopyOnWriteArrayList<>(); } /** @@ -91,6 +93,8 @@ setExportAll(exportAll); } + /** {@inheritDoc} */ + @Override public Repository open(final C req, final String name) throws RepositoryNotFoundException, ServiceNotEnabledException { if (isUnreasonableName(name)) @@ -147,6 +151,9 @@ } /** + * Whether git-daemon-export-ok is required to export a + * repository + * * @return false if git-daemon-export-ok is required to export * a repository; true if git-daemon-export-ok is * ignored. @@ -166,7 +173,7 @@ * If true, all repositories are available through the daemon, whether or * not git-daemon-export-ok exists. * - * @param export + * @param export a boolean. */ public void setExportAll(final boolean export) { exportAll = export; @@ -213,7 +220,7 @@ * @param db * the opened repository instance. * @return true if the repository is accessible; false if not. - * @throws IOException + * @throws java.io.IOException * the repository could not be accessed, the caller will claim * the repository does not exist. */ @@ -243,11 +250,11 @@ return true; // no absolute paths if (name.startsWith("../")) //$NON-NLS-1$ - return true; // no "l../etc/passwd" + return true; // no "l../etc/passwd" if (name.contains("/../")) //$NON-NLS-1$ - return true; // no "foo/../etc/passwd" + return true; // no "foo/../etc/passwd" if (name.contains("/./")) //$NON-NLS-1$ - return true; // "foo/./foo" is insane to ask + return true; // "foo/./foo" is insane to ask if (name.contains("//")) //$NON-NLS-1$ return true; // double slashes is sloppy, don't use it diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,14 +47,18 @@ import org.eclipse.jgit.transport.ReceivePack; /** - * Create and configure {@link ReceivePack} service instance. + * Create and configure {@link org.eclipse.jgit.transport.ReceivePack} service + * instance. * * @param * type of connection */ public interface ReceivePackFactory { - /** A factory disabling the ReceivePack service for all repositories */ + /** + * A factory disabling the ReceivePack service for all repositories + */ public static final ReceivePackFactory DISABLED = new ReceivePackFactory() { + @Override public ReceivePack create(Object req, Repository db) throws ServiceNotEnabledException { throw new ServiceNotEnabledException(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,14 +48,17 @@ import org.eclipse.jgit.transport.ServiceMayNotContinueException; /** - * Locate a Git {@link Repository} by name from the URL. + * Locate a Git {@link org.eclipse.jgit.lib.Repository} by name from the URL. * * @param * type of connection. */ public interface RepositoryResolver { - /** Resolver configured to open nothing. */ + /** + * Resolver configured to open nothing. + */ public static final RepositoryResolver NONE = new RepositoryResolver() { + @Override public Repository open(Object req, String name) throws RepositoryNotFoundException { throw new RepositoryNotFoundException(name); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,8 +56,12 @@ private static final long serialVersionUID = 1L; /** + * Constructor for ServiceNotAuthorizedException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} object. * @since 4.1 */ public ServiceNotAuthorizedException(String message, Throwable cause) { @@ -65,14 +69,19 @@ } /** + * Constructor for ServiceNotAuthorizedException. + * * @param message + * error message * @since 4.1 */ public ServiceNotAuthorizedException(String message) { super(message); } - /** Indicates that the requested service requires authentication. */ + /** + * Indicates that the requested service requires authentication. + */ public ServiceNotAuthorizedException() { super(JGitText.get().unauthorized); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,13 +45,19 @@ import org.eclipse.jgit.internal.JGitText; -/** Indicates the request service is not enabled on a repository. */ +/** + * Indicates the request service is not enabled on a repository. + */ public class ServiceNotEnabledException extends Exception { private static final long serialVersionUID = 1L; /** + * Constructor for ServiceNotEnabledException. + * * @param message + * error message * @param cause + * a {@link java.lang.Throwable} object. * @since 4.1 */ public ServiceNotEnabledException(String message, Throwable cause) { @@ -59,14 +65,19 @@ } /** + * Constructor for ServiceNotEnabledException. + * * @param message + * error message * @since 4.1 */ public ServiceNotEnabledException(String message) { super(message); } - /** Indicates the request service is not available. */ + /** + * Indicates the request service is not available. + */ public ServiceNotEnabledException() { super(JGitText.get().serviceNotEnabledNoName); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,14 +47,18 @@ import org.eclipse.jgit.transport.UploadPack; /** - * Create and configure {@link UploadPack} service instance. + * Create and configure {@link org.eclipse.jgit.transport.UploadPack} service + * instance. * * @param * the connection type */ public interface UploadPackFactory { - /** A factory disabling the UploadPack service for all repositories. */ + /** + * A factory disabling the UploadPack service for all repositories. + */ public static final UploadPackFactory DISABLED = new UploadPackFactory() { + @Override public UploadPack create(Object req, Repository db) throws ServiceNotEnabledException { throw new ServiceNotEnabledException(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,25 +53,50 @@ * @since 2.0 */ public class ServiceMayNotContinueException extends IOException { + private static final int FORBIDDEN = 403; private static final long serialVersionUID = 1L; + private final int statusCode; private boolean output; - /** Initialize with no message. */ + /** + * Initialize with no message. + */ public ServiceMayNotContinueException() { // Do not set a message. + statusCode = FORBIDDEN; } /** + *

    Constructor for ServiceMayNotContinueException.

    + * * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. */ public ServiceMayNotContinueException(String msg) { super(msg); + statusCode = FORBIDDEN; } /** + *

    Constructor for ServiceMayNotContinueException.

    + * + * @param msg + * a message explaining why it cannot continue. This message may + * be shown to an end-user. + * @param statusCode + * the HTTP status code. + * @since 4.5 + */ + public ServiceMayNotContinueException(String msg, int statusCode) { + super(msg); + this.statusCode = statusCode; + } + + /** + *

    Constructor for ServiceMayNotContinueException.

    + * * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. @@ -80,8 +105,26 @@ * @since 3.2 */ public ServiceMayNotContinueException(String msg, Throwable cause) { - super(msg); - initCause(cause); + super(msg, cause); + statusCode = FORBIDDEN; + } + + /** + *

    Constructor for ServiceMayNotContinueException.

    + * + * @param msg + * a message explaining why it cannot continue. This message may + * be shown to an end-user. + * @param cause + * the cause of the exception. + * @param statusCode + * the HTTP status code. + * @since 4.5 + */ + public ServiceMayNotContinueException( + String msg, Throwable cause, int statusCode) { + super(msg, cause); + this.statusCode = statusCode; } /** @@ -95,13 +138,29 @@ this(JGitText.get().internalServerError, cause); } - /** @return true if the message was already output to the client. */ + /** + * Whether the message was already output to the client. + * + * @return {@code true} if the message was already output to the client. + */ public boolean isOutput() { return output; } - /** Mark this message has being sent to the client. */ + /** + * Mark this message has being sent to the client. + */ public void setOutput() { output = true; } + + /** + * Get status code + * + * @return true if the message was already output to the client. + * @since 4.5 + */ + public int getStatusCode() { + return statusCode; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -76,14 +76,11 @@ * an unrecoverable error. * * @see SideBandOutputStream + * @since 4.11 */ -class SideBandInputStream extends InputStream { - private static final String PFX_REMOTE = JGitText.get().prefixRemote; - +public class SideBandInputStream extends InputStream { static final int CH_DATA = 1; - static final int CH_PROGRESS = 2; - static final int CH_ERROR = 3; private static Pattern P_UNBOUNDED = Pattern @@ -124,6 +121,7 @@ out = outputStream; } + /** {@inheritDoc} */ @Override public int read() throws IOException { needDataPacket(); @@ -133,6 +131,7 @@ return rawIn.read(); } + /** {@inheritDoc} */ @Override public int read(final byte[] b, int off, int len) throws IOException { int r = 0; @@ -174,7 +173,7 @@ continue; case CH_ERROR: eof = true; - throw new TransportException(PFX_REMOTE + readString(available)); + throw new TransportException(remote(readString(available))); default: throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidChannel, @@ -241,7 +240,18 @@ } private void beginTask(final int totalWorkUnits) { - monitor.beginTask(PFX_REMOTE + currentTask, totalWorkUnits); + monitor.beginTask(remote(currentTask), totalWorkUnits); + } + + private static String remote(String msg) { + String prefix = JGitText.get().prefixRemote; + StringBuilder r = new StringBuilder(prefix.length() + msg.length() + 1); + r.append(prefix); + if (prefix.length() > 0 && prefix.charAt(prefix.length() - 1) != ' ') { + r.append(' '); + } + r.append(msg); + return r.toString(); } private String readString(final int len) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -127,12 +127,14 @@ writeBuffer(); } + /** {@inheritDoc} */ @Override public void flush() throws IOException { flushBuffer(); out.flush(); } + /** {@inheritDoc} */ @Override public void write(final byte[] b, int off, int len) throws IOException { while (0 < len) { @@ -159,6 +161,7 @@ } } + /** {@inheritDoc} */ @Override public void write(final int b) throws IOException { if (cnt == buffer.length) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,7 @@ write = true; } + /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int workCurr) { StringBuilder s = new StringBuilder(); @@ -68,6 +69,7 @@ send(s); } + /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int workCurr) { StringBuilder s = new StringBuilder(); @@ -82,6 +84,7 @@ s.append(workCurr); } + /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) { StringBuilder s = new StringBuilder(); @@ -90,6 +93,7 @@ send(s); } + /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) { StringBuilder s = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,17 +54,15 @@ public class SignedPushConfig { /** Key for {@link Config#get(SectionParser)}. */ public static final SectionParser KEY = - new SectionParser() { - public SignedPushConfig parse(Config cfg) { - return new SignedPushConfig(cfg); - } - }; + SignedPushConfig::new; private String certNonceSeed; private int certNonceSlopLimit; private NonceGenerator nonceGenerator; - /** Create a new config with default values disabling push verification. */ + /** + * Create a new config with default values disabling push verification. + */ public SignedPushConfig() { } @@ -77,9 +75,10 @@ * Set the seed used by the nonce verifier. *

    * 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)}. + * using the default + * {@link org.eclipse.jgit.transport.HMACSHA1NonceGenerator} implementation, + * if a different implementation was not set using + * {@link #setNonceGenerator(NonceGenerator)}. * * @param seed * new seed value. @@ -88,7 +87,11 @@ certNonceSeed = seed; } - /** @return the configured seed. */ + /** + * Get the configured seed. + * + * @return the configured seed. + */ public String getCertNonceSeed() { return certNonceSeed; } @@ -105,18 +108,23 @@ certNonceSlopLimit = limit; } - /** @return the configured nonce slop limit. */ + /** + * Get the configured nonce slop limit. + * + * @return the configured nonce slop limit. + */ public int getCertNonceSlopLimit() { return certNonceSlopLimit; } /** - * Set the {@link NonceGenerator} used for signed pushes. + * Set the {@link org.eclipse.jgit.transport.NonceGenerator} used for signed + * pushes. *

    - * 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. + * 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 org.eclipse.jgit.transport.HMACSHA1NonceGenerator} even if + * {@link #setCertNonceSeed(String)} was called. * * @param generator * new nonce generator. @@ -126,12 +134,13 @@ } /** - * Get the {@link NonceGenerator} used for signed pushes. + * Get the {@link org.eclipse.jgit.transport.NonceGenerator} used for signed + * pushes. *

    * 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}. + * implementation, that will be returned. If no custom implementation was + * set but {@link #setCertNonceSeed(String)} was called, returns a + * newly-created {@link org.eclipse.jgit.transport.HMACSHA1NonceGenerator}. * * @return the configured nonce generator. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,9 +54,9 @@ * communicating with the end-user as well as reading their personal SSH * configuration settings, such as known hosts and private keys. *

    - * A {@link RemoteSession} must be returned to the factory that created it. - * Callers are encouraged to retain the SshSessionFactory for the duration of - * the period they are using the Session. + * A {@link org.eclipse.jgit.transport.RemoteSession} must be returned to the + * factory that created it. Callers are encouraged to retain the + * SshSessionFactory for the duration of the period they are using the Session. */ public abstract class SshSessionFactory { private static SshSessionFactory INSTANCE = new DefaultSshSessionFactory(); @@ -66,7 +66,7 @@ *

    * A factory is always available. By default the factory will read from the * user's $HOME/.ssh and assume OpenSSH compatibility. - * + * * @return factory the current factory for this JVM. */ public static SshSessionFactory getInstance() { @@ -106,7 +106,7 @@ * @param tms * Timeout value, in milliseconds. * @return a session that can contact the remote host. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the session could not be created. */ public abstract RemoteSession getSession(URIish uri, diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java 2019-09-03 12:37:49.000000000 +0000 @@ -99,7 +99,7 @@ * * @param factory * a factory to set, must not be null - * @throws IllegalStateException + * @throws java.lang.IllegalStateException * if session has been already created. */ public void setSshSessionFactory(SshSessionFactory factory) { @@ -112,7 +112,10 @@ } /** - * @return the SSH session factory that will be used for creating SSH sessions + * Get the SSH session factory + * + * @return the SSH session factory that will be used for creating SSH + * sessions */ public SshSessionFactory getSshSessionFactory() { return sch; @@ -120,9 +123,9 @@ /** * Get the default SSH session - * + * * @return a remote session - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * in case of error with opening SSH session */ protected RemoteSession getSession() throws TransportException { @@ -138,6 +141,7 @@ return sock; } + /** {@inheritDoc} */ @Override public void close() { if (sock != null) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,9 @@ import org.eclipse.jgit.internal.JGitText; -/** Specification of annotated tag behavior during fetch. */ +/** + * Specification of annotated tag behavior during fetch. + */ public enum TagOpt { /** * Automatically follow tags if we fetch the thing they point at. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,21 +54,23 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.BasePackFetchConnection.FetchConfig; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.UploadPackFactory; /** * Protocol for transport between manually-specified repositories in tests. *

    - * Remote repositories are registered using {@link #register(Object, - * Repository)}, after which they can be accessed using the returned URI. As - * this class provides both the client side (the protocol) and the server side, - * the caller is responsible for setting up and passing the connection context, - * whatever form that may take. + * Remote repositories are registered using + * {@link #register(Object, Repository)}, after which they can be accessed using + * the returned URI. As this class provides both the client side (the protocol) + * and the server side, the caller is responsible for setting up and passing the + * connection context, whatever form that may take. *

    * Unlike the other built-in protocols, which are automatically-registered * singletons, callers are expected to register/unregister specific protocol - * instances on demand with {@link Transport#register(TransportProtocol)}. + * instances on demand with + * {@link org.eclipse.jgit.transport.Transport#register(TransportProtocol)}. * * @param * the connection type @@ -77,45 +79,54 @@ public class TestProtocol extends TransportProtocol { private static final String SCHEME = "test"; //$NON-NLS-1$ + private static FetchConfig fetchConfig; + private class Handle { - private final C req; - private final Repository remote; + final C req; + final Repository remote; - private Handle(C req, Repository remote) { + Handle(C req, Repository remote) { this.req = req; this.remote = remote; } } - private final UploadPackFactory uploadPackFactory; - private final ReceivePackFactory receivePackFactory; + final UploadPackFactory uploadPackFactory; + final ReceivePackFactory receivePackFactory; private final HashMap handles; /** + * Constructor for TestProtocol. + * * @param uploadPackFactory - * factory for creating {@link UploadPack} used by all connections - * from this protocol instance. + * factory for creating + * {@link org.eclipse.jgit.transport.UploadPack} used by all + * connections from this protocol instance. * @param receivePackFactory - * factory for creating {@link ReceivePack} used by all connections - * from this protocol instance. + * factory for creating + * {@link org.eclipse.jgit.transport.ReceivePack} used by all + * connections from this protocol instance. */ public TestProtocol(UploadPackFactory uploadPackFactory, ReceivePackFactory receivePackFactory) { this.uploadPackFactory = uploadPackFactory; this.receivePackFactory = receivePackFactory; - this.handles = new HashMap(); + this.handles = new HashMap<>(); } + /** {@inheritDoc} */ @Override public String getName() { return JGitText.get().transportProtoTest; } + /** {@inheritDoc} */ @Override public Set getSchemes() { return Collections.singleton(SCHEME); } + /** {@inheritDoc} */ @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException { @@ -127,16 +138,22 @@ return new TransportInternal(local, uri, h); } + /** {@inheritDoc} */ @Override public Set getRequiredFields() { return EnumSet.of(URIishField.HOST, URIishField.PATH); } + /** {@inheritDoc} */ @Override public Set getOptionalFields() { return Collections.emptySet(); } + static void setFetchConfig(FetchConfig c) { + fetchConfig = c; + } + /** * Register a repository connection over the internal test protocol. * @@ -165,7 +182,7 @@ private class TransportInternal extends Transport implements PackTransport { private final Handle handle; - private TransportInternal(Repository local, URIish uri, Handle handle) { + TransportInternal(Repository local, URIish uri, Handle handle) { super(local, uri); this.handle = handle; } @@ -174,15 +191,21 @@ public FetchConnection openFetch() throws NotSupportedException, TransportException { handle.remote.incrementOpen(); - return new InternalFetchConnection( - this, uploadPackFactory, handle.req, handle.remote); + return new InternalFetchConnection(this, uploadPackFactory, + handle.req, handle.remote) { + @Override + FetchConfig getFetchConfig() { + return fetchConfig != null ? fetchConfig + : super.getFetchConfig(); + } + }; } @Override public PushConnection openPush() throws NotSupportedException, TransportException { handle.remote.incrementOpen(); - return new InternalPushConnection( + return new InternalPushConnection<>( this, receivePackFactory, handle.req, handle.remote); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,13 +49,15 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; -/** Update of a locally stored tracking branch. */ +/** + * Update of a locally stored tracking branch. + */ public class TrackingRefUpdate { private final String remoteName; - private final String localName; - private boolean forceUpdate; - private ObjectId oldObjectId; - private ObjectId newObjectId; + final String localName; + boolean forceUpdate; + ObjectId oldObjectId; + ObjectId newObjectId; private RefUpdate.Result result; private ReceiveCommand cmd; @@ -132,6 +134,8 @@ } /** + * Get this update wrapped by a ReceiveCommand. + * * @return this update wrapped by a ReceiveCommand. * @since 3.4 */ @@ -142,7 +146,7 @@ } final class Command extends ReceiveCommand { - private Command() { + Command() { super(oldObjectId, newObjectId, localName); } @@ -201,6 +205,7 @@ } } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,12 +43,20 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase; +import static org.eclipse.jgit.util.StringUtils.toLowerCase; + +import java.io.File; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.SystemReader; @@ -58,41 +66,89 @@ * parameters. */ public class TransferConfig { + private static final String FSCK = "fsck"; //$NON-NLS-1$ + /** Key for {@link Config#get(SectionParser)}. */ - public static final Config.SectionParser KEY = new SectionParser() { - public TransferConfig parse(final Config cfg) { - return new TransferConfig(cfg); - } - }; + public static final Config.SectionParser KEY = + TransferConfig::new; - private final boolean checkReceivedObjects; - private final boolean allowLeadingZeroFileMode; + /** + * A git configuration value for how to handle a fsck failure of a particular kind. + * Used in e.g. fsck.missingEmail. + * @since 4.9 + */ + public enum FsckMode { + /** + * Treat it as an error (the default). + */ + ERROR, + /** + * Issue a warning (in fact, jgit treats this like IGNORE, but git itself does warn). + */ + WARN, + /** + * Ignore the error. + */ + IGNORE; + } + + private final boolean fetchFsck; + private final boolean receiveFsck; + private final String fsckSkipList; + private final EnumSet ignore; private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; private final boolean safeForMacOS; private final boolean allowTipSha1InWant; private final boolean allowReachableSha1InWant; - private final String[] hideRefs; + final String[] hideRefs; TransferConfig(final Repository db) { this(db.getConfig()); } - private TransferConfig(final Config rc) { - checkReceivedObjects = rc.getBoolean( - "fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ - rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ - allowLeadingZeroFileMode = checkReceivedObjects - && rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowInvalidPersonIdent = checkReceivedObjects - && rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForWindows = checkReceivedObjects - && rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ + TransferConfig(final Config rc) { + boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ + fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ + receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ + fsckSkipList = rc.getString(FSCK, null, "skipList"); //$NON-NLS-1$ + allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); //$NON-NLS-1$ + safeForWindows = rc.getBoolean(FSCK, "safeForWindows", //$NON-NLS-1$ SystemReader.getInstance().isWindows()); - safeForMacOS = checkReceivedObjects - && rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$ + safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", //$NON-NLS-1$ SystemReader.getInstance().isMacOS()); + ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class); + EnumSet set = EnumSet + .noneOf(ObjectChecker.ErrorType.class); + for (String key : rc.getNames(FSCK)) { + if (equalsIgnoreCase(key, "skipList") //$NON-NLS-1$ + || equalsIgnoreCase(key, "allowLeadingZeroFileMode") //$NON-NLS-1$ + || equalsIgnoreCase(key, "allowInvalidPersonIdent") //$NON-NLS-1$ + || equalsIgnoreCase(key, "safeForWindows") //$NON-NLS-1$ + || equalsIgnoreCase(key, "safeForMacOS")) { //$NON-NLS-1$ + continue; + } + + ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key); + if (id != null) { + switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) { + case ERROR: + ignore.remove(id); + break; + case WARN: + case IGNORE: + ignore.add(id); + break; + } + set.add(id); + } + } + if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE) + && rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { //$NON-NLS-1$ + ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE); + } + allowTipSha1InWant = rc.getBoolean( "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ allowReachableSha1InWant = rc.getBoolean( @@ -101,21 +157,51 @@ } /** + * Create checker to verify fetched objects + * * @return checker to verify fetched objects, or null if checking is not * enabled in the repository configuration. * @since 3.6 */ + @Nullable public ObjectChecker newObjectChecker() { - if (!checkReceivedObjects) + return newObjectChecker(fetchFsck); + } + + /** + * Create checker to verify objects pushed into this repository + * + * @return checker to verify objects pushed into this repository, or null if + * checking is not enabled in the repository configuration. + * @since 4.2 + */ + @Nullable + public ObjectChecker newReceiveObjectChecker() { + return newObjectChecker(receiveFsck); + } + + private ObjectChecker newObjectChecker(boolean check) { + if (!check) { return null; + } return new ObjectChecker() - .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) + .setIgnore(ignore) .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) - .setSafeForMacOS(safeForMacOS); + .setSafeForMacOS(safeForMacOS) + .setSkipList(skipList()); + } + + private ObjectIdSet skipList() { + if (fsckSkipList != null && !fsckSkipList.isEmpty()) { + return new LazyObjectIdSetFile(new File(fsckSkipList)); + } + return null; } /** + * Whether to allow clients to request non-advertised tip SHA-1s + * * @return allow clients to request non-advertised tip SHA-1s? * @since 3.1 */ @@ -124,6 +210,8 @@ } /** + * Whether to allow clients to request non-tip SHA-1s + * * @return allow clients to request non-tip SHA-1s? * @since 4.1 */ @@ -132,7 +220,11 @@ } /** - * @return {@link RefFilter} respecting configured hidden refs. + * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured + * hidden refs. + * + * @return {@link org.eclipse.jgit.transport.RefFilter} respecting + * configured hidden refs. * @since 3.1 */ public RefFilter getRefFilter() { @@ -140,8 +232,9 @@ return RefFilter.DEFAULT; return new RefFilter() { + @Override public Map filter(Map refs) { - Map result = new HashMap(); + Map result = new HashMap<>(); for (Map.Entry e : refs.entrySet()) { boolean add = true; for (String hide : hideRefs) { @@ -161,4 +254,34 @@ } }; } + + static class FsckKeyNameHolder { + private static final Map errors; + + static { + errors = new HashMap<>(); + for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) { + errors.put(keyNameFor(m.name()), m); + } + } + + @Nullable + static ObjectChecker.ErrorType parse(String key) { + return errors.get(toLowerCase(key)); + } + + private static String keyNameFor(String name) { + StringBuilder r = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c != '_') { + r.append(c); + } + } + return toLowerCase(r.toString()); + } + + private FsckKeyNameHolder() { + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java 2019-09-03 12:37:49.000000000 +0000 @@ -86,8 +86,9 @@ * extended HTTP/1.1 semantics. This make it possible to read or write Git data * from a remote repository that is stored on S3. *

    - * Unlike the HTTP variant (see {@link TransportHttp}) we rely upon being able - * to list objects in a bucket, as the S3 API supports this function. By listing + * Unlike the HTTP variant (see + * {@link org.eclipse.jgit.transport.TransportHttp}) we rely upon being able to + * list objects in a bucket, as the S3 API supports this function. By listing * the bucket contents we can avoid relying on objects/info/packs * or info/refs in the remote repository. *

    @@ -101,23 +102,28 @@ static final String S3_SCHEME = "amazon-s3"; //$NON-NLS-1$ static final TransportProtocol PROTO_S3 = new TransportProtocol() { + @Override public String getName() { return "Amazon S3"; //$NON-NLS-1$ } + @Override public Set getSchemes() { return Collections.singleton(S3_SCHEME); } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PASS)); } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportAmazonS3(local, uri); @@ -125,10 +131,10 @@ }; /** User information necessary to connect to S3. */ - private final AmazonS3 s3; + final AmazonS3 s3; /** Bucket the remote repository is stored in. */ - private final String bucket; + final String bucket; /** * Key prefix which all objects related to the repository start with. @@ -148,8 +154,9 @@ super(local, uri); Properties props = loadProperties(); - if (!props.containsKey("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$ - props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$ + File directory = local.getDirectory(); + if (!props.containsKey("tmpdir") && directory != null) //$NON-NLS-1$ + props.put("tmpdir", directory.getPath()); //$NON-NLS-1$ s3 = new AmazonS3(props); bucket = uri.getHost(); @@ -195,6 +202,7 @@ } } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$ @@ -203,6 +211,7 @@ return r; } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws TransportException { final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$ @@ -211,6 +220,7 @@ return r; } + /** {@inheritDoc} */ @Override public void close() { // No explicit connections are maintained. @@ -264,10 +274,10 @@ @Override Collection getPackNames() throws IOException { - final HashSet have = new HashSet(); + final HashSet have = new HashSet<>(); have.addAll(s3.list(bucket, resolveKey("pack"))); //$NON-NLS-1$ - final Collection packs = new ArrayList(); + final Collection packs = new ArrayList<>(); for (final String n : have) { if (!n.startsWith("pack-") || !n.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ continue; @@ -306,7 +316,7 @@ } Map readAdvertisedRefs() throws TransportException { - final TreeMap avail = new TreeMap(); + final TreeMap avail = new TreeMap<>(); readPackedRefs(avail); readLooseRefs(avail); readRef(avail, Constants.HEAD); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,7 +66,7 @@ private final String[] schemeNames = { "bundle", "file" }; //$NON-NLS-1$ //$NON-NLS-2$ private final Set schemeSet = Collections - .unmodifiableSet(new LinkedHashSet(Arrays + .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); @Override @@ -74,6 +74,7 @@ return JGitText.get().transportProtoBundleFile; } + @Override public Set getSchemes() { return schemeSet; } @@ -94,7 +95,7 @@ public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException { if ("bundle".equals(uri.getScheme())) { //$NON-NLS-1$ - File path = local.getFS().resolve(new File("."), uri.getPath()); //$NON-NLS-1$ + File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$ return new TransportBundleFile(local, uri, path); } @@ -106,6 +107,7 @@ return TransportLocal.PROTO_LOCAL.open(uri, local, remoteName); } + @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { if ("bundle".equals(uri.getScheme())) { //$NON-NLS-1$ @@ -123,11 +125,20 @@ bundle = bundlePath; } + /** + * Constructor for TransportBundleFile. + * + * @param uri + * a {@link org.eclipse.jgit.transport.URIish} object. + * @param bundlePath + * transport bundle path + */ public TransportBundleFile(URIish uri, File bundlePath) { super(uri); bundle = bundlePath; } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws NotSupportedException, TransportException { @@ -140,12 +151,14 @@ return new BundleFetchConnection(this, src); } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws NotSupportedException { throw new NotSupportedException( JGitText.get().pushIsNotSupportedForBundleTransport); } + /** {@inheritDoc} */ @Override public void close() { // Resources must be established per-connection. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -90,6 +90,7 @@ src = in; } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { if (src == null) @@ -101,12 +102,14 @@ } } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws NotSupportedException { throw new NotSupportedException( JGitText.get().pushIsNotSupportedForBundleTransport); } + /** {@inheritDoc} */ @Override public void close() { if (src != null) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -62,7 +63,6 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * Transport through a git-daemon waiting for anonymous TCP connections. @@ -75,27 +75,33 @@ static final int GIT_PORT = Daemon.DEFAULT_PORT; static final TransportProtocol PROTO_GIT = new TransportProtocol() { + @Override public String getName() { return JGitText.get().transportProtoGitAnon; } + @Override public Set getSchemes() { return Collections.singleton("git"); //$NON-NLS-1$ } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PORT)); } + @Override public int getDefaultPort() { return GIT_PORT; } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportGitAnon(local, uri); @@ -115,16 +121,19 @@ super(uri); } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { return new TcpFetchConnection(); } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws TransportException { return new TcpPushConnection(); } + /** {@inheritDoc} */ @Override public void close() { // Resources must be established per-connection. @@ -182,7 +191,7 @@ OutputStream sOut = sock.getOutputStream(); sIn = new BufferedInputStream(sIn); - sOut = new SafeBufferedOutputStream(sOut); + sOut = new BufferedOutputStream(sOut); init(sIn, sOut); service("git-upload-pack", pckOut); //$NON-NLS-1$ @@ -221,7 +230,7 @@ OutputStream sOut = sock.getOutputStream(); sIn = new BufferedInputStream(sIn); - sOut = new SafeBufferedOutputStream(sOut); + sOut = new BufferedOutputStream(sOut); init(sIn, sOut); service("git-receive-pack", pckOut); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ package org.eclipse.jgit.transport; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; @@ -55,6 +56,7 @@ import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import org.eclipse.jgit.errors.NoRemoteRepositoryException; @@ -63,11 +65,11 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; -import org.eclipse.jgit.util.FS; /** * Transport through an SSH tunnel. @@ -85,27 +87,32 @@ private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ private final Set schemeSet = Collections - .unmodifiableSet(new LinkedHashSet(Arrays + .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); + @Override public String getName() { return JGitText.get().transportProtoSSH; } + @Override public Set getSchemes() { return schemeSet; } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } + @Override public int getDefaultPort() { return 22; } @@ -122,6 +129,7 @@ return super.canHandle(uri, local, remoteName); } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportGitSsh(local, uri); @@ -156,11 +164,13 @@ } } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { return new SshFetchConnection(); } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws TransportException { return new SshPushConnection(); @@ -213,14 +223,16 @@ } private class ExtSession implements RemoteSession { + @Override public Process exec(String command, int timeout) throws TransportException { String ssh = SystemReader.getInstance().getenv("GIT_SSH"); //$NON-NLS-1$ - boolean putty = ssh.toLowerCase().contains("plink"); //$NON-NLS-1$ + boolean putty = ssh.toLowerCase(Locale.ROOT).contains("plink"); //$NON-NLS-1$ - List args = new ArrayList(); + List args = new ArrayList<>(); args.add(ssh); - if (putty && !ssh.toLowerCase().contains("tortoiseplink")) //$NON-NLS-1$ + if (putty + && !ssh.toLowerCase(Locale.ROOT).contains("tortoiseplink")) //$NON-NLS-1$ args.add("-batch"); //$NON-NLS-1$ if (0 < getURI().getPort()) { args.add(putty ? "-P" : "-p"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -232,13 +244,7 @@ args.add(getURI().getHost()); args.add(command); - ProcessBuilder pb = new ProcessBuilder(); - pb.command(args); - - if (local.getDirectory() != null) - pb.environment().put(Constants.GIT_DIR_KEY, - local.getDirectory().getPath()); - + ProcessBuilder pb = createProcess(args); try { return pb.start(); } catch (IOException err) { @@ -246,6 +252,18 @@ } } + private ProcessBuilder createProcess(List args) { + ProcessBuilder pb = new ProcessBuilder(); + pb.command(args); + File directory = local != null ? local.getDirectory() : null; + if (directory != null) { + pb.environment().put(Constants.GIT_DIR_KEY, + directory.getPath()); + } + return pb; + } + + @Override public void disconnect() { // Nothing to do } @@ -273,7 +291,7 @@ } catch (TransportException err) { close(); throw err; - } catch (IOException err) { + } catch (Throwable err) { close(); throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); @@ -329,10 +347,18 @@ init(process.getInputStream(), process.getOutputStream()); } catch (TransportException err) { - close(); + try { + close(); + } catch (Exception e) { + // ignore + } throw err; - } catch (IOException err) { - close(); + } catch (Throwable err) { + try { + close(); + } catch (Exception e) { + // ignore + } throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java 2019-09-03 12:37:49.000000000 +0000 @@ -2,6 +2,7 @@ * Copyright (C) 2008-2010, Google Inc. * Copyright (C) 2008, Shawn O. Pearce * Copyright (C) 2013, Matthias Sohn + * Copyright (C) 2017, Thomas Wolf * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -45,11 +46,14 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; +import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; +import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION; import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA; import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE; @@ -66,50 +70,57 @@ import java.net.MalformedURLException; import java.net.Proxy; import java.net.ProxySelector; +import java.net.URISyntaxException; import java.net.URL; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; +import java.security.cert.CertPathBuilderException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLHandshakeException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.RefDirectory; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.transport.HttpAuthMethod.Type; +import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode; import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.io.DisabledOutputStream; import org.eclipse.jgit.util.io.UnionInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Transport over HTTP and FTP protocols. @@ -130,102 +141,123 @@ public class TransportHttp extends HttpTransport implements WalkTransport, PackTransport { + private static final Logger LOG = LoggerFactory + .getLogger(TransportHttp.class); + private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$ private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ + /** + * Accept-Encoding header in the HTTP request + * (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). + * + * @since 4.6 + */ + public enum AcceptEncoding { + /** + * Do not specify an Accept-Encoding header. In most servers this + * results in the content being transmitted as-is. + */ + UNSPECIFIED, + + /** + * Accept gzip content encoding. + */ + GZIP + } + static final TransportProtocol PROTO_HTTP = new TransportProtocol() { private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$ private final Set schemeSet = Collections - .unmodifiableSet(new LinkedHashSet(Arrays + .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); + @Override public String getName() { return JGitText.get().transportProtoHTTP; } + @Override public Set getSchemes() { return schemeSet; } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } + @Override public int getDefaultPort() { return 80; } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportHttp(local, uri); } + @Override public Transport open(URIish uri) throws NotSupportedException { return new TransportHttp(uri); } }; static final TransportProtocol PROTO_FTP = new TransportProtocol() { + @Override public String getName() { return JGitText.get().transportProtoFTP; } + @Override public Set getSchemes() { return Collections.singleton("ftp"); //$NON-NLS-1$ } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } + @Override public int getDefaultPort() { return 21; } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportHttp(local, uri); } }; - private static final Config.SectionParser HTTP_KEY = new SectionParser() { - public HttpConfig parse(final Config cfg) { - return new HttpConfig(cfg); - } - }; - - private static class HttpConfig { - final int postBuffer; - - final boolean sslVerify; - - HttpConfig(final Config rc) { - postBuffer = rc.getInt("http", "postbuffer", 1 * 1024 * 1024); //$NON-NLS-1$ //$NON-NLS-2$ - sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$ - } - - private HttpConfig() { - this(new Config()); - } - } + /** + * The current URI we're talking to. The inherited (final) field + * {@link #uri} stores the original URI; {@code currentUri} may be different + * after redirects. + */ + private URIish currentUri; - private final URL baseUrl; + private URL baseUrl; - private final URL objectsUrl; + private URL objectsUrl; private final HttpConfig http; @@ -237,20 +269,43 @@ private Map headers; + private boolean sslVerify; + + private boolean sslFailure = false; + TransportHttp(final Repository local, final URIish uri) throws NotSupportedException { super(local, uri); + setURI(uri); + http = new HttpConfig(local.getConfig(), uri); + proxySelector = ProxySelector.getDefault(); + sslVerify = http.isSslVerify(); + } + + private URL toURL(URIish urish) throws MalformedURLException { + String uriString = urish.toString(); + if (!uriString.endsWith("/")) { //$NON-NLS-1$ + uriString += '/'; + } + return new URL(uriString); + } + + /** + * Set uri a {@link org.eclipse.jgit.transport.URIish} object. + * + * @param uri + * a {@link org.eclipse.jgit.transport.URIish} object. + * @throws org.eclipse.jgit.errors.NotSupportedException + * @since 4.9 + */ + protected void setURI(final URIish uri) throws NotSupportedException { try { - String uriString = uri.toString(); - if (!uriString.endsWith("/")) //$NON-NLS-1$ - uriString += "/"; //$NON-NLS-1$ - baseUrl = new URL(uriString); + currentUri = uri; + baseUrl = toURL(uri); objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$ } catch (MalformedURLException e) { throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); } - http = local.getConfig().get(HTTP_KEY); - proxySelector = ProxySelector.getDefault(); } /** @@ -261,17 +316,10 @@ */ TransportHttp(final URIish uri) throws NotSupportedException { super(uri); - try { - String uriString = uri.toString(); - if (!uriString.endsWith("/")) //$NON-NLS-1$ - uriString += "/"; //$NON-NLS-1$ - baseUrl = new URL(uriString); - objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$ - } catch (MalformedURLException e) { - throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); - } - http = new HttpConfig(); + setURI(uri); + http = new HttpConfig(uri); proxySelector = ProxySelector.getDefault(); + sslVerify = http.isSslVerify(); } /** @@ -288,6 +336,7 @@ useSmartHttp = on; } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException, NotSupportedException { @@ -322,40 +371,38 @@ private WalkFetchConnection newDumbConnection(InputStream in) throws IOException, PackProtocolException { HttpObjectDB d = new HttpObjectDB(objectsUrl); - BufferedReader br = toBufferedReader(in); Map refs; - try { + try (BufferedReader br = toBufferedReader(in)) { refs = d.readAdvertisedImpl(br); - } finally { - br.close(); } - if (!refs.containsKey(Constants.HEAD)) { + if (!refs.containsKey(HEAD)) { // If HEAD was not published in the info/refs file (it usually // is not there) download HEAD by itself as a loose file and do // the resolution by hand. // - HttpConnection conn = httpOpen(new URL(baseUrl, Constants.HEAD)); + HttpConnection conn = httpOpen( + METHOD_GET, + new URL(baseUrl, HEAD), + AcceptEncoding.GZIP); int status = HttpSupport.response(conn); switch (status) { case HttpConnection.HTTP_OK: { - br = toBufferedReader(openInputStream(conn)); - try { + try (BufferedReader br = toBufferedReader( + openInputStream(conn))) { String line = br.readLine(); if (line != null && line.startsWith(RefDirectory.SYMREF)) { String target = line.substring(RefDirectory.SYMREF.length()); Ref r = refs.get(target); if (r == null) r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null); - r = new SymbolicRef(Constants.HEAD, r); + r = new SymbolicRef(HEAD, r); refs.put(r.getName(), r); } else if (line != null && ObjectId.isId(line)) { Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, - Constants.HEAD, ObjectId.fromString(line)); + HEAD, ObjectId.fromString(line)); refs.put(r.getName(), r); } - } finally { - br.close(); } break; } @@ -379,14 +426,14 @@ return new BufferedReader(new InputStreamReader(in, Constants.CHARSET)); } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws NotSupportedException, TransportException { final String service = SVC_RECEIVE_PACK; try { final HttpConnection c = connect(service); - final InputStream in = openInputStream(c); - try { + try (InputStream in = openInputStream(c)) { if (isSmartHttp(c, service)) { return smartPush(service, c, in); } else if (!useSmartHttp) { @@ -397,8 +444,6 @@ final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush; throw new NotSupportedException(msg); } - } finally { - in.close(); } } catch (NotSupportedException err) { throw err; @@ -417,6 +462,7 @@ return p; } + /** {@inheritDoc} */ @Override public void close() { // No explicit connections are maintained. @@ -436,30 +482,13 @@ private HttpConnection connect(final String service) throws TransportException, NotSupportedException { - final URL u; - try { - final StringBuilder b = new StringBuilder(); - b.append(baseUrl); - - if (b.charAt(b.length() - 1) != '/') - b.append('/'); - b.append(Constants.INFO_REFS); - - if (useSmartHttp) { - b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$ - b.append("service="); //$NON-NLS-1$ - b.append(service); - } - - u = new URL(b.toString()); - } catch (MalformedURLException e) { - throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); - } - - try { - int authAttempts = 1; - for (;;) { - final HttpConnection conn = httpOpen(u); + URL u = getServiceURL(service); + int authAttempts = 1; + int redirects = 0; + Collection ignoreTypes = null; + for (;;) { + try { + final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP); if (useSmartHttp) { String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$ conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$ @@ -475,7 +504,7 @@ // explicit authentication would be required if (authMethod.getType() == HttpAuthMethod.Type.NONE && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null) - authMethod = HttpAuthMethod.scanResponse(conn); + authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes); return conn; case HttpConnection.HTTP_NOT_FOUND: @@ -483,7 +512,7 @@ MessageFormat.format(JGitText.get().uriNotFound, u)); case HttpConnection.HTTP_UNAUTHORIZED: - authMethod = HttpAuthMethod.scanResponse(conn); + authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes); if (authMethod.getType() == HttpAuthMethod.Type.NONE) throw new TransportException(uri, MessageFormat.format( JGitText.get().authenticationNotSupported, uri)); @@ -492,9 +521,10 @@ throw new TransportException(uri, JGitText.get().noCredentialsProvider); if (authAttempts > 1) - credentialsProvider.reset(uri); + credentialsProvider.reset(currentUri); if (3 < authAttempts - || !authMethod.authorize(uri, credentialsProvider)) { + || !authMethod.authorize(currentUri, + credentialsProvider)) { throw new TransportException(uri, JGitText.get().notAuthorized); } @@ -503,47 +533,315 @@ case HttpConnection.HTTP_FORBIDDEN: throw new TransportException(uri, MessageFormat.format( - JGitText.get().serviceNotPermitted, service)); + JGitText.get().serviceNotPermitted, baseUrl, + service)); + case HttpConnection.HTTP_MOVED_PERM: + case HttpConnection.HTTP_MOVED_TEMP: + case HttpConnection.HTTP_SEE_OTHER: + case HttpConnection.HTTP_11_MOVED_TEMP: + // SEE_OTHER should actually never be sent by a git server, + // and in general should occur only on POST requests. But it + // doesn't hurt to accept it here as a redirect. + if (http.getFollowRedirects() == HttpRedirectMode.FALSE) { + throw new TransportException(uri, + MessageFormat.format( + JGitText.get().redirectsOff, + Integer.valueOf(status))); + } + URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION), + Constants.INFO_REFS, redirects++); + setURI(newUri); + u = getServiceURL(service); + authAttempts = 1; + break; default: String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$ throw new TransportException(uri, err); } + } catch (NotSupportedException e) { + throw e; + } catch (TransportException e) { + throw e; + } catch (SSLHandshakeException e) { + handleSslFailure(e); + continue; // Re-try + } catch (IOException e) { + if (authMethod.getType() != HttpAuthMethod.Type.NONE) { + if (ignoreTypes == null) { + ignoreTypes = new HashSet<>(); + } + + ignoreTypes.add(authMethod.getType()); + + // reset auth method & attempts for next authentication type + authMethod = HttpAuthMethod.Type.NONE.method(null); + authAttempts = 1; + + continue; + } + + throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e); } - } catch (NotSupportedException e) { - throw e; - } catch (TransportException e) { - throw e; + } + } + + private static class CredentialItems { + CredentialItem.InformationalMessage message; + + /** Trust the server for this git operation */ + CredentialItem.YesNoType now; + + /** + * Trust the server for all git operations from this repository; may be + * {@code null} if the transport was created via + * {@link #TransportHttp(URIish)}. + */ + CredentialItem.YesNoType forRepo; + + /** Always trust the server from now on. */ + CredentialItem.YesNoType always; + + public CredentialItem[] items() { + if (forRepo == null) { + return new CredentialItem[] { message, now, always }; + } else { + return new CredentialItem[] { message, now, forRepo, always }; + } + } + } + + private void handleSslFailure(Throwable e) throws TransportException { + if (sslFailure || !trustInsecureSslConnection(e.getCause())) { + throw new TransportException(uri, + MessageFormat.format( + JGitText.get().sslFailureExceptionMessage, + currentUri.setPass(null)), + e); + } + sslFailure = true; + } + + private boolean trustInsecureSslConnection(Throwable cause) { + if (cause instanceof CertificateException + || cause instanceof CertPathBuilderException + || cause instanceof CertPathValidatorException) { + // Certificate expired or revoked, PKIX path building not + // possible, self-signed certificate, host does not match ... + CredentialsProvider provider = getCredentialsProvider(); + if (provider != null) { + CredentialItems trust = constructSslTrustItems(cause); + CredentialItem[] items = trust.items(); + if (provider.supports(items)) { + boolean answered = provider.get(uri, items); + if (answered) { + // Not canceled + boolean trustNow = trust.now.getValue(); + boolean trustLocal = trust.forRepo != null + && trust.forRepo.getValue(); + boolean trustAlways = trust.always.getValue(); + if (trustNow || trustLocal || trustAlways) { + sslVerify = false; + if (trustAlways) { + updateSslVerifyUser(false); + } else if (trustLocal) { + updateSslVerify(local.getConfig(), false); + } + return true; + } + } + } + } + } + return false; + } + + private CredentialItems constructSslTrustItems(Throwable cause) { + CredentialItems items = new CredentialItems(); + String info = MessageFormat.format(JGitText.get().sslFailureInfo, + currentUri.setPass(null)); + String sslMessage = cause.getLocalizedMessage(); + if (sslMessage == null) { + sslMessage = cause.toString(); + } + sslMessage = MessageFormat.format(JGitText.get().sslFailureCause, + sslMessage); + items.message = new CredentialItem.InformationalMessage(info + '\n' + + sslMessage + '\n' + + JGitText.get().sslFailureTrustExplanation); + items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow); + if (local != null) { + items.forRepo = new CredentialItem.YesNoType( + MessageFormat.format(JGitText.get().sslTrustForRepo, + local.getDirectory())); + } + items.always = new CredentialItem.YesNoType( + JGitText.get().sslTrustAlways); + return items; + } + + private void updateSslVerify(StoredConfig config, boolean value) { + // Since git uses the original URI for matching, we must also use the + // original URI and cannot use the current URI (which might be different + // after redirects). + String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$ + int port = uri.getPort(); + if (port > 0) { + uriPattern += ":" + port; //$NON-NLS-1$ + } + config.setBoolean(HttpConfig.HTTP, uriPattern, + HttpConfig.SSL_VERIFY_KEY, value); + try { + config.save(); } catch (IOException e) { - throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e); + LOG.error(JGitText.get().sslVerifyCannotSave, e); + } + } + + private void updateSslVerifyUser(boolean value) { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + try { + userConfig.load(); + updateSslVerify(userConfig, value); + } catch (IOException | ConfigInvalidException e) { + // Log it, but otherwise ignore here. + LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid, + userConfig.getFile().getAbsolutePath(), e)); + } + } + + private URIish redirect(String location, String checkFor, int redirects) + throws TransportException { + if (location == null || location.isEmpty()) { + throw new TransportException(uri, + MessageFormat.format(JGitText.get().redirectLocationMissing, + baseUrl)); + } + if (redirects >= http.getMaxRedirects()) { + throw new TransportException(uri, + MessageFormat.format(JGitText.get().redirectLimitExceeded, + Integer.valueOf(http.getMaxRedirects()), baseUrl, + location)); + } + try { + if (!isValidRedirect(baseUrl, location, checkFor)) { + throw new TransportException(uri, + MessageFormat.format(JGitText.get().redirectBlocked, + baseUrl, location)); + } + location = location.substring(0, location.indexOf(checkFor)); + URIish result = new URIish(location); + if (LOG.isInfoEnabled()) { + LOG.info(MessageFormat.format(JGitText.get().redirectHttp, + uri.setPass(null), + Integer.valueOf(redirects), baseUrl, result)); + } + return result; + } catch (URISyntaxException e) { + throw new TransportException(uri, + MessageFormat.format(JGitText.get().invalidRedirectLocation, + baseUrl, location), + e); } } - final HttpConnection httpOpen(URL u) throws IOException { - return httpOpen(METHOD_GET, u); + private boolean isValidRedirect(URL current, String next, String checkFor) { + // Protocols must be the same, or current is "http" and next "https". We + // do not follow redirects from https back to http. + String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT); + int schemeEnd = next.indexOf("://"); //$NON-NLS-1$ + if (schemeEnd < 0) { + return false; + } + String newProtocol = next.substring(0, schemeEnd) + .toLowerCase(Locale.ROOT); + if (!oldProtocol.equals(newProtocol)) { + if (!"https".equals(newProtocol)) { //$NON-NLS-1$ + return false; + } + } + // git allows only rewriting the root, i.e., everything before INFO_REFS + // or the service name + if (next.indexOf(checkFor) < 0) { + return false; + } + // Basically we should test here that whatever follows INFO_REFS is + // unchanged. But since we re-construct the query part + // anyway, it doesn't matter. + return true; + } + + private URL getServiceURL(final String service) + throws NotSupportedException { + try { + final StringBuilder b = new StringBuilder(); + b.append(baseUrl); + + if (b.charAt(b.length() - 1) != '/') { + b.append('/'); + } + b.append(Constants.INFO_REFS); + + if (useSmartHttp) { + b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$ + b.append("service="); //$NON-NLS-1$ + b.append(service); + } + + return new URL(b.toString()); + } catch (MalformedURLException e) { + throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); + } } /** - * Open an HTTP connection. + * Open an HTTP connection, setting the accept-encoding request header to gzip. * - * @param method - * @param u - * @return the connection - * @throws IOException + * @param method HTTP request method + * @param u url of the HTTP connection + * @return the HTTP connection + * @throws java.io.IOException * @since 3.3 + * @deprecated use {@link #httpOpen(String, URL, AcceptEncoding)} instead. */ - protected HttpConnection httpOpen(String method, URL u) - throws IOException { + @Deprecated + protected HttpConnection httpOpen(String method, URL u) throws IOException { + return httpOpen(method, u, AcceptEncoding.GZIP); + } + + /** + * Open an HTTP connection. + * + * @param method HTTP request method + * @param u url of the HTTP connection + * @param acceptEncoding accept-encoding header option + * @return the HTTP connection + * @throws java.io.IOException + * @since 4.6 + */ + protected HttpConnection httpOpen(String method, URL u, + AcceptEncoding acceptEncoding) throws IOException { + if (method == null || u == null || acceptEncoding == null) { + throw new NullPointerException(); + } + final Proxy proxy = HttpSupport.proxyFor(proxySelector, u); HttpConnection conn = connectionFactory.create(u, proxy); - if (!http.sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$ - disableSslVerify(conn); + if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$ + HttpSupport.disableSslVerify(conn); } + // We must do our own redirect handling to implement git rules and to + // handle http->https redirects + conn.setInstanceFollowRedirects(false); + conn.setRequestMethod(method); conn.setUseCaches(false); - conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); + if (acceptEncoding == AcceptEncoding.GZIP) { + conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); + } conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$ if (UserAgent.get() != null) { conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get()); @@ -562,23 +860,10 @@ return conn; } - private void disableSslVerify(HttpConnection conn) - throws IOException { - final TrustManager[] trustAllCerts = new TrustManager[] { new DummyX509TrustManager() }; - try { - conn.configure(null, trustAllCerts, null); - conn.setHostnameVerifier(new DummyHostnameVerifier()); - } catch (KeyManagementException e) { - throw new IOException(e.getMessage()); - } catch (NoSuchAlgorithmException e) { - throw new IOException(e.getMessage()); - } - } - final InputStream openInputStream(HttpConnection conn) throws IOException { InputStream input = conn.getInputStream(); - if (ENCODING_GZIP.equals(conn.getHeaderField(HDR_CONTENT_ENCODING))) + if (isGzipContent(conn)) input = new GZIPInputStream(input); return input; } @@ -594,6 +879,11 @@ return expType.equals(actType); } + private boolean isGzipContent(final HttpConnection c) { + return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING)) + || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING)); + } + private void readSmartHeaders(final InputStream in, final String service) throws IOException { // A smart reply will have a '#' after the first 4 bytes, but @@ -658,23 +948,26 @@ } @Override + BufferedReader openReader(String path) throws IOException { + // Line oriented readable content is likely to compress well. + // Request gzip encoding. + InputStream is = open(path, AcceptEncoding.GZIP).in; + return new BufferedReader(new InputStreamReader(is, Constants.CHARSET)); + } + + @Override Collection getPackNames() throws IOException { - final Collection packs = new ArrayList(); - try { - final BufferedReader br = openReader(INFO_PACKS); - try { - for (;;) { - final String s = br.readLine(); - if (s == null || s.length() == 0) - break; - if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ - throw invalidAdvertisement(s); - packs.add(s.substring(2)); - } - return packs; - } finally { - br.close(); + final Collection packs = new ArrayList<>(); + try (BufferedReader br = openReader(INFO_PACKS)) { + for (;;) { + final String s = br.readLine(); + if (s == null || s.length() == 0) + break; + if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ + throw invalidAdvertisement(s); + packs.add(s.substring(2)); } + return packs; } catch (FileNotFoundException err) { return packs; } @@ -682,14 +975,25 @@ @Override FileStream open(final String path) throws IOException { + return open(path, AcceptEncoding.UNSPECIFIED); + } + + FileStream open(String path, AcceptEncoding acceptEncoding) + throws IOException { final URL base = httpObjectsUrl; final URL u = new URL(base, path); - final HttpConnection c = httpOpen(u); + final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding); switch (HttpSupport.response(c)) { case HttpConnection.HTTP_OK: final InputStream in = openInputStream(c); - final int len = c.getContentLength(); - return new FileStream(in, len); + // If content is being gzipped and then transferred, the content + // length in the header is the zipped content length, not the + // actual content length. + if (!isGzipContent(c)) { + final int len = c.getContentLength(); + return new FileStream(in, len); + } + return new FileStream(in); case HttpConnection.HTTP_NOT_FOUND: throw new FileNotFoundException(u.toString()); default: @@ -701,7 +1005,7 @@ Map readAdvertisedImpl(final BufferedReader br) throws IOException, PackProtocolException { - final TreeMap avail = new TreeMap(); + final TreeMap avail = new TreeMap<>(); for (;;) { String line = br.readLine(); if (line == null) @@ -799,6 +1103,7 @@ readAdvertisedRefs(); } + @Override protected void doPush(final ProgressMonitor monitor, final Map refUpdates, OutputStream outputStream) throws TransportException { @@ -835,7 +1140,8 @@ } void openStream() throws IOException { - conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName)); + conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName), + AcceptEncoding.GZIP); conn.setInstanceFollowRedirects(false); conn.setDoOutput(true); conn.setRequestProperty(HDR_CONTENT_TYPE, requestType); @@ -844,11 +1150,10 @@ void sendRequest() throws IOException { // Try to compress the content, but only if that is smaller. - TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer); - try { - GZIPOutputStream gzip = new GZIPOutputStream(buf); + TemporaryBuffer buf = new TemporaryBuffer.Heap( + http.getPostBuffer()); + try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) { out.writeTo(gzip, null); - gzip.close(); if (out.length() < buf.length()) buf = out; } catch (IOException err) { @@ -857,15 +1162,156 @@ buf = out; } - openStream(); - if (buf != out) - conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP); - conn.setFixedLengthStreamingMode((int) buf.length()); - final OutputStream httpOut = conn.getOutputStream(); - try { - buf.writeTo(httpOut, null); - } finally { - httpOut.close(); + HttpAuthMethod authenticator = null; + Collection ignoreTypes = EnumSet.noneOf(Type.class); + // Counts number of repeated authentication attempts using the same + // authentication scheme + int authAttempts = 1; + int redirects = 0; + for (;;) { + try { + // The very first time we will try with the authentication + // method used on the initial GET request. This is a hint + // only; it may fail. If so, we'll then re-try with proper + // 401 handling, going through the available authentication + // schemes. + openStream(); + if (buf != out) { + conn.setRequestProperty(HDR_CONTENT_ENCODING, + ENCODING_GZIP); + } + conn.setFixedLengthStreamingMode((int) buf.length()); + try (OutputStream httpOut = conn.getOutputStream()) { + buf.writeTo(httpOut, null); + } + + final int status = HttpSupport.response(conn); + switch (status) { + case HttpConnection.HTTP_OK: + // We're done. + return; + + case HttpConnection.HTTP_NOT_FOUND: + throw new NoRemoteRepositoryException(uri, + MessageFormat.format(JGitText.get().uriNotFound, + conn.getURL())); + + case HttpConnection.HTTP_FORBIDDEN: + throw new TransportException(uri, + MessageFormat.format( + JGitText.get().serviceNotPermitted, + baseUrl, serviceName)); + + case HttpConnection.HTTP_MOVED_PERM: + case HttpConnection.HTTP_MOVED_TEMP: + case HttpConnection.HTTP_11_MOVED_TEMP: + // SEE_OTHER after a POST doesn't make sense for a git + // server, so we don't handle it here and thus we'll + // report an error in openResponse() later on. + if (http.getFollowRedirects() != HttpRedirectMode.TRUE) { + // Let openResponse() issue an error + return; + } + currentUri = redirect(conn.getHeaderField(HDR_LOCATION), + '/' + serviceName, redirects++); + try { + baseUrl = toURL(currentUri); + } catch (MalformedURLException e) { + throw new TransportException(uri, + MessageFormat.format( + JGitText.get().invalidRedirectLocation, + baseUrl, currentUri), + e); + } + continue; + + case HttpConnection.HTTP_UNAUTHORIZED: + HttpAuthMethod nextMethod = HttpAuthMethod + .scanResponse(conn, ignoreTypes); + switch (nextMethod.getType()) { + case NONE: + throw new TransportException(uri, + MessageFormat.format( + JGitText.get().authenticationNotSupported, + conn.getURL())); + case NEGOTIATE: + // RFC 4559 states "When using the SPNEGO [...] with + // [...] POST, the authentication should be complete + // [...] before sending the user data." So in theory + // the initial GET should have been authenticated + // already. (Unless there was a redirect?) + // + // We try this only once: + ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE); + if (authenticator != null) { + ignoreTypes.add(authenticator.getType()); + } + authAttempts = 1; + // We only do the Kerberos part of SPNEGO, which + // requires only one round. + break; + default: + // DIGEST or BASIC. Let's be sure we ignore + // NEGOTIATE; if it was available, we have tried it + // before. + ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE); + if (authenticator == null || authenticator + .getType() != nextMethod.getType()) { + if (authenticator != null) { + ignoreTypes.add(authenticator.getType()); + } + authAttempts = 1; + } + break; + } + authMethod = nextMethod; + authenticator = nextMethod; + CredentialsProvider credentialsProvider = getCredentialsProvider(); + if (credentialsProvider == null) { + throw new TransportException(uri, + JGitText.get().noCredentialsProvider); + } + if (authAttempts > 1) { + credentialsProvider.reset(currentUri); + } + if (3 < authAttempts || !authMethod + .authorize(currentUri, credentialsProvider)) { + throw new TransportException(uri, + JGitText.get().notAuthorized); + } + authAttempts++; + continue; + + default: + // Just return here; openResponse() will report an + // appropriate error. + return; + } + } catch (SSLHandshakeException e) { + handleSslFailure(e); + continue; // Re-try + } catch (IOException e) { + if (authenticator == null || authMethod + .getType() != HttpAuthMethod.Type.NONE) { + // Can happen for instance if the server advertises + // Negotiate, but the client isn't configured for + // Kerberos. The first time (authenticator == null) we + // must re-try even if the authMethod was NONE: this may + // occur if the server advertised NTLM on the GET + // and the HttpConnection managed to successfully + // authenticate under the hood with NTLM. We might not + // have picked this up on the GET's 200 response. + if (authMethod.getType() != HttpAuthMethod.Type.NONE) { + ignoreTypes.add(authMethod.getType()); + } + // Start over with the remaining available methods. + authMethod = HttpAuthMethod.Type.NONE.method(null); + authenticator = authMethod; + authAttempts = 1; + continue; + } + throw e; + } } } @@ -894,16 +1340,19 @@ abstract void execute() throws IOException; class HttpExecuteStream extends InputStream { + @Override public int read() throws IOException { execute(); return -1; } + @Override public int read(byte[] b, int off, int len) throws IOException { execute(); return -1; } + @Override public long skip(long n) throws IOException { execute(); return 0; @@ -912,7 +1361,7 @@ class HttpOutputStream extends TemporaryBuffer { HttpOutputStream() { - super(http.postBuffer); + super(http.getPostBuffer()); } @Override @@ -1002,25 +1451,4 @@ in.add(openInputStream(conn)); } } - - private static class DummyX509TrustManager implements X509TrustManager { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(X509Certificate[] certs, String authType) { - // no check - } - - public void checkServerTrusted(X509Certificate[] certs, String authType) { - // no check - } - } - - private static class DummyHostnameVerifier implements HostnameVerifier { - public boolean verify(String hostname, SSLSession session) { - // always accept - return true; - } - } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -70,12 +71,15 @@ import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.hooks.Hooks; +import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; @@ -94,7 +98,7 @@ * Transport instances and the connections they create are not thread-safe. * Callers must ensure a transport is accessed by only one thread at a time. */ -public abstract class Transport { +public abstract class Transport implements AutoCloseable { /** Type of operation a Transport is being opened for. */ public enum Operation { /** Transport is to fetch objects locally. */ @@ -104,7 +108,7 @@ } private static final List> protocols = - new CopyOnWriteArrayList>(); + new CopyOnWriteArrayList<>(); static { // Registration goes backwards in order of priority. @@ -210,7 +214,8 @@ * Protocol definitions are held by WeakReference, allowing them to be * garbage collected when the calling application drops all strongly held * references to the TransportProtocol. Therefore applications should use a - * singleton pattern as described in {@link TransportProtocol}'s class + * singleton pattern as described in + * {@link org.eclipse.jgit.transport.TransportProtocol}'s class * documentation to ensure their protocol does not get disabled by garbage * collection earlier than expected. *

    @@ -221,7 +226,7 @@ * the protocol definition. Must not be null. */ public static void register(TransportProtocol proto) { - protocols.add(0, new WeakReference(proto)); + protocols.add(0, new WeakReference<>(proto)); } /** @@ -251,7 +256,7 @@ */ public static List getTransportProtocols() { int cnt = protocols.size(); - List res = new ArrayList(cnt); + List res = new ArrayList<>(cnt); for (WeakReference ref : protocols) { TransportProtocol proto = ref.get(); if (proto != null) @@ -265,7 +270,8 @@ /** * Open a new transport instance to connect two repositories. *

    - * This method assumes {@link Operation#FETCH}. + * This method assumes + * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. @@ -274,12 +280,12 @@ * configuration name. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(final Repository local, final String remote) @@ -301,12 +307,12 @@ * based on the type of connection desired. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(final Repository local, final String remote, @@ -325,7 +331,8 @@ /** * Open new transport instances to connect two repositories. *

    - * This method assumes {@link Operation#FETCH}. + * This method assumes + * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. @@ -334,12 +341,12 @@ * configuration name. * @return the list of new transport instances for every URI in remote * configuration. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, @@ -361,12 +368,12 @@ * based on the type of connection desired. * @return the list of new transport instances for every URI in remote * configuration. - * @throws URISyntaxException + * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, @@ -375,7 +382,7 @@ TransportException { final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote); if (doesNotExist(cfg)) { - final ArrayList transports = new ArrayList(1); + final ArrayList transports = new ArrayList<>(1); transports.add(open(local, new URIish(remote), null)); return transports; } @@ -385,7 +392,8 @@ /** * Open a new transport instance to connect two repositories. *

    - * This method assumes {@link Operation#FETCH}. + * This method assumes + * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. @@ -394,11 +402,11 @@ * repository. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * if provided remote configuration doesn't have any URI * associated. */ @@ -420,11 +428,11 @@ * based on the type of connection desired. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * if provided remote configuration doesn't have any URI * associated. */ @@ -443,7 +451,8 @@ /** * Open new transport instances to connect two repositories. *

    - * This method assumes {@link Operation#FETCH}. + * This method assumes + * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. @@ -452,9 +461,9 @@ * repository. * @return the list of new transport instances for every URI in remote * configuration. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, @@ -476,16 +485,16 @@ * based on the type of connection desired. * @return the list of new transport instances for every URI in remote * configuration. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, final RemoteConfig cfg, final Operation op) throws NotSupportedException, TransportException { final List uris = getURIs(cfg, op); - final List transports = new ArrayList(uris.size()); + final List transports = new ArrayList<>(uris.size()); for (final URIish uri : uris) { final Transport tn = open(local, uri, cfg.getName()); tn.applyConfig(cfg); @@ -522,9 +531,9 @@ * @param uri * location of the remote repository. * @return the new transport instance. Never null. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(final Repository local, final URIish uri) @@ -543,9 +552,9 @@ * name of the remote, if the remote as configured in * {@code local}; otherwise null. * @return the new transport instance. Never null. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(Repository local, URIish uri, String remoteName) @@ -557,8 +566,13 @@ continue; } - if (proto.canHandle(uri, local, remoteName)) - return proto.open(uri, local, remoteName); + if (proto.canHandle(uri, local, remoteName)) { + Transport tn = proto.open(uri, local, remoteName); + tn.prePush = Hooks.prePush(local, tn.hookOutRedirect); + tn.prePush.setRemoteLocation(uri.toString()); + tn.prePush.setRemoteName(remoteName); + return tn; + } } throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri)); @@ -570,10 +584,10 @@ * Note that the resulting transport instance can not be used for fetching * or pushing, but only for reading remote refs. * - * @param uri + * @param uri a {@link org.eclipse.jgit.transport.URIish} object. * @return new Transport instance - * @throws NotSupportedException - * @throws TransportException + * @throws org.eclipse.jgit.errors.NotSupportedException + * @throws org.eclipse.jgit.errors.TransportException */ public static Transport open(URIish uri) throws NotSupportedException, TransportException { for (WeakReference ref : protocols) { @@ -591,36 +605,42 @@ } /** - * Convert push remote refs update specification from {@link RefSpec} form - * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching - * source part to local refs. expectedOldObjectId in RemoteRefUpdate is - * always set as null. Tracking branch is configured if RefSpec destination - * matches source of any fetch ref spec for this transport remote - * configuration. + * Convert push remote refs update specification from + * {@link org.eclipse.jgit.transport.RefSpec} form to + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands + * wildcards by matching source part to local refs. expectedOldObjectId in + * RemoteRefUpdate is set when specified in leases. Tracking branch is + * configured if RefSpec destination matches source of any fetch ref spec + * for this transport remote configuration. * * @param db * local database. * @param specs * collection of RefSpec to convert. + * @param leases + * map from ref to lease (containing expected old object id) * @param fetchSpecs * fetch specifications used for finding localtracking refs. May * be null or empty collection. - * @return collection of set up {@link RemoteRefUpdate}. - * @throws IOException + * @return collection of set up + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. + * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. + * @since 4.7 */ public static Collection findRemoteRefUpdatesFor( final Repository db, final Collection specs, + final Map leases, Collection fetchSpecs) throws IOException { if (fetchSpecs == null) fetchSpecs = Collections.emptyList(); - final List result = new LinkedList(); + final List result = new LinkedList<>(); final Collection procRefs = expandPushWildcardsFor(db, specs); for (final RefSpec spec : procRefs) { String srcSpec = spec.getSource(); - final Ref srcRef = db.getRef(srcSpec); + final Ref srcRef = db.findRef(srcSpec); if (srcRef != null) srcSpec = srcRef.getName(); @@ -643,18 +663,50 @@ final boolean forceUpdate = spec.isForceUpdate(); final String localName = findTrackingRefName(destSpec, fetchSpecs); + final RefLeaseSpec leaseSpec = leases.get(destSpec); + final ObjectId expected = leaseSpec == null ? null : + db.resolve(leaseSpec.getExpected()); final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcSpec, - destSpec, forceUpdate, localName, null); + destSpec, forceUpdate, localName, expected); result.add(rru); } return result; } + /** + * Convert push remote refs update specification from + * {@link org.eclipse.jgit.transport.RefSpec} form to + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands + * wildcards by matching source part to local refs. expectedOldObjectId in + * RemoteRefUpdate is always set as null. Tracking branch is configured if + * RefSpec destination matches source of any fetch ref spec for this + * transport remote configuration. + * + * @param db + * local database. + * @param specs + * collection of RefSpec to convert. + * @param fetchSpecs + * fetch specifications used for finding localtracking refs. May + * be null or empty collection. + * @return collection of set up + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. + * @throws java.io.IOException + * when problem occurred during conversion or specification set + * up: most probably, missing objects or refs. + */ + public static Collection findRemoteRefUpdatesFor( + final Repository db, final Collection specs, + Collection fetchSpecs) throws IOException { + return findRemoteRefUpdatesFor(db, specs, Collections.emptyMap(), + fetchSpecs); + } + private static Collection expandPushWildcardsFor( final Repository db, final Collection specs) throws IOException { final Map localRefs = db.getRefDatabase().getRefs(ALL); - final Collection procRefs = new HashSet(); + final Collection procRefs = new HashSet<>(); for (final RefSpec spec : specs) { if (spec.isWildcard()) { @@ -743,6 +795,9 @@ /** Should push produce thin-pack when sending objects to remote repository. */ private boolean pushThin = DEFAULT_PUSH_THIN; + /** Should push be all-or-nothing atomic behavior? */ + private boolean pushAtomic; + /** Should push just check for operation result, not really push. */ private boolean dryRun; @@ -761,6 +816,12 @@ /** Assists with authentication the connection. */ private CredentialsProvider credentialsProvider; + /** The option strings associated with the push operation. */ + private List pushOptions; + + private PrintStream hookOutRedirect; + + private PrePushHook prePush; /** * Create a new transport instance. * @@ -778,12 +839,14 @@ this.uri = uri; this.objectChecker = tc.newObjectChecker(); this.credentialsProvider = CredentialsProvider.getDefault(); + prePush = Hooks.prePush(local, hookOutRedirect); } /** * Create a minimal transport instance not tied to a single repository. * * @param uri + * a {@link org.eclipse.jgit.transport.URIish} object. */ protected Transport(final URIish uri) { this.uri = uri; @@ -869,6 +932,8 @@ } /** + * Whether fetch will verify if received objects are formatted correctly. + * * @return true if fetch will verify received objects are formatted * correctly. Validating objects requires more CPU time on the * client side of the connection. @@ -878,6 +943,8 @@ } /** + * Configure if checking received objects is enabled + * * @param check * true to enable checking received objects; false to assume all * received objects are valid. @@ -891,6 +958,8 @@ } /** + * Get configured object checker for received objects + * * @return configured object checker for received objects, or null. * @since 3.6 */ @@ -899,6 +968,8 @@ } /** + * Set the object checker to verify each received object with + * * @param impl * if non-null the object checking instance to verify each * received object with; null to disable object checking. @@ -909,7 +980,8 @@ } /** - * Default setting is: {@link RemoteConfig#DEFAULT_RECEIVE_PACK} + * Default setting is: + * {@link org.eclipse.jgit.transport.RemoteConfig#DEFAULT_RECEIVE_PACK} * * @return remote executable providing receive-pack service for pack * transports. @@ -921,7 +993,8 @@ /** * Set remote executable providing receive-pack service for pack transports. - * Default setting is: {@link RemoteConfig#DEFAULT_RECEIVE_PACK} + * Default setting is: + * {@link org.eclipse.jgit.transport.RemoteConfig#DEFAULT_RECEIVE_PACK} * * @param optionReceivePack * remote executable, if null or empty default one is set; @@ -957,6 +1030,34 @@ } /** + * Default setting is false. + * + * @return true if push requires all-or-nothing atomic behavior. + * @since 4.2 + */ + public boolean isPushAtomic() { + return pushAtomic; + } + + /** + * Request atomic push (all references succeed, or none do). + *

    + * Server must also support atomic push. If the server does not support the + * feature the push will abort without making changes. + * + * @param atomic + * true when push should be an all-or-nothing operation. + * @see PackTransport + * @since 4.2 + */ + public void setPushAtomic(final boolean atomic) { + this.pushAtomic = atomic; + } + + /** + * Whether destination refs should be removed if they no longer exist at the + * source repository. + * * @return true if destination refs should be removed if they no longer * exist at the source repository. */ @@ -996,6 +1097,9 @@ } /** + * Whether push operation should just check for possible result and not + * really update remote refs + * * @return true if push operation should just check for possible result and * not really update remote refs, false otherwise - when push should * act normally. @@ -1016,7 +1120,11 @@ this.dryRun = dryRun; } - /** @return timeout (in seconds) before aborting an IO operation. */ + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @return timeout (in seconds) before aborting an IO operation. + */ public int getTimeout() { return timeout; } @@ -1080,26 +1188,49 @@ } /** + * Get the option strings associated with the push operation + * + * @return the option strings associated with the push operation + * @since 4.5 + */ + public List getPushOptions() { + return pushOptions; + } + + /** + * Sets the option strings associated with the push operation. + * + * @param pushOptions + * null if push options are unsupported + * @since 4.5 + */ + public void setPushOptions(final List pushOptions) { + this.pushOptions = pushOptions; + } + + /** * Fetch objects and refs from the remote repository to the local one. *

    * This is a utility function providing standard fetch behavior. Local * tracking refs associated with the remote repository are automatically - * updated if this transport was created from a {@link RemoteConfig} with - * fetch RefSpecs defined. + * updated if this transport was created from a + * {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs + * defined. * * @param monitor * progress monitor to inform the user about our processing - * activity. Must not be null. Use {@link NullProgressMonitor} if - * progress updates are not interesting or necessary. + * activity. Must not be null. Use + * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress + * updates are not interesting or necessary. * @param toFetch * specification of refs to fetch locally. May be null or the * empty collection to use the specifications from the * RemoteConfig. Source for each RefSpec can't be null. * @return information describing the tracking refs updated. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support fetching * objects. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed or update specification was * incorrect. @@ -1119,7 +1250,7 @@ // the local tracking branches without incurring additional // object transfer overheads. // - final Collection tmp = new ArrayList(toFetch); + final Collection tmp = new ArrayList<>(toFetch); for (final RefSpec requested : toFetch) { final String reqSrc = requested.getSource(); for (final RefSpec configured : fetch) { @@ -1136,6 +1267,9 @@ final FetchResult result = new FetchResult(); new FetchProcess(this, toFetch).execute(monitor, result); + + local.autoGC(monitor); + return result; } @@ -1151,17 +1285,18 @@ * For setting up remote ref update specification from ref spec, see helper * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using - * directly {@link RemoteRefUpdate} for more possibilities. + * directly {@link org.eclipse.jgit.transport.RemoteRefUpdate} for more + * possibilities. *

    * When {@link #isDryRun()} is true, result of this operation is just * estimation of real operation result, no real action is performed. * * @see RemoteRefUpdate - * * @param monitor * progress monitor to inform the user about our processing - * activity. Must not be null. Use {@link NullProgressMonitor} if - * progress updates are not interesting or necessary. + * activity. Must not be null. Use + * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress + * updates are not interesting or necessary. * @param toPush * specification of refs to push. May be null or the empty * collection to use the specifications from the RemoteConfig @@ -1172,10 +1307,10 @@ * output stream to write messages to * @return information about results of remote refs updates, tracking refs * updates and refs advertised by remote repository. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support pushing * objects. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed at I/O or protocol level or * update specification was incorrect. @@ -1196,6 +1331,15 @@ if (toPush.isEmpty()) throw new TransportException(JGitText.get().nothingToPush); } + if (prePush != null) { + try { + prePush.setRefs(toPush); + prePush.call(); + } catch (AbortedByHookException | IOException e) { + throw new TransportException(e.getMessage(), e); + } + } + final PushProcess pushProcess = new PushProcess(this, toPush, out); return pushProcess.execute(monitor); } @@ -1212,30 +1356,30 @@ * For setting up remote ref update specification from ref spec, see helper * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using - * directly {@link RemoteRefUpdate} for more possibilities. + * directly {@link org.eclipse.jgit.transport.RemoteRefUpdate} for more + * possibilities. *

    * When {@link #isDryRun()} is true, result of this operation is just * estimation of real operation result, no real action is performed. * * @see RemoteRefUpdate - * * @param monitor * progress monitor to inform the user about our processing - * activity. Must not be null. Use {@link NullProgressMonitor} if - * progress updates are not interesting or necessary. + * activity. Must not be null. Use + * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress + * updates are not interesting or necessary. * @param toPush * specification of refs to push. May be null or the empty * collection to use the specifications from the RemoteConfig * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No * more than 1 RemoteRefUpdate with the same remoteName is * allowed. These objects are modified during this call. - * * @return information about results of remote refs updates, tracking refs * updates and refs advertised by remote repository. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support pushing * objects. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed at I/O or protocol level or * update specification was incorrect. @@ -1247,26 +1391,59 @@ } /** - * Convert push remote refs update specification from {@link RefSpec} form - * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching - * source part to local refs. expectedOldObjectId in RemoteRefUpdate is - * always set as null. Tracking branch is configured if RefSpec destination - * matches source of any fetch ref spec for this transport remote - * configuration. + * Convert push remote refs update specification from + * {@link org.eclipse.jgit.transport.RefSpec} form to + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands + * wildcards by matching source part to local refs. expectedOldObjectId in + * RemoteRefUpdate is always set as null. Tracking branch is configured if + * RefSpec destination matches source of any fetch ref spec for this + * transport remote configuration. *

    * Conversion is performed for context of this transport (database, fetch * specifications). * * @param specs * collection of RefSpec to convert. - * @return collection of set up {@link RemoteRefUpdate}. - * @throws IOException + * @return collection of set up + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. + * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. */ public Collection findRemoteRefUpdatesFor( final Collection specs) throws IOException { - return findRemoteRefUpdatesFor(local, specs, fetch); + return findRemoteRefUpdatesFor(local, specs, Collections.emptyMap(), + fetch); + } + + /** + * Convert push remote refs update specification from + * {@link org.eclipse.jgit.transport.RefSpec} form to + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands + * wildcards by matching source part to local refs. expectedOldObjectId in + * RemoteRefUpdate is set according to leases. Tracking branch is configured + * if RefSpec destination matches source of any fetch ref spec for this + * transport remote configuration. + *

    + * Conversion is performed for context of this transport (database, fetch + * specifications). + * + * @param specs + * collection of RefSpec to convert. + * @param leases + * map from ref to lease (containing expected old object id) + * @return collection of set up + * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. + * @throws java.io.IOException + * when problem occurred during conversion or specification set + * up: most probably, missing objects or refs. + * @since 4.7 + */ + public Collection findRemoteRefUpdatesFor( + final Collection specs, + final Map leases) throws IOException { + return findRemoteRefUpdatesFor(local, specs, leases, + fetch); } /** @@ -1276,9 +1453,9 @@ * be used for reading remote refs. * * @return a fresh connection to fetch from the remote repository. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the implementation does not support fetching. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established. */ public abstract FetchConnection openFetch() throws NotSupportedException, @@ -1288,21 +1465,28 @@ * Begins a new connection for pushing into the remote repository. * * @return a fresh connection to push into the remote repository. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * the implementation does not support pushing. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established */ public abstract PushConnection openPush() throws NotSupportedException, TransportException; /** + * {@inheritDoc} + *

    * Close any resources used by this transport. *

    * If the remote repository is contacted by a network socket this method * must close that network socket, disconnecting the two peers. If the * remote repository is actually local (same system) this method must close * any open file handles used to read the "remote" repository. + *

    + * {@code AutoClosable.close()} declares that it throws {@link Exception}. + * Implementers shouldn't throw checked exceptions. This override narrows + * the signature to prevent them from doing so. */ + @Override public abstract void close(); } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -67,7 +68,6 @@ import org.eclipse.jgit.transport.resolver.UploadPackFactory; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; import org.eclipse.jgit.util.io.StreamCopyThread; /** @@ -100,6 +100,7 @@ return JGitText.get().transportProtoLocal; } + @Override public Set getSchemes() { return Collections.singleton("file"); //$NON-NLS-1$ } @@ -132,6 +133,7 @@ return new TransportLocal(local, uri, gitDir); } + @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$ @@ -170,12 +172,15 @@ private Repository openRepo() throws TransportException { try { - return new RepositoryBuilder().setGitDir(remoteGitDir).build(); + return new RepositoryBuilder() + .setFS(local != null ? local.getFS() : FS.DETECTED) + .setGitDir(remoteGitDir).build(); } catch (IOException err) { throw new TransportException(uri, JGitText.get().notAGitDirectory); } } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { final String up = getOptionUploadPack(); @@ -189,9 +194,10 @@ return createUploadPack(db); } }; - return new InternalFetchConnection(this, upf, null, openRepo()); + return new InternalFetchConnection<>(this, upf, null, openRepo()); } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws TransportException { final String rp = getOptionReceivePack(); @@ -205,14 +211,24 @@ return createReceivePack(db); } }; - return new InternalPushConnection(this, rpf, null, openRepo()); + return new InternalPushConnection<>(this, rpf, null, openRepo()); } + /** {@inheritDoc} */ @Override public void close() { // Resources must be established per-connection. } + /** + * Spawn process + * + * @param cmd + * command + * @return a {@link java.lang.Process} object. + * @throws org.eclipse.jgit.errors.TransportException + * if any. + */ protected Process spawn(final String cmd) throws TransportException { try { @@ -258,7 +274,7 @@ OutputStream upOut = uploadPack.getOutputStream(); upIn = new BufferedInputStream(upIn); - upOut = new SafeBufferedOutputStream(upOut); + upOut = new BufferedOutputStream(upOut); init(upIn, upOut); readAdvertisedRefs(); @@ -311,7 +327,7 @@ OutputStream rpOut = receivePack.getOutputStream(); rpIn = new BufferedInputStream(rpIn); - rpOut = new SafeBufferedOutputStream(rpOut); + rpOut = new BufferedOutputStream(rpOut); init(rpIn, rpOut); readAdvertisedRefs(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,11 +72,12 @@ * *

    * Applications may register additional protocols for use by JGit by calling - * {@link Transport#register(TransportProtocol)}. Because that API holds onto - * the protocol object by a WeakReference, applications must ensure their own - * ClassLoader retains the TransportProtocol for the life of the application. - * Using a static singleton pattern as above will ensure the protocol is valid - * so long as the ClassLoader that defines it remains valid. + * {@link org.eclipse.jgit.transport.Transport#register(TransportProtocol)}. + * Because that API holds onto the protocol object by a WeakReference, + * applications must ensure their own ClassLoader retains the TransportProtocol + * for the life of the application. Using a static singleton pattern as above + * will ensure the protocol is valid so long as the ClassLoader that defines it + * remains valid. *

    * Applications may automatically register additional protocols by filling in * the names of their TransportProtocol defining classes using the services file @@ -102,25 +103,45 @@ PATH, } - /** @return text name of the protocol suitable for display to a user. */ + /** + * Get text name of the protocol suitable for display to a user. + * + * @return text name of the protocol suitable for display to a user. + */ public abstract String getName(); - /** @return immutable set of schemes supported by this protocol. */ + /** + * Get immutable set of schemes supported by this protocol. + * + * @return immutable set of schemes supported by this protocol. + */ public Set getSchemes() { return Collections.emptySet(); } - /** @return immutable set of URIishFields that must be filled in. */ + /** + * Get immutable set of URIishFields that must be filled in. + * + * @return immutable set of URIishFields that must be filled in. + */ public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PATH)); } - /** @return immutable set of URIishFields that may be filled in. */ + /** + * Get immutable set of URIishFields that may be filled in. + * + * @return immutable set of URIishFields that may be filled in. + */ public Set getOptionalFields() { return Collections.emptySet(); } - /** @return if a port is supported, the default port, else -1. */ + /** + * Get the default port if the protocol supports a port, else -1. + * + * @return the default port if the protocol supports a port, else -1. + */ public int getDefaultPort() { return -1; } @@ -246,9 +267,9 @@ * name of the remote, if the remote as configured in * {@code local}; otherwise null. * @return the transport. - * @throws NotSupportedException + * @throws org.eclipse.jgit.errors.NotSupportedException * this protocol does not support the URI. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public abstract Transport open(URIish uri, Repository local, @@ -260,9 +281,10 @@ * configuration instead of reading from configuration files. * * @param uri + * a {@link org.eclipse.jgit.transport.URIish} object. * @return new Transport - * @throws NotSupportedException - * @throws TransportException + * @throws org.eclipse.jgit.errors.NotSupportedException + * @throws org.eclipse.jgit.errors.TransportException */ public Transport open(URIish uri) throws NotSupportedException, TransportException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; + import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; @@ -86,11 +88,12 @@ * repository that is available over SSH, but whose remote host does not have * Git installed. *

    - * Unlike the HTTP variant (see {@link TransportHttp}) we rely upon being able - * to list files in directories, as the SFTP protocol supports this function. By + * Unlike the HTTP variant (see + * {@link org.eclipse.jgit.transport.TransportHttp}) we rely upon being able to + * list files in directories, as the SFTP protocol supports this function. By * listing files through SFTP we can avoid needing to have current - * objects/info/packs or info/refs files on the - * remote repository and access the data directly, much as Git itself would. + * objects/info/packs or info/refs files on the remote + * repository and access the data directly, much as Git itself would. *

    * Concurrent pushing over this transport is not supported. Multiple concurrent * push operations may cause confusion in the repository state. @@ -99,28 +102,34 @@ */ public class TransportSftp extends SshTransport implements WalkTransport { static final TransportProtocol PROTO_SFTP = new TransportProtocol() { + @Override public String getName() { return JGitText.get().transportProtoSFTP; } + @Override public Set getSchemes() { return Collections.singleton("sftp"); //$NON-NLS-1$ } + @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } + @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } + @Override public int getDefaultPort() { return 22; } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportSftp(local, uri); @@ -131,6 +140,7 @@ super(local, uri); } + /** {@inheritDoc} */ @Override public FetchConnection openFetch() throws TransportException { final SftpObjectDB c = new SftpObjectDB(uri.getPath()); @@ -139,6 +149,7 @@ return r; } + /** {@inheritDoc} */ @Override public PushConnection openPush() throws TransportException { final SftpObjectDB c = new SftpObjectDB(uri.getPath()); @@ -225,15 +236,15 @@ @Override Collection getPackNames() throws IOException { - final List packs = new ArrayList(); + final List packs = new ArrayList<>(); try { @SuppressWarnings("unchecked") final Collection list = ftp.ls("pack"); //$NON-NLS-1$ final HashMap files; final HashMap mtimes; - files = new HashMap(); - mtimes = new HashMap(); + files = new HashMap<>(); + mtimes = new HashMap<>(); for (final ChannelSftp.LsEntry ent : list) files.put(ent.getFilename(), ent); @@ -251,6 +262,7 @@ } Collections.sort(packs, new Comparator() { + @Override public int compare(final String o1, final String o2) { return mtimes.get(o2).intValue() - mtimes.get(o1).intValue(); @@ -334,7 +346,7 @@ @Override void writeFile(final String path, final byte[] data) throws IOException { - final String lock = path + ".lock"; //$NON-NLS-1$ + final String lock = path + LOCK_SUFFIX; try { super.writeFile(lock, data); try { @@ -381,7 +393,7 @@ } Map readAdvertisedRefs() throws TransportException { - final TreeMap avail = new TreeMap(); + final TreeMap avail = new TreeMap<>(); readPackedRefs(avail); readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD); readLooseRefs(avail, ROOT_DIR + "refs", "refs/"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -417,13 +429,8 @@ private Ref readRef(final TreeMap avail, final String path, final String name) throws TransportException { final String line; - try { - final BufferedReader br = openReader(path); - try { - line = br.readLine(); - } finally { - br.close(); - } + try (BufferedReader br = openReader(path)) { + line = br.readLine(); } catch (FileNotFoundException noRef) { return null; } catch (IOException err) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackInternalServerErrorException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackInternalServerErrorException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackInternalServerErrorException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackInternalServerErrorException.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ import java.io.IOException; -/** UploadPack has already reported an error to the client.*/ +/** + * UploadPack has already reported an error to the client. + */ public class UploadPackInternalServerErrorException extends IOException { private static final long serialVersionUID = 1L; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,8 +45,8 @@ 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_ALLOW_TIP_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; @@ -58,6 +58,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -70,20 +71,23 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.lib.BitmapIndex; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; +import org.eclipse.jgit.revwalk.BitmapWalker; import org.eclipse.jgit.revwalk.DepthWalk; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; @@ -176,7 +180,7 @@ */ public FirstLine(String line) { if (line.length() > 45) { - final HashSet opts = new HashSet(); + final HashSet opts = new HashSet<>(); String opt = line.substring(45); if (opt.startsWith(" ")) //$NON-NLS-1$ opt = opt.substring(1); @@ -234,7 +238,7 @@ private InputStream rawIn; - private OutputStream rawOut; + private ResponseBufferedOutputStream rawOut; private PacketLineIn pckIn; @@ -262,19 +266,19 @@ String userAgent; /** Raw ObjectIds the client has asked for, before validating them. */ - private final Set wantIds = new HashSet(); + private final Set wantIds = new HashSet<>(); /** Objects the client wants to obtain. */ - private final Set wantAll = new HashSet(); + private final Set wantAll = new HashSet<>(); /** Objects on both sides, these don't have to be sent. */ - private final Set commonBase = new HashSet(); + private final Set commonBase = new HashSet<>(); /** Shallow commits the client already has. */ - private final Set clientShallowCommits = new HashSet(); + private final Set clientShallowCommits = new HashSet<>(); /** Shallow commits on the client which are now becoming unshallow */ - private final List unshallowCommits = new ArrayList(); + private final List unshallowCommits = new ArrayList<>(); /** Desired depth from the client on a shallow request. */ private int depth; @@ -312,6 +316,7 @@ private PackStatistics statistics; + @SuppressWarnings("deprecation") private UploadPackLogger logger = UploadPackLogger.NULL; /** @@ -340,12 +345,20 @@ setTransferConfig(null); } - /** @return the repository this upload is reading from. */ + /** + * Get the repository this upload is reading from. + * + * @return the repository this upload is reading from. + */ public final Repository getRepository() { return db; } - /** @return the RevWalk instance used by this connection. */ + /** + * Get the RevWalk instance used by this connection. + * + * @return the RevWalk instance used by this connection. + */ public final RevWalk getRevWalk() { return walk; } @@ -363,14 +376,15 @@ /** * Set the refs advertised by this UploadPack. *

    - * Intended to be called from a {@link PreUploadHook}. + * Intended to be called from a + * {@link org.eclipse.jgit.transport.PreUploadHook}. * * @param allRefs * explicit set of references to claim as advertised by this - * UploadPack instance. This overrides any references that - * may exist in the source repository. The map is passed - * to the configured {@link #getRefFilter()}. If null, assumes - * all refs were advertised. + * UploadPack instance. This overrides any references that may + * exist in the source repository. The map is passed to the + * configured {@link #getRefFilter()}. If null, assumes all refs + * were advertised. */ public void setAdvertisedRefs(Map allRefs) { if (allRefs != null) @@ -383,7 +397,11 @@ refs = refFilter.filter(refs); } - /** @return timeout (in seconds) before aborting an IO operation. */ + /** + * Get timeout (in seconds) before aborting an IO operation. + * + * @return timeout (in seconds) before aborting an IO operation. + */ public int getTimeout() { return timeout; } @@ -401,6 +419,9 @@ } /** + * Whether this class expects a bi-directional pipe opened between the + * client and itself. + * * @return true if this class expects a bi-directional pipe opened between * the client and itself. The default is true. */ @@ -409,6 +430,9 @@ } /** + * Set whether this class will assume the socket is a fully bidirectional + * pipe between the two peers + * * @param twoWay * if true, this class will assume the socket is a fully * bidirectional pipe between the two peers and takes advantage @@ -422,8 +446,10 @@ } /** - * @return policy used by the service to validate client requests, or null for - * a custom request validator. + * Get policy used by the service to validate client requests + * + * @return policy used by the service to validate client requests, or null + * for a custom request validator. */ public RequestPolicy getRequestPolicy() { if (requestValidator instanceof AdvertisedRequestValidator) @@ -440,15 +466,21 @@ } /** + * Set the policy used to enforce validation of a client's want list. + * * @param policy * the policy used to enforce validation of a client's want list. - * By default the policy is {@link RequestPolicy#ADVERTISED}, + * By default the policy is + * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#ADVERTISED}, * which is the Git default requiring clients to only ask for an - * object that a reference directly points to. This may be relaxed - * to {@link RequestPolicy#REACHABLE_COMMIT} or - * {@link RequestPolicy#REACHABLE_COMMIT_TIP} when callers have - * {@link #setBiDirectionalPipe(boolean)} set to false. - * Overrides any policy specified in a {@link TransferConfig}. + * object that a reference directly points to. This may be + * relaxed to + * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#REACHABLE_COMMIT} + * or + * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#REACHABLE_COMMIT_TIP} + * when callers have {@link #setBiDirectionalPipe(boolean)} set + * to false. Overrides any policy specified in a + * {@link org.eclipse.jgit.transport.TransferConfig}. */ public void setRequestPolicy(RequestPolicy policy) { switch (policy) { @@ -472,6 +504,8 @@ } /** + * Set custom validator for client want list. + * * @param validator * custom validator for client want list. * @since 3.1 @@ -481,12 +515,20 @@ : new AdvertisedRequestValidator(); } - /** @return the hook used while advertising the refs to the client */ + /** + * Get the hook used while advertising the refs to the client. + * + * @return the hook used while advertising the refs to the client. + */ public AdvertiseRefsHook getAdvertiseRefsHook() { return advertiseRefsHook; } - /** @return the filter used while advertising the refs to the client */ + /** + * Get the filter used while advertising the refs to the client. + * + * @return the filter used while advertising the refs to the client. + */ public RefFilter getRefFilter() { return refFilter; } @@ -494,9 +536,10 @@ /** * Set the hook used while advertising the refs to the client. *

    - * If the {@link AdvertiseRefsHook} chooses to call - * {@link #setAdvertisedRefs(Map)}, only refs set by this hook and - * selected by the {@link RefFilter} will be shown to the client. + * If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to + * call {@link #setAdvertisedRefs(Map)}, only refs set by this hook + * and selected by the {@link org.eclipse.jgit.transport.RefFilter} + * will be shown to the client. * * @param advertiseRefsHook * the hook; may be null to show all refs. @@ -511,10 +554,11 @@ /** * Set the filter used while advertising the refs to the client. *

    - * Only refs allowed by this filter will be sent to the client. - * The filter is run against the refs specified by the - * {@link AdvertiseRefsHook} (if applicable). If null or not set, uses the - * filter implied by the {@link TransferConfig}. + * Only refs allowed by this filter will be sent to the client. The filter + * is run against the refs specified by the + * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). If + * null or not set, uses the filter implied by the + * {@link org.eclipse.jgit.transport.TransferConfig}. * * @param refFilter * the filter; may be null to show all refs. @@ -523,7 +567,11 @@ this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } - /** @return the configured pre upload hook. */ + /** + * Get the configured pre upload hook. + * + * @return the configured pre upload hook. + */ public PreUploadHook getPreUploadHook() { return preUploadHook; } @@ -539,6 +587,8 @@ } /** + * Get the configured post upload hook. + * * @return the configured post upload hook. * @since 4.1 */ @@ -569,6 +619,8 @@ } /** + * Set configuration controlling transfer options. + * * @param tc * configuration controlling transfer options. If null the source * repository's settings will be used. @@ -586,8 +638,9 @@ } /** - * @return the configured logger. + * Get the configured logger. * + * @return the configured logger. * @deprecated Use {@link #getPreUploadHook()}. */ @Deprecated @@ -612,7 +665,7 @@ * * @return true if the client has advertised a side-band capability, false * otherwise. - * @throws RequestNotYetReadException + * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, so * we do not know if they expect side-band. Note that the client * may have already written the request, it just has not been @@ -640,13 +693,12 @@ * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. - * @throws IOException + * @throws java.io.IOException */ - public void upload(final InputStream input, final OutputStream output, + public void upload(final InputStream input, OutputStream output, final OutputStream messages) throws IOException { try { rawIn = input; - rawOut = output; if (messages != null) msgOut = messages; @@ -654,11 +706,17 @@ final Thread caller = Thread.currentThread(); timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ TimeoutInputStream i = new TimeoutInputStream(rawIn, timer); - TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer); + @SuppressWarnings("resource") + TimeoutOutputStream o = new TimeoutOutputStream(output, timer); i.setTimeout(timeout * 1000); o.setTimeout(timeout * 1000); rawIn = i; - rawOut = o; + output = o; + } + + rawOut = new ResponseBufferedOutputStream(output); + if (biDirectionalPipe) { + rawOut.stopBuffering(); } pckIn = new PacketLineIn(rawIn); @@ -681,7 +739,7 @@ * 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 + * was sent, such as during the negotiation phase of a smart HTTP * connection, or if the client was already up-to-date. * @since 3.0 * @deprecated Use {@link #getStatistics()}. @@ -696,7 +754,7 @@ * 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 + * was sent, such as during the negotiation phase of a smart HTTP * connection, or if the client was already up-to-date. * @since 4.1 */ @@ -704,28 +762,40 @@ return statistics; } - private Map getAdvertisedOrDefaultRefs() { - if (refs == null) - setAdvertisedRefs(null); + private Map getAdvertisedOrDefaultRefs() throws IOException { + if (refs != null) { + return refs; + } + + advertiseRefsHook.advertiseRefs(this); + if (refs == null) { + setAdvertisedRefs(db.getRefDatabase().getRefs(ALL)); + } return refs; } private void service() throws IOException { - if (biDirectionalPipe) - sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); - else if (requestValidator instanceof AnyRequestValidator) - advertised = Collections.emptySet(); - else - advertised = refIdSet(getAdvertisedOrDefaultRefs().values()); - - boolean sendPack; + boolean sendPack = false; + // If it's a non-bidi request, we need to read the entire request before + // writing a response. Buffer the response until then. + PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator(); try { + if (biDirectionalPipe) + sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); + else if (requestValidator instanceof AnyRequestValidator) + advertised = Collections.emptySet(); + else + advertised = refIdSet(getAdvertisedOrDefaultRefs().values()); + + long negotiateStart = System.currentTimeMillis(); + accumulator.advertised = advertised.size(); recvWants(); if (wantIds.isEmpty()) { preUploadHook.onBeginNegotiateRound(this, wantIds, 0); preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false); return; } + accumulator.wants = wantIds.size(); if (options.contains(OPTION_MULTI_ACK_DETAILED)) { multiAck = MultiAck.DETAILED; @@ -741,11 +811,21 @@ processShallow(); if (!clientShallowCommits.isEmpty()) walk.assumeShallow(clientShallowCommits); - sendPack = negotiate(); - } catch (PackProtocolException err) { - reportErrorDuringNegotiate(err.getMessage()); - throw err; - + sendPack = negotiate(accumulator); + accumulator.timeNegotiating += System.currentTimeMillis() + - negotiateStart; + + if (sendPack && !biDirectionalPipe) { + // Ensure the request was fully consumed. Any remaining input must + // be a protocol error. If we aren't at EOF the implementation is broken. + int eof = rawIn.read(); + if (0 <= eof) { + sendPack = false; + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().expectedEOFReceived, + "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$ + } + } } catch (ServiceMayNotContinueException err) { if (!err.isOutput() && err.getMessage() != null) { try { @@ -756,42 +836,53 @@ } } throw err; - - } catch (IOException err) { - reportErrorDuringNegotiate(JGitText.get().internalServerError); - throw err; - } catch (RuntimeException err) { - reportErrorDuringNegotiate(JGitText.get().internalServerError); - throw err; - } catch (Error err) { - reportErrorDuringNegotiate(JGitText.get().internalServerError); + } catch (IOException | RuntimeException | Error err) { + boolean output = false; + try { + String msg = err instanceof PackProtocolException + ? err.getMessage() + : JGitText.get().internalServerError; + pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + output = true; + } catch (Throwable err2) { + // Ignore this secondary failure, leave output false. + } + if (output) { + throw new UploadPackInternalServerErrorException(err); + } throw err; + } finally { + if (!sendPack && !biDirectionalPipe) { + while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) { + // Discard until EOF. + } + } + rawOut.stopBuffering(); } if (sendPack) - sendPack(); + sendPack(accumulator); } private static Set refIdSet(Collection refs) { - Set ids = new HashSet(refs.size()); + Set ids = new HashSet<>(refs.size()); for (Ref ref : refs) { - if (ref.getObjectId() != null) - ids.add(ref.getObjectId()); + ObjectId id = ref.getObjectId(); + if (id != null) { + ids.add(id); + } + id = ref.getPeeledObjectId(); + if (id != null) { + ids.add(id); + } } return ids; } - private void reportErrorDuringNegotiate(String msg) { - try { - pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ - } catch (Throwable err) { - // Ignore this secondary failure. - } - } - private void processShallow() throws IOException { + int walkDepth = depth - 1; try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk( - walk.getObjectReader(), depth)) { + walk.getObjectReader(), walkDepth)) { // Find all the commits which will be shallow for (ObjectId o : wantIds) { @@ -808,12 +899,14 @@ // Commits at the boundary which aren't already shallow in // the client need to be marked as such - if (c.getDepth() == depth && !clientShallowCommits.contains(c)) + if (c.getDepth() == walkDepth + && !clientShallowCommits.contains(c)) pckOut.writeString("shallow " + o.name()); //$NON-NLS-1$ // Commits not on the boundary which are shallow in the client // need to become unshallowed - if (c.getDepth() < depth && clientShallowCommits.remove(c)) { + if (c.getDepth() < walkDepth + && clientShallowCommits.remove(c)) { unshallowCommits.add(c.copy()); pckOut.writeString("unshallow " + c.name()); //$NON-NLS-1$ } @@ -856,22 +949,14 @@ * * @param adv * the advertisement formatter. - * @throws IOException + * @throws java.io.IOException * the formatter failed to write an advertisement. - * @throws ServiceMayNotContinueException + * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * the hook denied advertisement. */ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException, ServiceMayNotContinueException { - try { - advertiseRefsHook.advertiseRefs(this); - } catch (ServiceMayNotContinueException fail) { - if (fail.getMessage() != null) { - adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$ - fail.setOutput(); - } - throw fail; - } + Map advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); adv.init(db); adv.advertiseCapability(OPTION_INCLUDE_TAG); @@ -896,7 +981,6 @@ adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT); adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.setDerefTags(true); - Map advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); findSymrefs(adv, advertisedOrDefaultRefs); advertised = adv.send(advertisedOrDefaultRefs); if (adv.isEmpty()) @@ -924,6 +1008,8 @@ } /** + * Get an underlying stream for sending messages to the client + * * @return an underlying stream for sending messages to the client, or null. * @since 3.1 */ @@ -948,6 +1034,11 @@ if (line.startsWith("deepen ")) { //$NON-NLS-1$ depth = Integer.parseInt(line.substring(7)); + if (depth <= 0) { + throw new PackProtocolException( + MessageFormat.format(JGitText.get().invalidDepth, + Integer.valueOf(depth))); + } continue; } @@ -974,7 +1065,8 @@ } /** - * Returns the clone/fetch depth. Valid only after calling recvWants(). + * Returns the clone/fetch depth. Valid only after calling recvWants(). A + * depth of 1 means return only the wants. * * @return the depth requested by the client, or 0 if unbounded. * @since 4.0 @@ -1004,11 +1096,12 @@ return UserAgent.getAgent(options, userAgent); } - private boolean negotiate() throws IOException { + private boolean negotiate(PackStatistics.Accumulator accumulator) + throws IOException { okToGiveUp = Boolean.FALSE; ObjectId last = ObjectId.zeroId(); - List peerHas = new ArrayList(64); + List peerHas = new ArrayList<>(64); for (;;) { String line; try { @@ -1038,7 +1131,7 @@ } else if (line.startsWith("have ") && line.length() == 45) { //$NON-NLS-1$ peerHas.add(ObjectId.fromString(line.substring(5))); - + accumulator.haves++; } else if (line.equals("done")) { //$NON-NLS-1$ last = processHaveLines(peerHas, last); @@ -1148,7 +1241,6 @@ if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) { ObjectId id = peerHas.get(peerHas.size() - 1); - sentReady = true; pckOut.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ sentReady = true; } @@ -1163,7 +1255,7 @@ for (ObjectId obj : wantIds) { if (!advertised.contains(obj)) { if (notAdvertisedWants == null) - notAdvertisedWants = new ArrayList(); + notAdvertisedWants = new ArrayList<>(); notAdvertisedWants.add(obj); } } @@ -1186,9 +1278,7 @@ } wantIds.clear(); } catch (MissingObjectException notFound) { - ObjectId id = notFound.getObjectId(); - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, id.name()), notFound); + throw new WantNotValidException(notFound.getObjectId(), notFound); } finally { q.release(); } @@ -1208,13 +1298,13 @@ */ public static final class AdvertisedRequestValidator implements RequestValidator { + @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { if (!up.isBiDirectionalPipe()) new ReachableCommitRequestValidator().checkWants(up, wants); else if (!wants.isEmpty()) - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, wants.iterator().next().name())); + throw new WantNotValidException(wants.iterator().next()); } } @@ -1225,9 +1315,10 @@ */ public static final class ReachableCommitRequestValidator implements RequestValidator { + @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { - checkNotAdvertisedWants(up.getRevWalk(), wants, + checkNotAdvertisedWants(up, wants, refIdSet(up.getAdvertisedRefs().values())); } } @@ -1238,6 +1329,7 @@ * @since 3.1 */ public static final class TipRequestValidator implements RequestValidator { + @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { if (!up.isBiDirectionalPipe()) @@ -1247,8 +1339,7 @@ refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values()); for (ObjectId obj : wants) { if (!refIds.contains(obj)) - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, obj.name())); + throw new WantNotValidException(obj); } } } @@ -1261,9 +1352,10 @@ */ public static final class ReachableCommitTipRequestValidator implements RequestValidator { + @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { - checkNotAdvertisedWants(up.getRevWalk(), wants, + checkNotAdvertisedWants(up, wants, refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values())); } } @@ -1274,13 +1366,26 @@ * @since 3.1 */ public static final class AnyRequestValidator implements RequestValidator { + @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { // All requests are valid. } } - private static void checkNotAdvertisedWants(RevWalk walk, + private static void checkNotAdvertisedWantsUsingBitmap(ObjectReader reader, + BitmapIndex bitmapIndex, List notAdvertisedWants, + Set reachableFrom) throws IOException { + BitmapWalker bitmapWalker = new BitmapWalker(new ObjectWalk(reader), bitmapIndex, null); + BitmapBuilder reachables = bitmapWalker.findObjects(reachableFrom, null, false); + for (ObjectId oid : notAdvertisedWants) { + if (!reachables.contains(oid)) { + throw new WantNotValidException(oid); + } + } + } + + private static void checkNotAdvertisedWants(UploadPack up, List notAdvertisedWants, Set reachableFrom) throws MissingObjectException, IncorrectObjectTypeException, IOException { // Walk the requested commits back to the provided set of commits. If any @@ -1289,37 +1394,49 @@ // into an advertised branch it will be marked UNINTERESTING and no commits // return. - AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true); - try { - RevObject obj; - while ((obj = q.next()) != null) { - if (!(obj instanceof RevCommit)) - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, obj.name())); - walk.markStart((RevCommit) obj); - } - } catch (MissingObjectException notFound) { - ObjectId id = notFound.getObjectId(); - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, id.name()), notFound); - } finally { - q.release(); - } - for (ObjectId id : reachableFrom) { + ObjectReader reader = up.getRevWalk().getObjectReader(); + try (RevWalk walk = new RevWalk(reader)) { + AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true); try { - walk.markUninteresting(walk.parseCommit(id)); - } catch (IncorrectObjectTypeException notCommit) { - continue; + RevObject obj; + while ((obj = q.next()) != null) { + if (!(obj instanceof RevCommit)) { + // If unadvertized non-commits are requested, use + // bitmaps. If there are no bitmaps, instead of + // incurring the expense of a manual walk, reject + // the request. + BitmapIndex bitmapIndex = reader.getBitmapIndex(); + if (bitmapIndex != null) { + checkNotAdvertisedWantsUsingBitmap( + reader, + bitmapIndex, + notAdvertisedWants, + reachableFrom); + return; + } + throw new WantNotValidException(obj); + } + walk.markStart((RevCommit) obj); + } + } catch (MissingObjectException notFound) { + throw new WantNotValidException(notFound.getObjectId(), + notFound); + } finally { + q.release(); + } + for (ObjectId id : reachableFrom) { + try { + walk.markUninteresting(walk.parseCommit(id)); + } catch (IncorrectObjectTypeException notCommit) { + continue; + } } - } - RevCommit bad = walk.next(); - if (bad != null) { - throw new PackProtocolException(MessageFormat.format( - JGitText.get().wantNotValid, - bad.name())); + RevCommit bad = walk.next(); + if (bad != null) { + throw new WantNotValidException(bad); + } } - walk.reset(); } private void addCommonBase(final RevObject o) { @@ -1372,23 +1489,13 @@ return false; } - private void sendPack() throws IOException { + private void sendPack(PackStatistics.Accumulator accumulator) + throws IOException { final boolean sideband = options.contains(OPTION_SIDE_BAND) || options.contains(OPTION_SIDE_BAND_64K); - - if (!biDirectionalPipe) { - // Ensure the request was fully consumed. Any remaining input must - // be a protocol error. If we aren't at EOF the implementation is broken. - int eof = rawIn.read(); - if (0 <= eof) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().expectedEOFReceived, - "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$ - } - if (sideband) { try { - sendPack(true); + sendPack(true, accumulator); } catch (ServiceMayNotContinueException noPack) { // This was already reported on (below). throw noPack; @@ -1409,7 +1516,7 @@ throw err; } } else { - sendPack(false); + sendPack(false, accumulator); } } @@ -1429,7 +1536,9 @@ } } - private void sendPack(final boolean sideband) throws IOException { + @SuppressWarnings("deprecation") + private void sendPack(final boolean sideband, + PackStatistics.Accumulator accumulator) throws IOException { ProgressMonitor pm = NullProgressMonitor.INSTANCE; OutputStream packOut = rawOut; @@ -1470,7 +1579,8 @@ PackConfig cfg = packConfig; if (cfg == null) cfg = new PackConfig(db); - final PackWriter pw = new PackWriter(cfg, walk.getObjectReader()); + final PackWriter pw = new PackWriter(cfg, walk.getObjectReader(), + accumulator); try { pw.setIndexDisabled(true); pw.setUseCachedPacks(true); @@ -1482,7 +1592,7 @@ pw.setReuseValidatingObjects(false); if (commonBase.isEmpty() && refs != null) { - Set tagTargets = new HashSet(); + Set tagTargets = new HashSet<>(); for (Ref ref : refs.values()) { if (ref.getPeeledObjectId() != null) tagTargets.add(ref.getPeeledObjectId()); @@ -1494,23 +1604,30 @@ pw.setTagTargets(tagTargets); } - if (depth > 0) + RevWalk rw = walk; + if (depth > 0) { pw.setShallowPack(depth, unshallowCommits); + rw = new DepthWalk.RevWalk(walk.getObjectReader(), depth - 1); + rw.assumeShallow(clientShallowCommits); + } - RevWalk rw = walk; if (wantAll.isEmpty()) { - pw.preparePack(pm, wantIds, commonBase); + pw.preparePack(pm, wantIds, commonBase, clientShallowCommits); } else { walk.reset(); - ObjectWalk ow = walk.toObjectWalkWithSameObjects(); - pw.preparePack(pm, ow, wantAll, commonBase); + ObjectWalk ow = rw.toObjectWalkWithSameObjects(); + pw.preparePack(pm, ow, wantAll, commonBase, PackWriter.NONE); rw = ow; } if (options.contains(OPTION_INCLUDE_TAG) && refs != null) { for (Ref ref : refs.values()) { ObjectId objectId = ref.getObjectId(); + if (objectId == null) { + // skip unborn branch + continue; + } // If the object was already requested, skip it. if (wantAll.isEmpty()) { @@ -1526,12 +1643,13 @@ ref = db.peel(ref); ObjectId peeledId = ref.getPeeledObjectId(); - if (peeledId == null) + objectId = ref.getObjectId(); + if (peeledId == null || objectId == null) continue; - objectId = ref.getObjectId(); - if (pw.willInclude(peeledId) && !pw.willInclude(objectId)) + if (pw.willInclude(peeledId) && !pw.willInclude(objectId)) { pw.addObject(rw.parseAny(objectId)); + } } } @@ -1563,4 +1681,47 @@ adv.addSymref(Constants.HEAD, head.getLeaf().getName()); } } + + private static class ResponseBufferedOutputStream extends OutputStream { + private final OutputStream rawOut; + + private OutputStream out; + + ResponseBufferedOutputStream(OutputStream rawOut) { + this.rawOut = rawOut; + this.out = new ByteArrayOutputStream(); + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte b[]) throws IOException { + out.write(b); + } + + @Override + public void write(byte b[], int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + out.close(); + } + + void stopBuffering() throws IOException { + if (out != rawOut) { + ((ByteArrayOutputStream) out).writeTo(rawOut); + out = rawOut; + } + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,8 @@ *

    * loggers are run in the order passed to the constructor. * - * @deprecated Use {@link PostUploadHookChain} instead. + * @deprecated Use {@link org.eclipse.jgit.transport.PostUploadHookChain} + * instead. */ @Deprecated public class UploadPackLoggerChain implements UploadPackLogger { @@ -81,9 +82,8 @@ return new UploadPackLoggerChain(newLoggers, i); } - /** - * @since 3.0 - */ + /** {@inheritDoc} */ + @Override public void onPackStatistics(PackWriter.Statistics stats) { for (int i = 0; i < count; i++) loggers[i].onPackStatistics(stats); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,19 +46,21 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter; /** - * Logs activity that occurred within {@link UploadPack}. + * Logs activity that occurred within + * {@link org.eclipse.jgit.transport.UploadPack}. *

    * 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. * - * @deprecated use {@link PostUploadHook} instead + * @deprecated use {@link org.eclipse.jgit.transport.PostUploadHook} instead */ @Deprecated -public interface UploadPackLogger { +public interface UploadPackLogger { // TODO remove in JGit 5.0 /** A simple no-op logger. */ public static final UploadPackLogger NULL = new UploadPackLogger() { + @Override public void onPackStatistics(PackWriter.Statistics stats) { // Do nothing. } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,7 +83,7 @@ * capturing groups: the first containing the user and the second containing * the password */ - private static final String OPT_USER_PWD_P = "(?:([^/:@]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$ + private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$ /** * Part of a pattern which matches the host part of URIs. Defines one @@ -95,7 +95,7 @@ * Part of a pattern which matches the optional port part of URIs. Defines * one capturing group containing the port without the preceding colon. */ - private static final String OPT_PORT_P = "(?::(\\d+))?"; //$NON-NLS-1$ + private static final String OPT_PORT_P = "(?::(\\d*))?"; //$NON-NLS-1$ /** * Part of a pattern which matches the ~username part (e.g. /~root in @@ -137,7 +137,11 @@ + OPT_PORT_P // + "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$ + (USER_HOME_P + "?") //$NON-NLS-1$ - + "[\\\\/])" //$NON-NLS-1$ + + "(?:" // start non capturing group for host //$NON-NLS-1$ + // separator or end of line + + "[\\\\/])|$" //$NON-NLS-1$ + + ")" // close non capturing group for the host//$NON-NLS-1$ + // separator or end of line + ")?" // close the optional group containing hostname //$NON-NLS-1$ + "(.+)?" //$NON-NLS-1$ + "$"); //$NON-NLS-1$ @@ -196,10 +200,12 @@ private String host; /** - * Parse and construct an {@link URIish} from a string + * Parse and construct an {@link org.eclipse.jgit.transport.URIish} from a + * string * * @param s - * @throws URISyntaxException + * a {@link java.lang.String} object. + * @throws java.net.URISyntaxException */ public URIish(String s) throws URISyntaxException { if (StringUtils.isEmptyOrNull(s)) { @@ -218,11 +224,23 @@ scheme = matcher.group(1); user = unescape(matcher.group(2)); pass = unescape(matcher.group(3)); - host = unescape(matcher.group(4)); - if (matcher.group(5) != null) - port = Integer.parseInt(matcher.group(5)); - rawPath = cleanLeadingSlashes( - n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme); + // empty ports are in general allowed, except for URLs like + // file://D:/path for which it is more desirable to parse with + // host=null and path=D:/path + String portString = matcher.group(5); + if ("file".equals(scheme) && "".equals(portString)) { //$NON-NLS-1$ //$NON-NLS-2$ + rawPath = cleanLeadingSlashes( + n2e(matcher.group(4)) + ":" + portString //$NON-NLS-1$ + + n2e(matcher.group(6)) + n2e(matcher.group(7)), + scheme); + } else { + host = unescape(matcher.group(4)); + if (portString != null && portString.length() > 0) { + port = Integer.parseInt(portString); + } + rawPath = cleanLeadingSlashes( + n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme); + } path = unescape(rawPath); return; } @@ -372,8 +390,10 @@ public URIish(final URL u) { scheme = u.getProtocol(); path = u.getPath(); + path = cleanLeadingSlashes(path, scheme); try { rawPath = u.toURI().getRawPath(); + rawPath = cleanLeadingSlashes(rawPath, scheme); } catch (URISyntaxException e) { throw new RuntimeException(e); // Impossible } @@ -389,7 +409,9 @@ host = u.getHost(); } - /** Create an empty, non-configured URI. */ + /** + * Create an empty, non-configured URI. + */ public URIish() { // Configure nothing. } @@ -405,6 +427,8 @@ } /** + * Whether this URI references a repository on another system. + * * @return true if this URI references a repository on another system. */ public boolean isRemote() { @@ -412,6 +436,8 @@ } /** + * Get host name part. + * * @return host name part or null */ public String getHost() { @@ -432,6 +458,8 @@ } /** + * Get protocol name + * * @return protocol name or null for local references */ public String getScheme() { @@ -452,6 +480,8 @@ } /** + * Get path name component + * * @return path name component */ public String getPath() { @@ -459,6 +489,8 @@ } /** + * Get path name component + * * @return path name component */ public String getRawPath() { @@ -485,7 +517,7 @@ * @param n * the new value for path. * @return a new URI with the updated value. - * @throws URISyntaxException + * @throws java.net.URISyntaxException */ public URIish setRawPath(final String n) throws URISyntaxException { final URIish r = new URIish(this); @@ -495,6 +527,8 @@ } /** + * Get user name requested for transfer + * * @return user name requested for transfer or null */ public String getUser() { @@ -515,6 +549,8 @@ } /** + * Get password requested for transfer + * * @return password requested for transfer or null */ public String getPass() { @@ -535,6 +571,8 @@ } /** + * Get port number requested for transfer or -1 if not explicit + * * @return port number requested for transfer or -1 if not explicit */ public int getPort() { @@ -554,6 +592,8 @@ return r; } + /** {@inheritDoc} */ + @Override public int hashCode() { int hc = 0; if (getScheme() != null) @@ -571,6 +611,8 @@ return hc; } + /** {@inheritDoc} */ + @Override public boolean equals(final Object obj) { if (!(obj instanceof URIish)) return false; @@ -593,6 +635,8 @@ private static boolean eq(final String a, final String b) { if (a == b) return true; + if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b)) + return true; if (a == null || b == null) return false; return a.equals(b); @@ -607,6 +651,8 @@ return format(true, false); } + /** {@inheritDoc} */ + @Override public String toString() { return format(false, false); } @@ -638,7 +684,7 @@ if (getPath() != null) { if (getScheme() != null) { - if (!getPath().startsWith("/")) //$NON-NLS-1$ + if (!getPath().startsWith("/") && !getPath().isEmpty()) //$NON-NLS-1$ r.append('/'); } else if (getHost() != null) r.append(':'); @@ -655,6 +701,8 @@ } /** + * Get the URI as an ASCII string. + * * @return the URI as an ASCII string. Password is not included. */ public String toASCIIString() { @@ -662,6 +710,9 @@ } /** + * Convert the URI including password, formatted with only ASCII characters + * such that it will be valid for use over the network. + * * @return the URI including password, formatted with only ASCII characters * such that it will be valid for use over the network. */ @@ -702,16 +753,16 @@ * * @return the "humanish" part of the path. May be an empty string. Never * {@code null}. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * if it's impossible to determine a humanish part, or path is * {@code null} or empty * @see #getPath */ public String getHumanishName() throws IllegalArgumentException { String s = getPath(); - if ("/".equals(s)) //$NON-NLS-1$ + if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$ //$NON-NLS-2$ s = getHost(); - if ("".equals(s) || s == null) //$NON-NLS-1$ + if (s == null) // $NON-NLS-1$ throw new IllegalArgumentException(); String[] elements; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,8 @@ import org.eclipse.jgit.errors.UnsupportedCredentialItem; /** - * Simple {@link CredentialsProvider} that always uses the same information. + * Simple {@link org.eclipse.jgit.transport.CredentialsProvider} that always + * uses the same information. */ public class UsernamePasswordCredentialsProvider extends CredentialsProvider { private String username; @@ -59,7 +60,9 @@ * Initialize the provider with a single username and password. * * @param username + * user name * @param password + * password */ public UsernamePasswordCredentialsProvider(String username, String password) { this(username, password.toCharArray()); @@ -69,18 +72,22 @@ * Initialize the provider with a single username and password. * * @param username + * user name * @param password + * password */ public UsernamePasswordCredentialsProvider(String username, char[] password) { this.username = username; this.password = password; } + /** {@inheritDoc} */ @Override public boolean isInteractive() { return false; } + /** {@inheritDoc} */ @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { @@ -96,6 +103,7 @@ return true; } + /** {@inheritDoc} */ @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { @@ -121,7 +129,9 @@ return true; } - /** Destroy the saved username and password.. */ + /** + * Destroy the saved username and password.. + */ public void clear() { username = null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,22 +47,29 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.KeySpec; import java.text.MessageFormat; +import java.util.Locale; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.util.Base64; abstract class WalkEncryption { static final WalkEncryption NONE = new NoEncryption(); @@ -71,36 +78,60 @@ static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$ - abstract OutputStream encrypt(OutputStream os) throws IOException; + // Note: encrypt -> request state machine, step 1. + abstract OutputStream encrypt(OutputStream output) throws IOException; - abstract InputStream decrypt(InputStream in) throws IOException; + // Note: encrypt -> request state machine, step 2. + abstract void request(HttpURLConnection conn, String prefix) throws IOException; - abstract void request(HttpURLConnection u, String prefix); + // Note: validate -> decrypt state machine, step 1. + abstract void validate(HttpURLConnection conn, String prefix) throws IOException; - abstract void validate(HttpURLConnection u, String p) throws IOException; - - protected void validateImpl(final HttpURLConnection u, final String p, + // Note: validate -> decrypt state machine, step 2. + abstract InputStream decrypt(InputStream input) throws IOException; + + + // TODO mixed ciphers + // consider permitting mixed ciphers to facilitate algorithm migration + // i.e. user keeps the password, but changes the algorithm + // then existing remote entries will still be readable + /** + * Validate + * + * @param u + * a {@link java.net.HttpURLConnection} object. + * @param prefix + * a {@link java.lang.String} object. + * @param version + * a {@link java.lang.String} object. + * @param name + * a {@link java.lang.String} object. + * @throws java.io.IOException + * if any. + */ + protected void validateImpl(final HttpURLConnection u, final String prefix, final String version, final String name) throws IOException { String v; - v = u.getHeaderField(p + JETS3T_CRYPTO_VER); + v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER); if (v == null) v = ""; //$NON-NLS-1$ if (!version.equals(v)) throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v)); - v = u.getHeaderField(p + JETS3T_CRYPTO_ALG); + v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG); if (v == null) v = ""; //$NON-NLS-1$ - if (!name.equals(v)) - throw new IOException(JGitText.get().unsupportedEncryptionAlgorithm + v); + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + if (!name.equalsIgnoreCase(v)) + throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v)); } IOException error(final Throwable why) { - final IOException e; - e = new IOException(MessageFormat.format(JGitText.get().encryptionError, why.getMessage())); - e.initCause(why); - return e; + return new IOException(MessageFormat + .format(JGitText.get().encryptionError, + why.getMessage()), why); } private static class NoEncryption extends WalkEncryption { @@ -110,9 +141,9 @@ } @Override - void validate(final HttpURLConnection u, final String p) + void validate(final HttpURLConnection u, final String prefix) throws IOException { - validateImpl(u, p, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ + validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -126,53 +157,107 @@ } } - static class ObjectEncryptionV2 extends WalkEncryption { - private static int ITERATION_COUNT = 5000; - - private static byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, - (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 }; - - private final String algorithmName; - - private final SecretKey skey; - - private final PBEParameterSpec aspec; - - ObjectEncryptionV2(final String algo, final String key) - throws InvalidKeySpecException, NoSuchAlgorithmException { - algorithmName = algo; + /** + * JetS3t compatibility reference: + * EncryptionUtil.java + *

    + * Note: EncryptionUtil is inadequate: + *

  • EncryptionUtil.isCipherAvailableForUse checks encryption only which + * "always works", but in JetS3t both encryption and decryption use non-IV + * aware algorithm parameters for all PBE specs, which breaks in case of AES + *
  • that means that only non-IV algorithms will work round trip in + * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC + *
  • any AES based algorithms such as "PBE...With...And...AES" will not + * work, since they need proper IV setup + */ + static class JetS3tV2 extends WalkEncryption { + + static final String VERSION = "2"; //$NON-NLS-1$ + + static final String ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$ + + static final int ITERATIONS = 5000; + + static final int KEY_SIZE = 32; + + static final byte[] SALT = { // + (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, // + (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 // + }; + + // Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE + static final byte[] ZERO_AES_IV = new byte[16]; + + private static final String CRYPTO_VER = VERSION; + + private final String cryptoAlg; + + private final SecretKey secretKey; + + private final AlgorithmParameterSpec paramSpec; + + JetS3tV2(final String algo, final String key) + throws GeneralSecurityException { + cryptoAlg = algo; + + // Verify if cipher is present. + Cipher cipher = InsecureCipherFactory.create(cryptoAlg); + + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + String cryptoName = cryptoAlg.toUpperCase(Locale.ROOT); + + if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$ + throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE); + + PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), SALT, ITERATIONS, KEY_SIZE); + secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec); + + // Detect algorithms which require initialization vector. + boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$ + + // PBEParameterSpec algorithm parameters are supported from Java 8. + if (useIV) { + // Support IV where possible: + // * since JCE provider uses random IV for PBE/AES + // * and there is no place to store dynamic IV in JetS3t V2 + // * we use static IV, and tolerate increased security risk + // TODO back port this change to JetS3t V2 + // See: + // https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java + // http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java + IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV); + paramSpec = new PBEParameterSpec(SALT, ITERATIONS, paramIV); + } else { + // Strict legacy JetS3t V2 compatibility, with no IV support. + paramSpec = new PBEParameterSpec(SALT, ITERATIONS); + } - final PBEKeySpec s; - s = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, 32); - skey = SecretKeyFactory.getInstance(algo).generateSecret(s); - aspec = new PBEParameterSpec(salt, ITERATION_COUNT); + // Verify if cipher + key are allowed by policy. + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + cipher.doFinal(); } @Override void request(final HttpURLConnection u, final String prefix) { - u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, "2"); //$NON-NLS-1$ - u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, algorithmName); + u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, CRYPTO_VER); + u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg); } @Override - void validate(final HttpURLConnection u, final String p) + void validate(final HttpURLConnection u, final String prefix) throws IOException { - validateImpl(u, p, "2", algorithmName); //$NON-NLS-1$ + validateImpl(u, prefix, CRYPTO_VER, cryptoAlg); } @Override OutputStream encrypt(final OutputStream os) throws IOException { try { - final Cipher c = Cipher.getInstance(algorithmName); - c.init(Cipher.ENCRYPT_MODE, skey, aspec); - return new CipherOutputStream(os, c); - } catch (NoSuchAlgorithmException e) { - throw error(e); - } catch (NoSuchPaddingException e) { - throw error(e); - } catch (InvalidKeyException e) { - throw error(e); - } catch (InvalidAlgorithmParameterException e) { + final Cipher cipher = InsecureCipherFactory.create(cryptoAlg); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + return new CipherOutputStream(os, cipher); + } catch (GeneralSecurityException e) { throw error(e); } } @@ -180,18 +265,311 @@ @Override InputStream decrypt(final InputStream in) throws IOException { try { - final Cipher c = Cipher.getInstance(algorithmName); - c.init(Cipher.DECRYPT_MODE, skey, aspec); - return new CipherInputStream(in, c); - } catch (NoSuchAlgorithmException e) { - throw error(e); - } catch (NoSuchPaddingException e) { + final Cipher cipher = InsecureCipherFactory.create(cryptoAlg); + cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + return new CipherInputStream(in, cipher); + } catch (GeneralSecurityException e) { throw error(e); - } catch (InvalidKeyException e) { + } + } + } + + /** Encryption property names. */ + interface Keys { + // Remote S3 meta: V1 algorithm name or V2 profile name. + String JGIT_PROFILE = "jgit-crypto-profile"; //$NON-NLS-1$ + + // Remote S3 meta: JGit encryption implementation version. + String JGIT_VERSION = "jgit-crypto-version"; //$NON-NLS-1$ + + // Remote S3 meta: base-64 encoded cipher algorithm parameters. + String JGIT_CONTEXT = "jgit-crypto-context"; //$NON-NLS-1$ + + // Amazon S3 connection configuration file profile property suffixes: + String X_ALGO = ".algo"; //$NON-NLS-1$ + String X_KEY_ALGO = ".key.algo"; //$NON-NLS-1$ + String X_KEY_SIZE = ".key.size"; //$NON-NLS-1$ + String X_KEY_ITER = ".key.iter"; //$NON-NLS-1$ + String X_KEY_SALT = ".key.salt"; //$NON-NLS-1$ + } + + /** Encryption constants and defaults. */ + interface Vals { + // Compatibility defaults. + String DEFAULT_VERS = "0"; //$NON-NLS-1$ + String DEFAULT_ALGO = JetS3tV2.ALGORITHM; + String DEFAULT_KEY_ALGO = JetS3tV2.ALGORITHM; + String DEFAULT_KEY_SIZE = Integer.toString(JetS3tV2.KEY_SIZE); + String DEFAULT_KEY_ITER = Integer.toString(JetS3tV2.ITERATIONS); + String DEFAULT_KEY_SALT = DatatypeConverter.printHexBinary(JetS3tV2.SALT); + + String EMPTY = ""; //$NON-NLS-1$ + + // Match white space. + String REGEX_WS = "\\s+"; //$NON-NLS-1$ + + // Match PBE ciphers, i.e: PBEWithMD5AndDES + String REGEX_PBE = "(PBE).*(WITH).+(AND).+"; //$NON-NLS-1$ + + // Match transformation ciphers, i.e: AES/CBC/PKCS5Padding + String REGEX_TRANS = "(.+)/(.+)/(.+)"; //$NON-NLS-1$ + } + + static GeneralSecurityException securityError(String message) { + return new GeneralSecurityException( + MessageFormat.format(JGitText.get().encryptionError, message)); + } + + /** + * Base implementation of JGit symmetric encryption. Supports V2 properties + * format. + */ + static abstract class SymmetricEncryption extends WalkEncryption + implements Keys, Vals { + + /** Encryption profile, root name of group of related properties. */ + final String profile; + + /** Encryption version, reflects actual implementation class. */ + final String version; + + /** Full cipher algorithm name. */ + final String cipherAlgo; + + /** Cipher algorithm name for parameters lookup. */ + final String paramsAlgo; + + /** Generated secret key. */ + final SecretKey secretKey; + + SymmetricEncryption(Properties props) throws GeneralSecurityException { + + profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + version = props.getProperty(AmazonS3.Keys.CRYPTO_VER); + String pass = props.getProperty(AmazonS3.Keys.PASSWORD); + + cipherAlgo = props.getProperty(profile + X_ALGO, DEFAULT_ALGO); + + String keyAlgo = props.getProperty(profile + X_KEY_ALGO, DEFAULT_KEY_ALGO); + String keySize = props.getProperty(profile + X_KEY_SIZE, DEFAULT_KEY_SIZE); + String keyIter = props.getProperty(profile + X_KEY_ITER, DEFAULT_KEY_ITER); + String keySalt = props.getProperty(profile + X_KEY_SALT, DEFAULT_KEY_SALT); + + // Verify if cipher is present. + Cipher cipher = InsecureCipherFactory.create(cipherAlgo); + + // Verify if key factory is present. + SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo); + + final int size; + try { + size = Integer.parseInt(keySize); + } catch (Exception e) { + throw securityError(X_KEY_SIZE + EMPTY + keySize); + } + + final int iter; + try { + iter = Integer.parseInt(keyIter); + } catch (Exception e) { + throw securityError(X_KEY_ITER + EMPTY + keyIter); + } + + final byte[] salt; + try { + salt = DatatypeConverter + .parseHexBinary(keySalt.replaceAll(REGEX_WS, EMPTY)); + } catch (Exception e) { + throw securityError(X_KEY_SALT + EMPTY + keySalt); + } + + KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iter, size); + + SecretKey keyBase = factory.generateSecret(keySpec); + + String name = cipherAlgo.toUpperCase(Locale.ROOT); + Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); + Matcher matcherTrans = Pattern.compile(REGEX_TRANS).matcher(name); + if (matcherPBE.matches()) { + paramsAlgo = cipherAlgo; + secretKey = keyBase; + } else if (matcherTrans.find()) { + paramsAlgo = matcherTrans.group(1); + secretKey = new SecretKeySpec(keyBase.getEncoded(), paramsAlgo); + } else { + throw new GeneralSecurityException(MessageFormat.format( + JGitText.get().unsupportedEncryptionAlgorithm, + cipherAlgo)); + } + + // Verify if cipher + key are allowed by policy. + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + cipher.doFinal(); + + } + + // Shared state encrypt -> request. + volatile String context; + + @Override + OutputStream encrypt(OutputStream output) throws IOException { + try { + Cipher cipher = InsecureCipherFactory.create(cipherAlgo); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + AlgorithmParameters params = cipher.getParameters(); + if (params == null) { + context = EMPTY; + } else { + context = Base64.encodeBytes(params.getEncoded()); + } + return new CipherOutputStream(output, cipher); + } catch (Exception e) { throw error(e); - } catch (InvalidAlgorithmParameterException e) { + } + } + + @Override + void request(HttpURLConnection conn, String prefix) throws IOException { + conn.setRequestProperty(prefix + JGIT_PROFILE, profile); + conn.setRequestProperty(prefix + JGIT_VERSION, version); + conn.setRequestProperty(prefix + JGIT_CONTEXT, context); + // No cleanup: + // single encrypt can be followed by several request + // from the AmazonS3.putImpl() multiple retry attempts + // context = null; // Cleanup encrypt -> request transition. + // TODO re-factor AmazonS3.putImpl to be more transaction-like + } + + // Shared state validate -> decrypt. + volatile Cipher decryptCipher; + + @Override + void validate(HttpURLConnection conn, String prefix) + throws IOException { + String prof = conn.getHeaderField(prefix + JGIT_PROFILE); + String vers = conn.getHeaderField(prefix + JGIT_VERSION); + String cont = conn.getHeaderField(prefix + JGIT_CONTEXT); + + if (prof == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_PROFILE)); + } + if (vers == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_VERSION)); + } + if (cont == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_CONTEXT)); + } + if (!profile.equals(prof)) { + throw new IOException(MessageFormat.format( + JGitText.get().unsupportedEncryptionAlgorithm, prof)); + } + if (!version.equals(vers)) { + throw new IOException(MessageFormat.format( + JGitText.get().unsupportedEncryptionVersion, vers)); + } + try { + decryptCipher = InsecureCipherFactory.create(cipherAlgo); + if (cont.isEmpty()) { + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); + } else { + AlgorithmParameters params = AlgorithmParameters + .getInstance(paramsAlgo); + params.init(Base64.decode(cont)); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, params); + } + } catch (Exception e) { throw error(e); } } + + @Override + InputStream decrypt(InputStream input) throws IOException { + try { + return new CipherInputStream(input, decryptCipher); + } finally { + decryptCipher = null; // Cleanup validate -> decrypt transition. + } + } + } + + /** + * Provides JetS3t-like encryption with AES support. Uses V1 connection file + * format. For reference, see: 'jgit-s3-connection-v-1.properties'. + */ + static class JGitV1 extends SymmetricEncryption { + + static final String VERSION = "1"; //$NON-NLS-1$ + + // Re-map connection properties V1 -> V2. + static Properties wrap(String algo, String pass) { + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, algo); + props.put(AmazonS3.Keys.CRYPTO_VER, VERSION); + props.put(AmazonS3.Keys.PASSWORD, pass); + props.put(algo + Keys.X_ALGO, algo); + props.put(algo + Keys.X_KEY_ALGO, algo); + props.put(algo + Keys.X_KEY_ITER, DEFAULT_KEY_ITER); + props.put(algo + Keys.X_KEY_SIZE, DEFAULT_KEY_SIZE); + props.put(algo + Keys.X_KEY_SALT, DEFAULT_KEY_SALT); + return props; + } + + JGitV1(String algo, String pass) + throws GeneralSecurityException { + super(wrap(algo, pass)); + String name = cipherAlgo.toUpperCase(Locale.ROOT); + Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); + if (!matcherPBE.matches()) + throw new GeneralSecurityException( + JGitText.get().encryptionOnlyPBE); + } + + } + + /** + * Supports both PBE and non-PBE algorithms. Uses V2 connection file format. + * For reference, see: 'jgit-s3-connection-v-2.properties'. + */ + static class JGitV2 extends SymmetricEncryption { + + static final String VERSION = "2"; //$NON-NLS-1$ + + JGitV2(Properties props) + throws GeneralSecurityException { + super(props); + } + } + + /** + * Encryption factory. + * + * @param props + * @return instance + * @throws GeneralSecurityException + */ + static WalkEncryption instance(Properties props) + throws GeneralSecurityException { + + String algo = props.getProperty(AmazonS3.Keys.CRYPTO_ALG, Vals.DEFAULT_ALGO); + String vers = props.getProperty(AmazonS3.Keys.CRYPTO_VER, Vals.DEFAULT_VERS); + String pass = props.getProperty(AmazonS3.Keys.PASSWORD); + + if (pass == null) // Disable encryption. + return WalkEncryption.NONE; + + switch (vers) { + case Vals.DEFAULT_VERS: + return new JetS3tV2(algo, pass); + case JGitV1.VERSION: + return new JGitV1(algo, pass); + case JGitV2.VERSION: + return new JGitV2(props); + default: + throw new GeneralSecurityException(MessageFormat.format( + JGitText.get().unsupportedEncryptionVersion, vers)); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -115,10 +115,10 @@ */ class WalkFetchConnection extends BaseFetchConnection { /** The repository this transport fetches into, or pushes out of. */ - private final Repository local; + final Repository local; /** If not null the validator for received objects. */ - private final ObjectChecker objCheck; + final ObjectChecker objCheck; /** * List of all remote repositories we may need to get objects out of. @@ -180,12 +180,12 @@ */ private final HashMap> fetchErrors; - private String lockMessage; + String lockMessage; - private final List packLocks; + final List packLocks; /** Inserter to write objects onto {@link #local}. */ - private final ObjectInserter inserter; + final ObjectInserter inserter; /** Inserter to read objects from {@link #local}. */ private final ObjectReader reader; @@ -195,22 +195,22 @@ local = wt.local; objCheck = wt.getObjectChecker(); inserter = local.newObjectInserter(); - reader = local.newObjectReader(); + reader = inserter.newReader(); - remotes = new ArrayList(); + remotes = new ArrayList<>(); remotes.add(w); - unfetchedPacks = new LinkedList(); - packsConsidered = new HashSet(); + unfetchedPacks = new LinkedList<>(); + packsConsidered = new HashSet<>(); - noPacksYet = new LinkedList(); + noPacksYet = new LinkedList<>(); noPacksYet.add(w); - noAlternatesYet = new LinkedList(); + noAlternatesYet = new LinkedList<>(); noAlternatesYet.add(w); - fetchErrors = new HashMap>(); - packLocks = new ArrayList(4); + fetchErrors = new HashMap<>(); + packLocks = new ArrayList<>(4); revWalk = new RevWalk(reader); revWalk.setRetainBody(false); @@ -220,13 +220,16 @@ LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$ localCommitQueue = new DateRevQueue(); - workQueue = new LinkedList(); + workQueue = new LinkedList<>(); } + /** {@inheritDoc} */ + @Override public boolean didFetchTestConnectivity() { return true; } + /** {@inheritDoc} */ @Override protected void doFetch(final ProgressMonitor monitor, final Collection want, final Set have) @@ -240,16 +243,27 @@ downloadObject(monitor, id); process(id); } + + try { + inserter.flush(); + } catch (IOException e) { + throw new TransportException(e.getMessage(), e); + } } + /** {@inheritDoc} */ + @Override public Collection getPackLocks() { return packLocks; } + /** {@inheritDoc} */ + @Override public void setPackLockMessage(final String message) { lockMessage = message; } + /** {@inheritDoc} */ @Override public void close() { inserter.close(); @@ -264,9 +278,13 @@ private void queueWants(final Collection want) throws TransportException { - final HashSet inWorkQueue = new HashSet(); + final HashSet inWorkQueue = new HashSet<>(); for (final Ref r : want) { final ObjectId id = r.getObjectId(); + if (id == null) { + throw new NullPointerException(MessageFormat.format( + JGitText.get().transportProvidedRefWithNoObjectId, r.getName())); + } try { final RevObject obj = revWalk.parseAny(id); if (obj.has(COMPLETE)) @@ -584,7 +602,7 @@ private Iterator swapFetchQueue() { final Iterator r = workQueue.iterator(); - workQueue = new LinkedList(); + workQueue = new LinkedList<>(); return r; } @@ -633,10 +651,11 @@ final byte[] raw = uol.getCachedBytes(); if (objCheck != null) { try { - objCheck.check(type, raw); + objCheck.check(id, type, raw); } catch (CorruptObjectException e) { - throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid - , Constants.typeString(type), id.name(), e.getMessage())); + throw new TransportException(MessageFormat.format( + JGitText.get().transportExceptionInvalid, + Constants.typeString(type), id.name(), e.getMessage())); } } @@ -647,7 +666,6 @@ Constants.typeString(type), Integer.valueOf(compressed.length))); } - inserter.flush(); } private Collection expandOneAlternate( @@ -781,7 +799,7 @@ final ObjectId objId = id.copy(); List errors = fetchErrors.get(objId); if (errors == null) { - errors = new ArrayList(2); + errors = new ArrayList<>(2); fetchErrors.put(objId, errors); } errors.add(what); @@ -871,14 +889,17 @@ void downloadPack(final ProgressMonitor monitor) throws IOException { String name = "pack/" + packName; //$NON-NLS-1$ WalkRemoteObjectDatabase.FileStream s = connection.open(name); - PackParser parser = inserter.newPackParser(s.in); - parser.setAllowThin(false); - parser.setObjectChecker(objCheck); - parser.setLockMessage(lockMessage); - PackLock lock = parser.parse(monitor); - if (lock != null) - packLocks.add(lock); - inserter.flush(); + try { + PackParser parser = inserter.newPackParser(s.in); + parser.setAllowThin(false); + parser.setObjectChecker(objCheck); + parser.setLockMessage(lockMessage); + PackLock lock = parser.parse(monitor); + if (lock != null) + packLocks.add(lock); + } finally { + s.in.close(); + } } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,7 @@ import static org.eclipse.jgit.transport.WalkRemoteObjectDatabase.ROOT_DIR; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -69,7 +70,6 @@ import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * Generic push support for dumb transport protocols. @@ -103,7 +103,7 @@ private final URIish uri; /** Database connection to the remote repository. */ - private final WalkRemoteObjectDatabase dest; + final WalkRemoteObjectDatabase dest; /** The configured transport we were constructed by. */ private final Transport transport; @@ -134,25 +134,29 @@ dest = w; } + /** {@inheritDoc} */ + @Override public void push(final ProgressMonitor monitor, final Map refUpdates) throws TransportException { push(monitor, refUpdates, null); } + /** {@inheritDoc} */ + @Override public void push(final ProgressMonitor monitor, final Map refUpdates, OutputStream out) throws TransportException { markStartedOperation(); packNames = null; - newRefs = new TreeMap(getRefsMap()); - packedRefUpdates = new ArrayList(refUpdates.size()); + newRefs = new TreeMap<>(getRefsMap()); + packedRefUpdates = new ArrayList<>(refUpdates.size()); // Filter the commands and issue all deletes first. This way we // can correctly handle a directory being cleared out and a new // ref using the directory name being created. // - final List updates = new ArrayList(); + final List updates = new ArrayList<>(); for (final RemoteRefUpdate u : refUpdates.values()) { final String n = u.getRemoteName(); if (!n.startsWith("refs/") || !Repository.isValidRefName(n)) { //$NON-NLS-1$ @@ -210,6 +214,7 @@ } } + /** {@inheritDoc} */ @Override public void close() { dest.close(); @@ -223,8 +228,8 @@ try (final PackWriter writer = new PackWriter(transport.getPackConfig(), local.newObjectReader())) { - final Set need = new HashSet(); - final Set have = new HashSet(); + final Set need = new HashSet<>(); + final Set have = new HashSet<>(); for (final RemoteRefUpdate r : updates) need.add(r.getNewObjectId()); for (final Ref r : getRefs()) { @@ -241,7 +246,7 @@ if (writer.getObjectCount() == 0) return; - packNames = new LinkedHashMap(); + packNames = new LinkedHashMap<>(); for (final String n : dest.getPackNames()) packNames.put(n, n); @@ -262,28 +267,22 @@ // Write the pack file, then the index, as readers look the // other direction (index, then pack file). // - final String wt = "Put " + base.substring(0, 12); //$NON-NLS-1$ - OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack"); //$NON-NLS-1$ - try { - os = new SafeBufferedOutputStream(os); + String wt = "Put " + base.substring(0, 12); //$NON-NLS-1$ + try (OutputStream os = new BufferedOutputStream( + dest.writeFile(pathPack, monitor, wt + "..pack"))) { //$NON-NLS-1$ writer.writePack(monitor, monitor, os); - } finally { - os.close(); } - os = dest.writeFile(pathIdx, monitor, wt + "..idx"); //$NON-NLS-1$ - try { - os = new SafeBufferedOutputStream(os); + try (OutputStream os = new BufferedOutputStream( + dest.writeFile(pathIdx, monitor, wt + "..idx"))) { //$NON-NLS-1$ writer.writeIndex(os); - } finally { - os.close(); } // Record the pack at the start of the pack info list. This // way clients are likely to consult the newest pack first, // and discover the most recent objects there. // - final ArrayList infoPacks = new ArrayList(); + final ArrayList infoPacks = new ArrayList<>(); infoPacks.add(packName); infoPacks.addAll(packNames.keySet()); dest.writeInfoPacks(infoPacks); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java 2019-09-03 12:37:49.000000000 +0000 @@ -133,6 +133,9 @@ * Callers such as {@link WalkFetchConnection} are prepared to handle this * by validating the content received, and assuming content that fails to * match its hash is an incorrectly phrased FileNotFoundException. + *

    + * This method is recommended for already compressed files like loose objects + * and pack files. For text files, see {@link #openReader(String)}. * * @param path * location of the file to read, relative to this objects @@ -261,11 +264,8 @@ * failed, possibly due to permissions or remote disk full, etc. */ void writeFile(final String path, final byte[] data) throws IOException { - final OutputStream os = writeFile(path, null, null); - try { + try (OutputStream os = writeFile(path, null, null)) { os.write(data); - } finally { - os.close(); } } @@ -346,8 +346,8 @@ /** * Open a buffered reader around a file. *

    - * This is shorthand for calling {@link #open(String)} and then wrapping it - * in a reader suitable for line oriented files like the alternates list. + * This method is suitable for for reading line-oriented resources like + * info/packs, info/refs, and the alternates list. * * @return a stream to read from the file. Never null. * @param path @@ -391,9 +391,8 @@ */ Collection readAlternates(final String listPath) throws IOException { - final BufferedReader br = openReader(listPath); - try { - final Collection alts = new ArrayList(); + try (BufferedReader br = openReader(listPath)) { + final Collection alts = new ArrayList<>(); for (;;) { String line = br.readLine(); if (line == null) @@ -403,8 +402,6 @@ alts.add(openAlternate(line)); } return alts; - } finally { - br.close(); } } @@ -414,19 +411,13 @@ * @param avail * return collection of references. Any existing entries will be * replaced if they are found in the packed-refs file. - * @throws TransportException + * @throws org.eclipse.jgit.errors.TransportException * an error occurred reading from the packed refs file. */ protected void readPackedRefs(final Map avail) throws TransportException { - try { - final BufferedReader br = openReader(ROOT_DIR - + Constants.PACKED_REFS); - try { - readPackedRefsImpl(avail, br); - } finally { - br.close(); - } + try (BufferedReader br = openReader(ROOT_DIR + Constants.PACKED_REFS)) { + readPackedRefsImpl(avail, br); } catch (FileNotFoundException notPacked) { // Perhaps it wasn't worthwhile, or is just an older repository. } catch (IOException e) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,8 +53,9 @@ * from the loose objects directory, or entire packs if the source side does not * have the object as a loose object. *

    - * WalkTransports are not as efficient as {@link PackTransport} instances, but - * can be useful in situations where a pack transport is not acceptable. + * WalkTransports are not as efficient as + * {@link org.eclipse.jgit.transport.PackTransport} instances, but can be useful + * in situations where a pack transport is not acceptable. * * @see WalkFetchConnection */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WantNotValidException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WantNotValidException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WantNotValidException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WantNotValidException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016, 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.text.MessageFormat; + +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AnyObjectId; + +/** + * Indicates client requested an object the server does not want to serve. + *

    + * Typically visible only inside of the server implementation; clients are + * usually looking at the text message from the server in a generic + * {@link org.eclipse.jgit.errors.PackProtocolException}. + * + * @since 4.3 + */ +public class WantNotValidException extends PackProtocolException { + private static final long serialVersionUID = 1L; + + /** + * Construct a {@code "want $id not valid"} exception. + * + * @param id + * invalid object identifier received from the client. + */ + public WantNotValidException(AnyObjectId id) { + super(msg(id)); + } + + /** + * Construct a {@code "want $id not valid"} exception. + * + * @param id + * invalid object identifier received from the client. + * @param cause + * root cause of the object being invalid, such as an IOException + * from the storage system. + */ + public WantNotValidException(AnyObjectId id, Throwable cause) { + super(msg(id), cause); + } + + private static String msg(AnyObjectId id) { + return MessageFormat.format(JGitText.get().wantNotValid, id.name()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,7 @@ * An exception to be thrown when the write operation is aborted. *

    * That can be thrown inside - * {@link ObjectCountCallback#setObjectCount(long)}. + * {@link org.eclipse.jgit.transport.ObjectCountCallback#setObjectCount(long)}. * * @since 4.1 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,8 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; -import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.attributes.AttributesHandler; +import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; @@ -57,7 +58,7 @@ import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.Paths; /** * Walks a Git tree (directory) in Git sort order. @@ -86,13 +87,26 @@ /** A dummy object id buffer that matches the zero ObjectId. */ protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH]; - /** Iterator for the parent tree; null if we are the root iterator. */ - final AbstractTreeIterator parent; + /** + * Iterator for the parent tree; null if we are the root iterator. + *

    + * Used by {@link TreeWalk} and {@link AttributesHandler} + * + * @since 4.3 + */ + public final AbstractTreeIterator parent; /** The iterator this current entry is path equal to. */ AbstractTreeIterator matches; /** + * Parsed rules of .gitattributes file if it exists. + * + * @since 4.2 + */ + protected AttributesNode attributesNode; + + /** * Number of entries we moved forward to force a D/F conflict match. * * @see NameConflictTreeWalk @@ -138,7 +152,9 @@ */ protected int pathLen; - /** Create a new iterator with no parent. */ + /** + * Create a new iterator with no parent. + */ protected AbstractTreeIterator() { parent = null; path = new byte[DEFAULT_PATH_SIZE]; @@ -320,6 +336,42 @@ } /** + * Seek the iterator on a file, if present. + * + * @param name + * file name to find (will not find a directory). + * @return true if the file exists in this tree; false otherwise. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * tree is invalid. + * @since 4.2 + */ + public boolean findFile(String name) throws CorruptObjectException { + return findFile(Constants.encode(name)); + } + + /** + * Seek the iterator on a file, if present. + * + * @param name + * file name to find (will not find a directory). + * @return true if the file exists in this tree; false otherwise. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * tree is invalid. + * @since 4.2 + */ + public boolean findFile(byte[] name) throws CorruptObjectException { + for (; !eof(); next(1)) { + int cmp = pathCompare(name, 0, name.length, 0, pathOffset); + if (cmp == 0) { + return true; + } else if (cmp > 0) { + return false; + } + } + return false; + } + + /** * Compare the path of this current entry to a raw buffer. * * @param buf @@ -338,20 +390,9 @@ } private int pathCompare(byte[] b, int bPos, int bEnd, int bMode, int aPos) { - final byte[] a = path; - final int aEnd = pathLen; - - for (; aPos < aEnd && bPos < bEnd; aPos++, bPos++) { - final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff); - if (cmp != 0) - return cmp; - } - - if (aPos < aEnd) - return (a[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(mode) - (b[bPos] & 0xff); - return lastPathChar(mode) - lastPathChar(bMode); + return Paths.compare( + path, aPos, pathLen, mode, + b, bPos, bEnd, bMode); } private static int alreadyMatch(AbstractTreeIterator a, @@ -368,10 +409,6 @@ } } - private static int lastPathChar(final int mode) { - return FileMode.TREE.equals(mode) ? '/' : '\0'; - } - /** * Check if the current entry of both iterators has the same id. *

    @@ -388,7 +425,11 @@ otherIterator.idBuffer(), otherIterator.idOffset()); } - /** @return true if the entry has a valid ObjectId. */ + /** + * Whether the entry has a valid ObjectId. + * + * @return {@code true} if the entry has a valid ObjectId. + */ public abstract boolean hasId(); /** @@ -410,17 +451,29 @@ out.fromRaw(idBuffer(), idOffset()); } - /** @return the file mode of the current entry. */ + /** + * Get the file mode of the current entry. + * + * @return the file mode of the current entry. + */ public FileMode getEntryFileMode() { return FileMode.fromBits(mode); } - /** @return the file mode of the current entry as bits */ + /** + * Get the file mode of the current entry as bits. + * + * @return the file mode of the current entry as bits. + */ public int getEntryRawMode() { return mode; } - /** @return path of the current entry, as a string. */ + /** + * Get path of the current entry, as a string. + * + * @return path of the current entry, as a string. + */ public String getEntryPathString() { return TreeWalk.pathOf(this); } @@ -437,7 +490,11 @@ return path; } - /** @return length of the path in {@link #getEntryPathBuffer()}. */ + /** + * Get length of the path in {@link #getEntryPathBuffer()}. + * + * @return length of the path in {@link #getEntryPathBuffer()}. + */ public int getEntryPathLength() { return pathLen; } @@ -492,10 +549,10 @@ * @param reader * reader to load the tree data from. * @return a new parser that walks over the current subtree. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the current entry is not actually a tree and cannot be parsed * as though it were a tree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public abstract AbstractTreeIterator createSubtreeIterator( @@ -523,10 +580,10 @@ * @param idBuffer * temporary ObjectId buffer for use by this method. * @return a new parser that walks over the current subtree. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the current entry is not actually a tree and cannot be parsed * as though it were a tree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public AbstractTreeIterator createSubtreeIterator( @@ -543,7 +600,7 @@ * method of repositioning the iterator to its first entry, so subclasses * are strongly encouraged to override the method. * - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tree is invalid. */ public void reset() throws CorruptObjectException { @@ -592,7 +649,7 @@ * @param delta * number of entries to move the iterator by. Must be a positive, * non-zero integer. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tree is invalid. */ public abstract void next(int delta) throws CorruptObjectException; @@ -616,7 +673,7 @@ * @param delta * number of entries to move the iterator by. Must be a positive, * non-zero integer. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tree is invalid. */ public abstract void back(int delta) throws CorruptObjectException; @@ -625,11 +682,12 @@ * Advance to the next tree entry, populating this iterator with its data. *

    * This method behaves like seek(1) but is called by - * {@link TreeWalk} only if a {@link TreeFilter} was used and ruled out the - * current entry from the results. In such cases this tree iterator may - * perform special behavior. + * {@link org.eclipse.jgit.treewalk.TreeWalk} only if a + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter} was used and ruled + * out the current entry from the results. In such cases this tree iterator + * may perform special behavior. * - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the tree is invalid. */ public void skip() throws CorruptObjectException { @@ -648,14 +706,28 @@ } /** - * @return the length of the name component of the path for the current entry + * Whether the iterator implements {@link #stopWalk()}. + * + * @return {@code true} if the iterator implements {@link #stopWalk()}. + * @since 4.2 + */ + protected boolean needsStopWalk() { + return false; + } + + /** + * Get the length of the name component of the path for the current entry. + * + * @return the length of the name component of the path for the current + * entry. */ public int getNameLength() { return pathLen - pathOffset; } /** - * JGit internal API for use by {@link DirCacheCheckout} + * JGit internal API for use by + * {@link org.eclipse.jgit.dircache.DirCacheCheckout} * * @return start of name component part within {@link #getEntryPathBuffer()} * @since 2.0 @@ -679,9 +751,21 @@ System.arraycopy(path, pathOffset, buffer, offset, pathLen - pathOffset); } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { return getClass().getSimpleName() + "[" + getEntryPathString() + "]"; //$NON-NLS-1$ } + + /** + * Whether or not this Iterator is iterating through the working tree. + * + * @return whether or not this Iterator is iterating through the working + * tree + * @since 4.3 + */ + public boolean isWorkTree() { + return false; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,21 +44,34 @@ package org.eclipse.jgit.treewalk; +import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.lib.Constants.TYPE_TREE; +import static org.eclipse.jgit.lib.Constants.encode; + import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; -/** Parses raw Git trees from the canonical semi-text/semi-binary format. */ +/** + * Parses raw Git trees from the canonical semi-text/semi-binary format. + */ public class CanonicalTreeParser extends AbstractTreeIterator { private static final byte[] EMPTY = {}; + private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES); private byte[] raw; @@ -71,7 +84,9 @@ /** Offset one past the current entry (first byte of next entry). */ private int nextPtr; - /** Create a new parser. */ + /** + * Create a new parser. + */ public CanonicalTreeParser() { reset(EMPTY); } @@ -91,10 +106,10 @@ * messages if data corruption is found. * @throws MissingObjectException * the object supplied is not available from the repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object supplied as an argument is not actually a tree and * cannot be parsed as though it were a tree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader, @@ -109,7 +124,9 @@ } /** - * @return the parent of this tree parser + * Get the parent of this tree parser. + * + * @return the parent of this tree parser. * @deprecated internal use only */ @Deprecated @@ -124,6 +141,7 @@ * the raw tree content. */ public void reset(final byte[] treeData) { + attributesNode = null; raw = treeData; prevPtr = -1; currPtr = 0; @@ -144,10 +162,10 @@ * @return the root level parser. * @throws MissingObjectException * the object supplied is not available from the repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object supplied as an argument is not actually a tree and * cannot be parsed as though it were a tree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public CanonicalTreeParser resetRoot(final ObjectReader reader, @@ -160,7 +178,11 @@ return p; } - /** @return this iterator, or its parent, if the tree is at eof. */ + /** + * Get this iterator, or its parent, if the tree is at eof. + * + * @return this iterator, or its parent, if the tree is at eof. + */ public CanonicalTreeParser next() { CanonicalTreeParser p = this; for (;;) { @@ -191,17 +213,18 @@ * messages if data corruption is found. * @throws MissingObjectException * the object supplied is not available from the repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object supplied as an argument is not actually a tree and * cannot be parsed as though it were a tree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public void reset(final ObjectReader reader, final AnyObjectId id) throws IncorrectObjectTypeException, IOException { - reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes()); + reset(reader.open(id, OBJ_TREE).getCachedBytes()); } + /** {@inheritDoc} */ @Override public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader, final MutableObjectId idBuffer) @@ -209,7 +232,7 @@ idBuffer.fromRaw(idBuffer(), idOffset()); if (!FileMode.TREE.equals(mode)) { final ObjectId me = idBuffer.toObjectId(); - throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE); + throw new IncorrectObjectTypeException(me, TYPE_TREE); } return createSubtreeIterator0(reader, idBuffer); } @@ -226,7 +249,7 @@ * @param id * ObjectId of the tree to open. * @return a new parser that walks over the current subtree. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public final CanonicalTreeParser createSubtreeIterator0( @@ -237,41 +260,51 @@ return p; } + /** {@inheritDoc} */ + @Override public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { return createSubtreeIterator(reader, new MutableObjectId()); } + /** {@inheritDoc} */ @Override public boolean hasId() { return true; } + /** {@inheritDoc} */ @Override public byte[] idBuffer() { return raw; } + /** {@inheritDoc} */ @Override public int idOffset() { - return nextPtr - Constants.OBJECT_ID_LENGTH; + return nextPtr - OBJECT_ID_LENGTH; } + /** {@inheritDoc} */ @Override public void reset() { if (!first()) reset(raw); } + /** {@inheritDoc} */ @Override public boolean first() { return currPtr == 0; } + /** {@inheritDoc} */ + @Override public boolean eof() { return currPtr == raw.length; } + /** {@inheritDoc} */ @Override public void next(int delta) { if (delta == 1) { @@ -292,7 +325,7 @@ prevPtr = ptr; while (raw[ptr] != 0) ptr++; - ptr += Constants.OBJECT_ID_LENGTH + 1; + ptr += OBJECT_ID_LENGTH + 1; } if (delta != 0) throw new ArrayIndexOutOfBoundsException(delta); @@ -301,6 +334,7 @@ parseEntry(); } + /** {@inheritDoc} */ @Override public void back(int delta) { if (delta == 1 && 0 <= prevPtr) { @@ -328,7 +362,7 @@ trace[delta] = ptr; while (raw[ptr] != 0) ptr++; - ptr += Constants.OBJECT_ID_LENGTH + 1; + ptr += OBJECT_ID_LENGTH + 1; } if (trace[1] == -1) throw new ArrayIndexOutOfBoundsException(delta); @@ -363,6 +397,49 @@ } } pathLen = tmp; - nextPtr = ptr + Constants.OBJECT_ID_LENGTH; + nextPtr = ptr + OBJECT_ID_LENGTH; + } + + /** + * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. + * + * @param reader + * {@link org.eclipse.jgit.lib.ObjectReader} used to parse the + * .gitattributes entry. + * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. + * @throws java.io.IOException + * @since 4.2 + */ + public AttributesNode getEntryAttributesNode(ObjectReader reader) + throws IOException { + if (attributesNode == null) { + attributesNode = findAttributes(reader); + } + return attributesNode.getRules().isEmpty() ? null : attributesNode; + } + + private AttributesNode findAttributes(ObjectReader reader) + throws IOException { + CanonicalTreeParser itr = new CanonicalTreeParser(); + itr.reset(raw); + if (itr.findFile(ATTRS)) { + return loadAttributes(reader, itr.getEntryObjectId()); + } + return noAttributes(); + } + + private static AttributesNode loadAttributes(ObjectReader reader, + AnyObjectId id) throws IOException { + AttributesNode r = new AttributesNode(); + try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) { + r.parse(in); + } + return r.getRules().isEmpty() ? noAttributes() : r; + } + + private static AttributesNode noAttributes() { + return new AttributesNode(Collections. emptyList()); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,9 +52,13 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; -/** Iterator over an empty tree (a directory with no files). */ +/** + * Iterator over an empty tree (a directory with no files). + */ public class EmptyTreeIterator extends AbstractTreeIterator { - /** Create a new iterator with no parent. */ + /** + * Create a new iterator with no parent. + */ public EmptyTreeIterator() { // Create a root empty tree. } @@ -86,60 +90,77 @@ pathLen = childPathOffset - 1; } + /** {@inheritDoc} */ @Override public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { return new EmptyTreeIterator(this); } + /** {@inheritDoc} */ @Override public boolean hasId() { return false; } + /** {@inheritDoc} */ @Override public ObjectId getEntryObjectId() { return ObjectId.zeroId(); } + /** {@inheritDoc} */ @Override public byte[] idBuffer() { return zeroid; } + /** {@inheritDoc} */ @Override public int idOffset() { return 0; } + /** {@inheritDoc} */ @Override public void reset() { // Do nothing. } + /** {@inheritDoc} */ @Override public boolean first() { return true; } + /** {@inheritDoc} */ @Override public boolean eof() { return true; } + /** {@inheritDoc} */ @Override public void next(final int delta) throws CorruptObjectException { // Do nothing. } + /** {@inheritDoc} */ @Override public void back(final int delta) throws CorruptObjectException { // Do nothing. } + /** {@inheritDoc} */ @Override public void stopWalk() { if (parent != null) parent.stopWalk(); } + + /** {@inheritDoc} */ + @Override + protected boolean needsStopWalk() { + return parent != null && parent.needsStopWalk(); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,12 +63,13 @@ * Working directory iterator for standard Java IO. *

    * This iterator uses the standard java.io package to read the - * specified working directory as part of a {@link TreeWalk}. + * specified working directory as part of a + * {@link org.eclipse.jgit.treewalk.TreeWalk}. */ public class FileTreeIterator extends WorkingTreeIterator { /** - * the starting directory. This directory should correspond to the root of - * the repository. + * the starting directory of this Iterator. All entries are located directly + * in this directory. */ protected final File directory; @@ -79,14 +80,41 @@ protected final FS fs; /** + * the strategy used to compute the FileMode for a FileEntry. Can be used to + * control things such as whether to recurse into a directory or create a + * gitlink. + * + * @since 4.3 + */ + protected final FileModeStrategy fileModeStrategy; + + /** * Create a new iterator to traverse the work tree and its children. * * @param repo * the repository whose working tree will be scanned. */ public FileTreeIterator(Repository repo) { + this(repo, + repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ? + NoGitlinksStrategy.INSTANCE : + DefaultFileModeStrategy.INSTANCE); + } + + /** + * Create a new iterator to traverse the work tree and its children. + * + * @param repo + * the repository whose working tree will be scanned. + * @param fileModeStrategy + * the strategy to use to determine the FileMode for a FileEntry; + * controls gitlinks etc. + * @since 4.3 + */ + public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) { this(repo.getWorkTree(), repo.getFS(), - repo.getConfig().get(WorkingTreeOptions.KEY)); + repo.getConfig().get(WorkingTreeOptions.KEY), + fileModeStrategy); initRootIterator(repo); } @@ -103,9 +131,31 @@ * working tree options to be used */ public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options) { + this(root, fs, options, DefaultFileModeStrategy.INSTANCE); + } + + /** + * Create a new iterator to traverse the given directory and its children. + * + * @param root + * the starting directory. This directory should correspond to + * the root of the repository. + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @param options + * working tree options to be used + * @param fileModeStrategy + * the strategy to use to determine the FileMode for a FileEntry; + * controls gitlinks etc. + * @since 4.3 + */ + public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options, + FileModeStrategy fileModeStrategy) { super(options); directory = root; this.fs = fs; + this.fileModeStrategy = fileModeStrategy; init(entries()); } @@ -114,25 +164,71 @@ * * @param p * the parent iterator we were created from. + * @param root + * the subdirectory. This should be a directory contained within + * the parent directory. * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. + * @since 4.3 + * @deprecated use {@link #FileTreeIterator(FileTreeIterator, File, FS)} + * instead. + */ + @Deprecated + protected FileTreeIterator(final WorkingTreeIterator p, final File root, + FS fs) { + this(p, root, fs, DefaultFileModeStrategy.INSTANCE); + } + + /** + * Create a new iterator to traverse a subdirectory. + * + * @param p + * the parent iterator we were created from. * @param root * the subdirectory. This should be a directory contained within * the parent directory. + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @since 4.3 */ - protected FileTreeIterator(final WorkingTreeIterator p, final File root, + protected FileTreeIterator(final FileTreeIterator p, final File root, FS fs) { + this(p, root, fs, p.fileModeStrategy); + } + + /** + * Create a new iterator to traverse a subdirectory, given the specified + * FileModeStrategy. + * + * @param p + * the parent iterator we were created from. + * @param root + * the subdirectory. This should be a directory contained within + * the parent directory + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @param fileModeStrategy + * the strategy to use to determine the FileMode for a given + * FileEntry. + * @since 4.3 + */ + protected FileTreeIterator(final WorkingTreeIterator p, final File root, + FS fs, FileModeStrategy fileModeStrategy) { super(p); directory = root; this.fs = fs; + this.fileModeStrategy = fileModeStrategy; init(entries()); } + /** {@inheritDoc} */ @Override public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { - return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs); + return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy); } private Entry[] entries() { @@ -141,11 +237,92 @@ return EOF; final Entry[] r = new Entry[all.length]; for (int i = 0; i < r.length; i++) - r[i] = new FileEntry(all[i], fs); + r[i] = new FileEntry(all[i], fs, fileModeStrategy); return r; } /** + * An interface representing the methods used to determine the FileMode for + * a FileEntry. + * + * @since 4.3 + */ + public interface FileModeStrategy { + /** + * Compute the FileMode for a given File, based on its attributes. + * + * @param f + * the file to return a FileMode for + * @param attributes + * the attributes of a file + * @return a FileMode indicating whether the file is a regular file, a + * directory, a gitlink, etc. + */ + FileMode getMode(File f, FS.Attributes attributes); + } + + /** + * A default implementation of a FileModeStrategy; defaults to treating + * nested .git directories as gitlinks, etc. + * + * @since 4.3 + */ + static public class DefaultFileModeStrategy implements FileModeStrategy { + /** + * a singleton instance of the default FileModeStrategy + */ + public final static DefaultFileModeStrategy INSTANCE = + new DefaultFileModeStrategy(); + + @Override + public FileMode getMode(File f, FS.Attributes attributes) { + if (attributes.isSymbolicLink()) { + return FileMode.SYMLINK; + } else if (attributes.isDirectory()) { + if (new File(f, Constants.DOT_GIT).exists()) { + return FileMode.GITLINK; + } else { + return FileMode.TREE; + } + } else if (attributes.isExecutable()) { + return FileMode.EXECUTABLE_FILE; + } else { + return FileMode.REGULAR_FILE; + } + } + } + + /** + * A FileModeStrategy that implements native git's DIR_NO_GITLINKS + * behavior. This is the same as the default FileModeStrategy, except + * all directories will be treated as directories regardless of whether + * or not they contain a .git directory or file. + * + * @since 4.3 + */ + static public class NoGitlinksStrategy implements FileModeStrategy { + + /** + * a singleton instance of the default FileModeStrategy + */ + public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy(); + + @Override + public FileMode getMode(File f, FS.Attributes attributes) { + if (attributes.isSymbolicLink()) { + return FileMode.SYMLINK; + } else if (attributes.isDirectory()) { + return FileMode.TREE; + } else if (attributes.isExecutable()) { + return FileMode.EXECUTABLE_FILE; + } else { + return FileMode.REGULAR_FILE; + } + } + } + + + /** * Wrapper for a standard Java IO file */ static public class FileEntry extends Entry { @@ -164,20 +341,27 @@ * file system */ public FileEntry(File f, FS fs) { + this(f, fs, DefaultFileModeStrategy.INSTANCE); + } + + /** + * Create a new file entry given the specified FileModeStrategy + * + * @param f + * file + * @param fs + * file system + * @param fileModeStrategy + * the strategy to use when determining the FileMode of a + * file; controls gitlinks etc. + * + * @since 4.3 + */ + public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) { this.fs = fs; f = fs.normalize(f); attributes = fs.getAttributes(f); - if (attributes.isSymbolicLink()) - mode = FileMode.SYMLINK; - else if (attributes.isDirectory()) { - if (new File(f, Constants.DOT_GIT).exists()) - mode = FileMode.GITLINK; - else - mode = FileMode.TREE; - } else if (attributes.isExecutable()) - mode = FileMode.EXECUTABLE_FILE; - else - mode = FileMode.REGULAR_FILE; + mode = fileModeStrategy.getMode(f, attributes); } @Override @@ -221,6 +405,8 @@ } /** + *

    Getter for the field directory.

    + * * @return The root directory of this iterator */ public File getDirectory() { @@ -228,6 +414,8 @@ } /** + * Get the location of the working file. + * * @return The location of the working file. This is the same as {@code new * File(getDirectory(), getEntryPath())} but may be faster by * reusing an internal File instance. @@ -236,10 +424,15 @@ return ((FileEntry) current()).getFile(); } + /** {@inheritDoc} */ @Override protected byte[] idSubmodule(final Entry e) { - if (repository == null) - return idSubmodule(getDirectory(), e); - return super.idSubmodule(e); + return idSubmodule(getDirectory(), e); + } + + /** {@inheritDoc} */ + @Override + protected String readSymlinkTarget(Entry entry) throws IOException { + return fs.readSymLink(getEntryFile()); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,9 +56,10 @@ * Includes a tree entry only if all subfilters include the same tree entry. *

    * Classic shortcut behavior is used, so evaluation of the - * {@link TreeFilter#include(TreeWalk)} method stops as soon as a false result - * is obtained. Applications can improve filtering performance by placing faster - * filters that are more likely to reject a result earlier in the list. + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)} method + * stops as soon as a false result is obtained. Applications can improve + * filtering performance by placing faster filters that are more likely to + * reject a result earlier in the list. */ public abstract class AndTreeFilter extends TreeFilter { /** @@ -128,7 +129,25 @@ public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { - return a.include(walker) && b.include(walker); + return matchFilter(walker) <= 0; + } + + @Override + public int matchFilter(TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + final int ra = a.matchFilter(walker); + if (ra == 1) { + return 1; + } + final int rb = b.matchFilter(walker); + if (rb == 1) { + return 1; + } + if (ra == -1 || rb == -1) { + return -1; + } + return 0; } @Override @@ -159,11 +178,24 @@ public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { + return matchFilter(walker) <= 0; + } + + @Override + public int matchFilter(TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + int m = 0; for (final TreeFilter f : subfilters) { - if (!f.include(walker)) - return false; + int r = f.matchFilter(walker); + if (r == 1) { + return 1; + } + if (r == -1) { + m = -1; + } } - return true; + return m; } @Override diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java 2019-09-03 12:37:49.000000000 +0000 @@ -213,6 +213,7 @@ table = new byte[sz][]; } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,26 +58,33 @@ import org.eclipse.jgit.treewalk.WorkingTreeIterator; /** - * A performance optimized variant of {@link TreeFilter#ANY_DIFF} which should - * be used when among the walked trees there is a {@link DirCacheIterator} and a - * {@link WorkingTreeIterator}. Please see the documentation of - * {@link TreeFilter#ANY_DIFF} for a basic description of the semantics. + * A performance optimized variant of + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} which should be + * used when among the walked trees there is a + * {@link org.eclipse.jgit.dircache.DirCacheIterator} and a + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator}. Please see the + * documentation of {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} + * for a basic description of the semantics. *

    * This filter tries to avoid computing content ids of the files in the - * working-tree. In contrast to {@link TreeFilter#ANY_DIFF} this filter takes - * care to first compare the entry from the {@link DirCacheIterator} with the - * entries from all other iterators besides the {@link WorkingTreeIterator}. - * Since all those entries have fast access to content ids that is very fast. If - * a difference is detected in this step this filter decides to include that - * path before even looking at the working-tree entry. + * working-tree. In contrast to + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} this filter + * takes care to first compare the entry from the + * {@link org.eclipse.jgit.dircache.DirCacheIterator} with the entries from all + * other iterators besides the + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator}. Since all those + * entries have fast access to content ids that is very fast. If a difference is + * detected in this step this filter decides to include that path before even + * looking at the working-tree entry. *

    * If no difference is found then we have to compare index and working-tree as * the last step. By making use of - * {@link WorkingTreeIterator#isModified(org.eclipse.jgit.dircache.DirCacheEntry, boolean, ObjectReader)} + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator#isModified(org.eclipse.jgit.dircache.DirCacheEntry, boolean, ObjectReader)} * we can avoid the computation of the content id if the file is not dirty. *

    - * Instances of this filter should not be used for multiple {@link TreeWalk}s. - * Always construct a new instance of this filter for each TreeWalk. + * Instances of this filter should not be used for multiple + * {@link org.eclipse.jgit.treewalk.TreeWalk}s. Always construct a new instance + * of this filter for each TreeWalk. */ public class IndexDiffFilter extends TreeFilter { private final int dirCache; @@ -86,22 +93,24 @@ private final boolean honorIgnores; - private final Set ignoredPaths = new HashSet(); + private final Set ignoredPaths = new HashSet<>(); - private final LinkedList untrackedParentFolders = new LinkedList(); + private final LinkedList untrackedParentFolders = new LinkedList<>(); - private final LinkedList untrackedFolders = new LinkedList(); + private final LinkedList untrackedFolders = new LinkedList<>(); /** * Creates a new instance of this filter. Do not use an instance of this * filter in multiple treewalks. * * @param dirCacheIndex - * the index of the {@link DirCacheIterator} in the associated - * treewalk + * the index of the + * {@link org.eclipse.jgit.dircache.DirCacheIterator} in the + * associated treewalk * @param workingTreeIndex - * the index of the {@link WorkingTreeIterator} in the associated - * treewalk + * the index of the + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} in the + * associated treewalk */ public IndexDiffFilter(int dirCacheIndex, int workingTreeIndex) { this(dirCacheIndex, workingTreeIndex, true /* honor ignores */); @@ -112,14 +121,16 @@ * filter in multiple treewalks. * * @param dirCacheIndex - * the index of the {@link DirCacheIterator} in the associated - * treewalk + * the index of the + * {@link org.eclipse.jgit.dircache.DirCacheIterator} in the + * associated treewalk * @param workingTreeIndex - * the index of the {@link WorkingTreeIterator} in the associated - * treewalk + * the index of the + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} in the + * associated treewalk * @param honorIgnores * true if the filter should skip working tree files that are - * declared as ignored by the standard exclude mechanisms.. + * declared as ignored by the standard exclude mechanisms. */ public IndexDiffFilter(int dirCacheIndex, int workingTreeIndex, boolean honorIgnores) { @@ -128,6 +139,7 @@ this.honorIgnores = honorIgnores; } + /** {@inheritDoc} */ @Override public boolean include(TreeWalk tw) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -255,6 +267,7 @@ return tw.getTree(workingTree, WorkingTreeIterator.class); } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { // We cannot compare subtrees in the working tree, so encourage @@ -262,11 +275,13 @@ return true; } + /** {@inheritDoc} */ @Override public TreeFilter clone() { return this; } + /** {@inheritDoc} */ @Override public String toString() { return "INDEX_DIFF_FILTER"; //$NON-NLS-1$ @@ -286,13 +301,15 @@ } /** + *

    Getter for the field untrackedFolders.

    + * * @return all paths of folders which contain only untracked files/folders. * If on the associated treewalk postorder traversal was turned on - * (see {@link TreeWalk#setPostOrderTraversal(boolean)}) then an + * (see {@link org.eclipse.jgit.treewalk.TreeWalk#setPostOrderTraversal(boolean)}) then an * empty list will be returned. */ public List getUntrackedFolders() { - LinkedList ret = new LinkedList(untrackedFolders); + LinkedList ret = new LinkedList<>(untrackedFolders); if (!untrackedParentFolders.isEmpty()) { String toBeAdded = untrackedParentFolders.getLast(); while (!ret.isEmpty() && ret.getLast().startsWith(toBeAdded)) diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,9 @@ /** * A filter for extracting changes between two versions of the dircache. In - * addition to what {@link TreeFilter#ANY_DIFF} would do, it also detects - * changes that will affect decorations and show up in an attempt to commit. + * addition to what {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} + * would do, it also detects changes that will affect decorations and show up in + * an attempt to commit. */ public final class InterIndexDiffFilter extends TreeFilter { private static final int baseTree = 0; @@ -60,6 +61,7 @@ */ public static final TreeFilter INSTANCE = new InterIndexDiffFilter(); + /** {@inheritDoc} */ @Override public boolean include(final TreeWalk walker) { final int n = walker.getTreeCount(); @@ -88,18 +90,21 @@ return false; } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return false; } + /** {@inheritDoc} */ @Override public TreeFilter clone() { return this; } + /** {@inheritDoc} */ @Override public String toString() { return "INTERINDEX_DIFF"; //$NON-NLS-1$ } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,8 @@ import org.eclipse.jgit.treewalk.WorkingTreeIterator; /** - * Skip {@link WorkingTreeIterator} entries that appear in gitignore files. + * Skip {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} entries that + * appear in gitignore files. */ public class NotIgnoredFilter extends TreeFilter { private final int index; @@ -65,6 +66,7 @@ this.index = workdirTreeIndex; } + /** {@inheritDoc} */ @Override public boolean include(TreeWalk tw) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -72,17 +74,20 @@ return i == null || !i.isEntryIgnored(); } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return false; } + /** {@inheritDoc} */ @Override public TreeFilter clone() { // immutable return this; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.treewalk.TreeWalk; -/** Includes an entry only if the subfilter does not include the entry. */ +/** + * Includes an entry only if the subfilter does not include the entry. + */ public class NotTreeFilter extends TreeFilter { /** * Create a filter that negates the result of another filter. @@ -69,29 +71,51 @@ a = one; } + /** {@inheritDoc} */ @Override public TreeFilter negate() { return a; } + /** {@inheritDoc} */ @Override public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { - return !a.include(walker); + return matchFilter(walker) == 0; } + /** {@inheritDoc} */ + @Override + public int matchFilter(TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + final int r = a.matchFilter(walker); + // switch 0 and 1, keep -1 as that defines a subpath that must be + // traversed before a final verdict can be made. + if (r == 0) { + return 1; + } + if (r == 1) { + return 0; + } + return -1; + } + + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return a.shouldBeRecursive(); } + /** {@inheritDoc} */ @Override public TreeFilter clone() { final TreeFilter n = a.clone(); return n == a ? this : new NotTreeFilter(n); } + /** {@inheritDoc} */ @Override public String toString() { return "NOT " + a.toString(); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,9 +56,10 @@ * Includes a tree entry if any subfilters include the same tree entry. *

    * Classic shortcut behavior is used, so evaluation of the - * {@link TreeFilter#include(TreeWalk)} method stops as soon as a true result is - * obtained. Applications can improve filtering performance by placing faster - * filters that are more likely to accept a result earlier in the list. + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)} method + * stops as soon as a true result is obtained. Applications can improve + * filtering performance by placing faster filters that are more likely to + * accept a result earlier in the list. */ public abstract class OrTreeFilter extends TreeFilter { /** @@ -126,7 +127,25 @@ public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { - return a.include(walker) || b.include(walker); + return matchFilter(walker) <= 0; + } + + @Override + public int matchFilter(TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + final int ra = a.matchFilter(walker); + if (ra == 0) { + return 0; + } + final int rb = b.matchFilter(walker); + if (rb == 0) { + return 0; + } + if (ra == -1 || rb == -1) { + return -1; + } + return 1; } @Override @@ -157,11 +176,24 @@ public boolean include(final TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { + return matchFilter(walker) <= 0; + } + + @Override + public int matchFilter(TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + int m = 1; for (final TreeFilter f : subfilters) { - if (f.include(walker)) - return true; + int r = f.matchFilter(walker); + if (r == 0) { + return 0; + } + if (r == -1) { + m = -1; + } } - return false; + return m; } @Override diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,10 +55,11 @@ /** * Includes tree entries only if they match one or more configured paths. *

    - * Operates like {@link PathFilter} but causes the walk to abort as soon as the - * tree can no longer match any of the paths within the group. This may bypass - * the boolean logic of a higher level AND or OR group, but does improve - * performance for the common case of examining one or more modified paths. + * Operates like {@link org.eclipse.jgit.treewalk.filter.PathFilter} but causes + * the walk to abort as soon as the tree can no longer match any of the paths + * within the group. This may bypass the boolean logic of a higher level AND or + * OR group, but does improve performance for the common case of examining one + * or more modified paths. *

    * This filter is effectively an OR group around paths, with the early abort * feature described above. @@ -173,6 +174,7 @@ return this; } + @Override public String toString() { return "FAST_" + path.toString(); //$NON-NLS-1$ } @@ -245,9 +247,9 @@ int hash = hasher.nextHash(); if (fullpaths.contains(rp, hasher.length(), hash)) return true; - if (!hasher.hasNext()) - if (prefixes.contains(rp, hasher.length(), hash)) - return true; + if (!hasher.hasNext() && walker.isSubtree() + && prefixes.contains(rp, hasher.length(), hash)) + return true; } final int cmp = walker.isPathPrefix(max, max.length); @@ -267,6 +269,7 @@ return this; } + @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append("FAST("); //$NON-NLS-1$ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,9 +51,10 @@ /** * Includes tree entries only if they match the configured path. *

    - * Applications should use {@link PathFilterGroup} to connect these into a tree - * filter graph, as the group supports breaking out of traversal once it is - * known the path can never match. + * Applications should use + * {@link org.eclipse.jgit.treewalk.filter.PathFilterGroup} to connect these + * into a tree filter graph, as the group supports breaking out of traversal + * once it is known the path can never match. */ public class PathFilter extends TreeFilter { /** @@ -70,7 +71,7 @@ * trailing '/' characters will be trimmed before string's length * is checked or is used as part of the constructed filter. * @return a new filter for the requested path. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the path supplied was the empty string. */ public static PathFilter create(String path) { @@ -90,16 +91,28 @@ pathRaw = Constants.encode(pathStr); } - /** @return the path this filter matches. */ + /** + * Get the path this filter matches. + * + * @return the path this filter matches. + */ public String getPath() { return pathStr; } + /** {@inheritDoc} */ @Override public boolean include(final TreeWalk walker) { - return walker.isPathPrefix(pathRaw, pathRaw.length) == 0; + return matchFilter(walker) <= 0; + } + + /** {@inheritDoc} */ + @Override + public int matchFilter(final TreeWalk walker) { + return walker.isPathMatch(pathRaw, pathRaw.length); } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { for (final byte b : pathRaw) @@ -108,17 +121,23 @@ return false; } + /** {@inheritDoc} */ @Override public PathFilter clone() { return this; } + /** {@inheritDoc} */ + @Override @SuppressWarnings("nls") public String toString() { return "PATH(\"" + pathStr + "\")"; } /** + * Whether the path length of this filter matches the length of the current + * path of the supplied TreeWalk. + * * @param walker * The walk to check against. * @return {@code true} if the path length of this filter matches the length diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,8 +59,8 @@ * paths ending in .txt. *

    * Using this filter is recommended instead of filtering the entries using - * {@link TreeWalk#getPathString()} and endsWith or some other type - * of string match function. + * {@link org.eclipse.jgit.treewalk.TreeWalk#getPathString()} and + * endsWith or some other type of string match function. */ public class PathSuffixFilter extends TreeFilter { @@ -72,7 +72,7 @@ * @param path * the path suffix to filter on. Must not be the empty string. * @return a new filter for the requested path. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the path supplied was the empty string. */ public static PathSuffixFilter create(String path) { @@ -89,11 +89,13 @@ pathRaw = Constants.encode(pathStr); } + /** {@inheritDoc} */ @Override public TreeFilter clone() { return this; } + /** {@inheritDoc} */ @Override public boolean include(TreeWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -104,6 +106,7 @@ } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return true; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,6 +69,7 @@ this.treeIdx = treeIdx; } + /** {@inheritDoc} */ @Override public boolean include(TreeWalk walker) { DirCacheIterator i = walker.getTree(treeIdx, DirCacheIterator.class); @@ -79,16 +80,19 @@ return e == null || !e.isSkipWorkTree(); } + /** {@inheritDoc} */ @Override public boolean shouldBeRecursive() { return false; } + /** {@inheritDoc} */ @Override public TreeFilter clone() { return this; } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,7 +67,8 @@ *

    * Path filters: *

      - *
    • Matching pathname: {@link PathFilter}
    • + *
    • Matching pathname: + * {@link org.eclipse.jgit.treewalk.filter.PathFilter}
    • *
    * *

    @@ -79,9 +80,9 @@ *

    * Boolean modifiers: *

      - *
    • AND: {@link AndTreeFilter}
    • - *
    • OR: {@link OrTreeFilter}
    • - *
    • NOT: {@link NotTreeFilter}
    • + *
    • AND: {@link org.eclipse.jgit.treewalk.filter.AndTreeFilter}
    • + *
    • OR: {@link org.eclipse.jgit.treewalk.filter.OrTreeFilter}
    • + *
    • NOT: {@link org.eclipse.jgit.treewalk.filter.NotTreeFilter}
    • *
    */ public abstract class TreeFilter { @@ -173,24 +174,25 @@ * Determine if the current entry is interesting to report. *

    * This method is consulted for subtree entries even if - * {@link TreeWalk#isRecursive()} is enabled. The consultation allows the - * filter to bypass subtree recursion on a case-by-case basis, even when - * recursion is enabled at the application level. + * {@link org.eclipse.jgit.treewalk.TreeWalk#isRecursive()} is enabled. The + * consultation allows the filter to bypass subtree recursion on a + * case-by-case basis, even when recursion is enabled at the application + * level. * * @param walker * the walker the filter needs to examine. * @return true if the current entry should be seen by the application; * false to hide the entry. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * an object the filter needs to consult to determine its answer * does not exist in the Git repository the walker is operating * on. Filtering this current walker entry is impossible without * the object. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object the filter needed to consult was not of the * expected object type. This usually indicates a corrupt * repository, as an object link is referencing the wrong type. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read to obtain data * necessary for the filter to make its decision. */ @@ -199,6 +201,34 @@ IOException; /** + * Determine if the current entry is a parent, a match, or no match. + *

    + * This method extends the result returned by {@link #include(TreeWalk)} + * with a third option (-1), splitting the value true. This gives the + * application a possibility to distinguish between an exact match and the + * case when a subtree to the current entry might be a match. + * + * @param walker + * the walker the filter needs to examine. + * @return -1 if the current entry is a parent of the filter but no exact + * match has been made; 0 if the current entry should be seen by the + * application; 1 if it should be hidden. + * @throws org.eclipse.jgit.errors.MissingObjectException + * as thrown by {@link #include(TreeWalk)} + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * as thrown by {@link #include(TreeWalk)} + * @throws java.io.IOException + * as thrown by {@link #include(TreeWalk)} + * @since 4.7 + */ + public int matchFilter(final TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException + { + return include(walker) ? 0 : 1; + } + + /** * Does this tree filter require a recursive walk to match everything? *

    * If this tree filter is matching on full entry path names and its pattern @@ -213,15 +243,17 @@ public abstract boolean shouldBeRecursive(); /** + * {@inheritDoc} + * * Clone this tree filter, including its parameters. *

    * This is a deep clone. If this filter embeds objects or other filters it * must also clone those, to ensure the instances do not share mutable data. - * - * @return another copy of this filter, suitable for another thread. */ + @Override public abstract TreeFilter clone(); + /** {@inheritDoc} */ @Override public String toString() { String n = getClass().getName(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,8 +53,9 @@ import org.eclipse.jgit.treewalk.TreeWalk; /** - * For testing an array of {@link TreeFilter} during a {@link TreeWalk} for each - * entry and returning the result as a bitmask. + * For testing an array of {@link org.eclipse.jgit.treewalk.filter.TreeFilter} + * during a {@link org.eclipse.jgit.treewalk.TreeWalk} for each entry and + * returning the result as a bitmask. * * @since 2.3 */ @@ -68,8 +69,8 @@ * * @param markTreeFilters * the filters to use for marking, must not have more elements - * than {@link Integer#SIZE}. - * @throws IllegalArgumentException + * than {@link java.lang.Integer#SIZE}. + * @throws java.lang.IllegalArgumentException * if more tree filters are passed than possible */ public TreeFilterMarker(TreeFilter[] markTreeFilters) { @@ -85,19 +86,23 @@ /** * Test the filters against the walk. Returns a bitmask where each bit - * represents the result of a call to {@link TreeFilter#include(TreeWalk)}, + * represents the result of a call to + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)}, * ordered by the index for which the tree filters were passed in the * constructor. * * @param walk * the walk from which to test the current entry * @return the marks bitmask - * @throws MissingObjectException - * as thrown by {@link TreeFilter#include(TreeWalk)} - * @throws IncorrectObjectTypeException - * as thrown by {@link TreeFilter#include(TreeWalk)} - * @throws IOException - * as thrown by {@link TreeFilter#include(TreeWalk)} + * @throws org.eclipse.jgit.errors.MissingObjectException + * as thrown by + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)} + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * as thrown by + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)} + * @throws java.io.IOException + * as thrown by + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#include(TreeWalk)} */ public int getMarks(TreeWalk walk) throws MissingObjectException, IncorrectObjectTypeException, IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.treewalk; -import org.eclipse.jgit.dircache.DirCacheBuilder; +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectReader; @@ -52,24 +54,26 @@ /** * Specialized TreeWalk to detect directory-file (D/F) name conflicts. *

    - * Due to the way a Git tree is organized the standard {@link TreeWalk} won't - * easily find a D/F conflict when merging two or more trees together. In the - * standard TreeWalk the file will be returned first, and then much later the - * directory will be returned. This makes it impossible for the application to - * efficiently detect and handle the conflict. + * Due to the way a Git tree is organized the standard + * {@link org.eclipse.jgit.treewalk.TreeWalk} won't easily find a D/F conflict + * when merging two or more trees together. In the standard TreeWalk the file + * will be returned first, and then much later the directory will be returned. + * This makes it impossible for the application to efficiently detect and handle + * the conflict. *

    * Using this walk implementation causes the directory to report earlier than * usual, at the same time as the non-directory entry. This permits the * application to handle the D/F conflict in a single step. The directory is * returned only once, so it does not get returned later in the iteration. *

    - * When a D/F conflict is detected {@link TreeWalk#isSubtree()} will return true - * and {@link TreeWalk#enterSubtree()} will recurse into the subtree, no matter - * which iterator originally supplied the subtree. + * When a D/F conflict is detected + * {@link org.eclipse.jgit.treewalk.TreeWalk#isSubtree()} will return true and + * {@link org.eclipse.jgit.treewalk.TreeWalk#enterSubtree()} will recurse into + * the subtree, no matter which iterator originally supplied the subtree. *

    * Because conflicted directories report early, using this walk implementation - * to populate a {@link DirCacheBuilder} may cause the automatic resorting to - * run and fix the entry ordering. + * to populate a {@link org.eclipse.jgit.dircache.DirCacheBuilder} may cause the + * automatic resorting to run and fix the entry ordering. *

    * This walk implementation requires more CPU to implement a look-ahead and a * look-behind to merge a D/F pair together, or to skip a previously reported @@ -96,7 +100,20 @@ * the repository the walker will obtain data from. */ public NameConflictTreeWalk(final Repository repo) { - this(repo.newObjectReader()); + super(repo); + } + + /** + * Create a new tree walker for a given repository. + * + * @param repo + * the repository the walker will obtain data from. + * @param or + * the reader the walker will obtain tree data from. + * @since 4.3 + */ + public NameConflictTreeWalk(@Nullable Repository repo, final ObjectReader or) { + super(repo, or); } /** @@ -170,7 +187,7 @@ // t.matches = minRef; } else if (fastMinHasMatch && isTree(t) && !isTree(minRef) - && nameEqual(t, minRef)) { + && !isGitlink(minRef) && nameEqual(t, minRef)) { // The minimum is a file (non-tree) but the next entry // of this iterator is a tree whose name matches our file. // This is a classic D/F conflict and commonly occurs like @@ -201,6 +218,10 @@ return a.pathCompare(b, TREE_MODE) == 0; } + private boolean isGitlink(AbstractTreeIterator p) { + return FileMode.GITLINK.equals(p.mode); + } + private static boolean isTree(final AbstractTreeIterator p) { return FileMode.TREE.equals(p.mode); } @@ -289,8 +310,9 @@ if (t.matches == minRef) t.matches = treeMatch; - if (dfConflict == null) + if (dfConflict == null && !isGitlink(minRef)) { dfConflict = treeMatch; + } return treeMatch; } @@ -338,6 +360,42 @@ dfConflict = null; } + @Override + void stopWalk() throws IOException { + if (!needsStopWalk()) { + return; + } + + // Name conflicts make aborting early difficult. Multiple paths may + // exist between the file and directory versions of a name. To ensure + // the directory version is skipped over (as it was previously visited + // during the file version step) requires popping up the stack and + // finishing out each subtree that the walker dove into. Siblings in + // parents do not need to be recursed into, bounding the cost. + for (;;) { + AbstractTreeIterator t = min(); + if (t.eof()) { + if (depth > 0) { + exitSubtree(); + popEntriesEqual(); + continue; + } + return; + } + currentHead = t; + skipEntriesEqual(); + } + } + + private boolean needsStopWalk() { + for (AbstractTreeIterator t : trees) { + if (t.needsStopWalk()) { + return true; + } + } + return false; + } + /** * True if the current entry is covered by a directory/file conflict. * diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,13 +45,29 @@ package org.eclipse.jgit.treewalk; import java.io.IOException; - +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.attributes.AttributesHandler; +import org.eclipse.jgit.attributes.AttributesNodeProvider; +import org.eclipse.jgit.attributes.AttributesProvider; +import org.eclipse.jgit.attributes.FilterCommandRegistry; +import org.eclipse.jgit.dircache.DirCacheBuildIterator; +import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -60,10 +76,13 @@ import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; /** - * Walks one or more {@link AbstractTreeIterator}s in parallel. + * Walks one or more {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}s in + * parallel. *

    * This class can perform n-way differences across as many trees as necessary. *

    @@ -79,13 +98,53 @@ * usage of a TreeWalk instance to a single thread, or implement their own * synchronization at a higher level. *

    - * Multiple simultaneous TreeWalk instances per {@link Repository} are - * permitted, even from concurrent threads. + * Multiple simultaneous TreeWalk instances per + * {@link org.eclipse.jgit.lib.Repository} are permitted, even from concurrent + * threads. */ -public class TreeWalk implements AutoCloseable { +public class TreeWalk implements AutoCloseable, AttributesProvider { private static final AbstractTreeIterator[] NO_TREES = {}; /** + * @since 4.2 + */ + public static enum OperationType { + /** + * Represents a checkout operation (for example a checkout or reset + * operation). + */ + CHECKOUT_OP, + + /** + * Represents a checkin operation (for example an add operation) + */ + CHECKIN_OP + } + + /** + * Type of operation you want to retrieve the git attributes for. + */ + private OperationType operationType = OperationType.CHECKOUT_OP; + + /** + * The filter command as defined in gitattributes. The keys are + * filterName+"."+filterCommandType. E.g. "lfs.clean" + */ + private Map filterCommandsByNameDotType = new HashMap<>(); + + /** + * Set the operation type of this walk + * + * @param operationType + * a {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} + * object. + * @since 4.2 + */ + public void setOperationType(OperationType operationType) { + this.operationType = operationType; + } + + /** * Open a tree walk and filter to exactly one path. *

    * The returned tree walk is already positioned on the requested path, so @@ -100,20 +159,58 @@ * one or more trees to walk through, all with the same root. * @return a new tree walk configured for exactly this one path; null if no * path was found in any of the trees. - * @throws IOException + * @throws java.io.IOException * reading a pack file or loose object failed. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * an tree object could not be read as its data stream did not * appear to be a tree, or could not be inflated. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object we expected to be a tree was not a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a tree object was not found. */ public static TreeWalk forPath(final ObjectReader reader, final String path, final AnyObjectId... trees) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { - TreeWalk tw = new TreeWalk(reader); + return forPath(null, reader, path, trees); + } + + /** + * Open a tree walk and filter to exactly one path. + *

    + * The returned tree walk is already positioned on the requested path, so + * the caller should not need to invoke {@link #next()} unless they are + * looking for a possible directory/file name conflict. + * + * @param repo + * repository to read config data and + * {@link org.eclipse.jgit.attributes.AttributesNodeProvider} + * from. + * @param reader + * the reader the walker will obtain tree data from. + * @param path + * single path to advance the tree walk instance into. + * @param trees + * one or more trees to walk through, all with the same root. + * @return a new tree walk configured for exactly this one path; null if no + * path was found in any of the trees. + * @throws java.io.IOException + * reading a pack file or loose object failed. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * an tree object could not be read as its data stream did not + * appear to be a tree, or could not be inflated. + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException + * an object we expected to be a tree was not a tree. + * @throws org.eclipse.jgit.errors.MissingObjectException + * a tree object was not found. + * @since 4.3 + */ + public static TreeWalk forPath(final @Nullable Repository repo, + final ObjectReader reader, final String path, + final AnyObjectId... trees) + throws MissingObjectException, IncorrectObjectTypeException, + CorruptObjectException, IOException { + TreeWalk tw = new TreeWalk(repo, reader); PathFilter f = PathFilter.create(path); tw.setFilter(f); tw.reset(trees); @@ -144,21 +241,21 @@ * one or more trees to walk through, all with the same root. * @return a new tree walk configured for exactly this one path; null if no * path was found in any of the trees. - * @throws IOException + * @throws java.io.IOException * reading a pack file or loose object failed. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * an tree object could not be read as its data stream did not * appear to be a tree, or could not be inflated. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object we expected to be a tree was not a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a tree object was not found. */ public static TreeWalk forPath(final Repository db, final String path, final AnyObjectId... trees) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { try (ObjectReader reader = db.newObjectReader()) { - return forPath(reader, path, trees); + return forPath(db, reader, path, trees); } } @@ -177,14 +274,14 @@ * the single tree to walk through. * @return a new tree walk configured for exactly this one path; null if no * path was found in any of the trees. - * @throws IOException + * @throws java.io.IOException * reading a pack file or loose object failed. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * an tree object could not be read as its data stream did not * appear to be a tree, or could not be inflated. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * an object we expected to be a tree was not a tree. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a tree object was not found. */ public static TreeWalk forPath(final Repository db, final String path, @@ -207,14 +304,26 @@ private boolean postOrderTraversal; - private int depth; + int depth; private boolean advance; private boolean postChildren; + private AttributesNodeProvider attributesNodeProvider; + AbstractTreeIterator currentHead; + /** Cached attribute for the current entry */ + private Attributes attrs = null; + + /** Cached attributes handler */ + private AttributesHandler attributesHandler; + + private Config config; + + private Set filterCommands; + /** * Create a new tree walker for a given repository. * @@ -224,7 +333,23 @@ * when the walker is closed. */ public TreeWalk(final Repository repo) { - this(repo.newObjectReader(), true); + this(repo, repo.newObjectReader(), true); + } + + /** + * Create a new tree walker for a given repository. + * + * @param repo + * the repository the walker will obtain data from. An + * ObjectReader will be created by the walker, and will be closed + * when the walker is closed. + * @param or + * the reader the walker will obtain tree data from. The reader + * is not closed when the walker is closed. + * @since 4.3 + */ + public TreeWalk(final @Nullable Repository repo, final ObjectReader or) { + this(repo, or, false); } /** @@ -235,22 +360,48 @@ * is not closed when the walker is closed. */ public TreeWalk(final ObjectReader or) { - this(or, false); + this(null, or, false); } - private TreeWalk(final ObjectReader or, final boolean closeReader) { + private TreeWalk(final @Nullable Repository repo, final ObjectReader or, + final boolean closeReader) { + if (repo != null) { + config = repo.getConfig(); + attributesNodeProvider = repo.createAttributesNodeProvider(); + filterCommands = FilterCommandRegistry + .getRegisteredFilterCommands(); + } else { + config = null; + attributesNodeProvider = null; + } reader = or; filter = TreeFilter.ALL; trees = NO_TREES; this.closeReader = closeReader; } - /** @return the reader this walker is using to load objects. */ + /** + * Get the reader this walker is using to load objects. + * + * @return the reader this walker is using to load objects. + */ public ObjectReader getObjectReader() { return reader; } /** + * Get the operation type + * + * @return the {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} + * @since 4.3 + */ + public OperationType getOperationType() { + return operationType; + } + + /** + * {@inheritDoc} + *

    * Release any resources used by this walker's reader. *

    * A walker that has been released can be used again, but may need to be @@ -285,12 +436,14 @@ * Note that filters are not thread-safe and may not be shared by concurrent * TreeWalk instances. Every TreeWalk must be supplied its own unique * filter, unless the filter implementation specifically states it is (and - * always will be) thread-safe. Callers may use {@link TreeFilter#clone()} - * to create a unique filter tree for this TreeWalk instance. + * always will be) thread-safe. Callers may use + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#clone()} to create a + * unique filter tree for this TreeWalk instance. * * @param newFilter - * the new filter. If null the special {@link TreeFilter#ALL} - * filter will be used instead, as it matches every entry. + * the new filter. If null the special + * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} filter + * will be used instead, as it matches every entry. * @see org.eclipse.jgit.treewalk.filter.AndTreeFilter * @see org.eclipse.jgit.treewalk.filter.OrTreeFilter */ @@ -356,8 +509,151 @@ postOrderTraversal = b; } - /** Reset this walker so new tree iterators can be added to it. */ + /** + * Sets the {@link org.eclipse.jgit.attributes.AttributesNodeProvider} for + * this {@link org.eclipse.jgit.treewalk.TreeWalk}. + *

    + * This is a requirement for a correct computation of the git attributes. If + * this {@link org.eclipse.jgit.treewalk.TreeWalk} has been built using + * {@link #TreeWalk(Repository)} constructor, the + * {@link org.eclipse.jgit.attributes.AttributesNodeProvider} has already + * been set. Indeed,the {@link org.eclipse.jgit.lib.Repository} can provide + * an {@link org.eclipse.jgit.attributes.AttributesNodeProvider} using + * {@link org.eclipse.jgit.lib.Repository#createAttributesNodeProvider()} + * method. Otherwise you should provide one. + *

    + * + * @see Repository#createAttributesNodeProvider() + * @param provider + * a {@link org.eclipse.jgit.attributes.AttributesNodeProvider} + * object. + * @since 4.2 + */ + public void setAttributesNodeProvider(AttributesNodeProvider provider) { + attributesNodeProvider = provider; + } + + /** + * Get the attributes node provider + * + * @return the {@link org.eclipse.jgit.attributes.AttributesNodeProvider} + * for this {@link org.eclipse.jgit.treewalk.TreeWalk}. + * @since 4.3 + */ + public AttributesNodeProvider getAttributesNodeProvider() { + return attributesNodeProvider; + } + + /** + * {@inheritDoc} + *

    + * Retrieve the git attributes for the current entry. + * + *

    Git attribute computation

    + * + *
      + *
    • Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).
    • + *
    • Completes the list of attributes using the .gitattributes files + * located on the current path (the further the directory that contains + * .gitattributes is from the path in question, the lower its precedence). + * For a checkin operation, it will look first on the working tree (if any). + * If there is no attributes file, it will fallback on the index. For a + * checkout operation, it will first use the index entry and then fallback + * on the working tree if none.
    • + *
    • In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})
    • + * + *
    + * + * + *

    Iterator constraints

    + * + *

    + * In order to have a correct list of attributes for the current entry, this + * {@link TreeWalk} requires to have at least one + * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An + * {@link AttributesNodeProvider} is used to retrieve the attributes from + * the info attributes file and the global attributes file. The + * {@link DirCacheIterator} is used to retrieve the .gitattributes files + * stored in the index. A {@link WorkingTreeIterator} can also be provided + * to access the local version of the .gitattributes files. If none is + * provided it will fallback on the {@link DirCacheIterator}. + *

    + * + * @since 4.2 + */ + @Override + public Attributes getAttributes() { + if (attrs != null) + return attrs; + + if (attributesNodeProvider == null) { + // The work tree should have a AttributesNodeProvider to be able to + // retrieve the info and global attributes node + throw new IllegalStateException( + "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ + } + + try { + // Lazy create the attributesHandler on the first access of + // attributes. This requires the info, global and root + // attributes nodes + if (attributesHandler == null) { + attributesHandler = new AttributesHandler(this); + } + attrs = attributesHandler.getAttributes(); + return attrs; + } catch (IOException e) { + throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$ + e); + } + } + + /** + * Get the EOL stream type of the current entry using the config and + * {@link #getAttributes()}. + * + * @param opType + * the operationtype (checkin/checkout) which should be used + * @return the EOL stream type of the current entry using the config and + * {@link #getAttributes()}. Note that this method may return null + * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on + * a working tree + * @since 4.10 + */ + @Nullable + public EolStreamType getEolStreamType(OperationType opType) { + if (attributesNodeProvider == null || config == null) + return null; + return EolStreamTypeUtil.detectStreamType( + opType != null ? opType : operationType, + config.get(WorkingTreeOptions.KEY), getAttributes()); + } + + /** + * Get the EOL stream type of the current entry using the config and + * {@link #getAttributes()}. + * + * @return the EOL stream type of the current entry using the config and + * {@link #getAttributes()}. Note that this method may return null + * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on + * a working tree + * @since 4.3 + * @deprecated use {@link #getEolStreamType(OperationType)} instead. + */ + @Deprecated + public @Nullable EolStreamType getEolStreamType() { + return (getEolStreamType(operationType)); + } + + /** + * Reset this walker so new tree iterators can be added to it. + */ public void reset() { + attrs = null; + attributesHandler = null; trees = NO_TREES; advance = false; depth = 0; @@ -369,16 +665,16 @@ * @param id * the tree we need to parse. The walker will execute over this * single tree if the reset is successful. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the given tree object does not exist in this repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the given object id does not denote a tree, but instead names * some other non-tree type of object. Note that commits are not * trees, even if they are sometimes called a "tree-ish". - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the object claimed to be a tree, but its contents did not * appear to be a tree. The repository may have data corruption. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public void reset(final AnyObjectId id) throws MissingObjectException, @@ -401,6 +697,7 @@ advance = false; depth = 0; + attrs = null; } /** @@ -409,16 +706,16 @@ * @param ids * the trees we need to parse. The walker will execute over this * many parallel trees if the reset is successful. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the given tree object does not exist in this repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the given object id does not denote a tree, but instead names * some other non-tree type of object. Note that commits are not * trees, even if they are sometimes called a "tree-ish". - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the object claimed to be a tree, but its contents did not * appear to be a tree. The repository may have data corruption. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public void reset(final AnyObjectId... ids) throws MissingObjectException, @@ -450,6 +747,7 @@ trees = r; advance = false; depth = 0; + attrs = null; } /** @@ -463,16 +761,16 @@ * @param id * identity of the tree object the caller wants walked. * @return position of this tree within the walker. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * the given tree object does not exist in this repository. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the given object id does not denote a tree, but instead names * some other non-tree type of object. Note that commits are not * trees, even if they are sometimes called a "tree-ish". - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the object claimed to be a tree, but its contents did not * appear to be a tree. The repository may have data corruption. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public int addTree(final AnyObjectId id) throws MissingObjectException, @@ -492,18 +790,13 @@ * @param p * an iterator to walk over. The iterator should be new, with no * parent, and should still be positioned before the first entry. - * The tree which the iterator operates on must have the same root - * as other trees in the walk. - * + * The tree which the iterator operates on must have the same + * root as other trees in the walk. * @return position of this tree within the walker. - * @throws CorruptObjectException - * the iterator was unable to obtain its first entry, due to - * possible data corruption within the backing data store. - */ - public int addTree(final AbstractTreeIterator p) - throws CorruptObjectException { - final int n = trees.length; - final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; + */ + public int addTree(AbstractTreeIterator p) { + int n = trees.length; + AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; System.arraycopy(trees, 0, newTrees, 0, n); newTrees[n] = p; @@ -528,19 +821,19 @@ * * @return true if there is an entry available; false if all entries have * been walked and the walk of this set of tree iterators is over. - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * {@link #isRecursive()} was enabled, a subtree was found, but * the subtree object does not exist in this repository. The * repository may be missing objects. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * {@link #isRecursive()} was enabled, a subtree was found, and * the subtree id does not denote a tree, but instead names some * other non-tree type of object. The repository may have data * corruption. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the contents of a tree did not appear to be a tree. The * repository may have data corruption. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public boolean next() throws MissingObjectException, @@ -553,6 +846,7 @@ } for (;;) { + attrs = null; final AbstractTreeIterator t = min(); if (t.eof()) { if (depth > 0) { @@ -569,7 +863,7 @@ } currentHead = t; - if (!filter.include(this)) { + if (filter.matchFilter(this) == 1) { skipEntriesEqual(); continue; } @@ -583,13 +877,30 @@ return true; } } catch (StopWalkException stop) { - for (final AbstractTreeIterator t : trees) - t.stopWalk(); + stopWalk(); return false; } } /** + * Notify iterators the walk is aborting. + *

    + * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so + * that it can copy any remaining entries. + * + * @throws IOException + * if traversal of remaining entries throws an exception during + * object access. This should never occur as remaining trees + * should already be in memory, however the methods used to + * finish traversal are declared to throw IOException. + */ + void stopWalk() throws IOException { + for (AbstractTreeIterator t : trees) { + t.stopWalk(); + } + } + + /** * Obtain the tree iterator for the current entry. *

    * Entering into (or exiting out of) a subtree causes the current tree @@ -597,8 +908,6 @@ * iterators to manage only one list of items, with the diving handled by * recursive trees. * - * @param - * type of the tree iterator expected by the caller. * @param nth * tree to obtain the current iterator of. * @param clazz @@ -614,11 +923,13 @@ } /** - * Obtain the raw {@link FileMode} bits for the current entry. + * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for the current + * entry. *

    * Every added tree supplies mode bits, even if the tree does not contain - * the current entry. In the latter case {@link FileMode#MISSING}'s mode - * bits (0) are returned. + * the current entry. In the latter case + * {@link org.eclipse.jgit.lib.FileMode#MISSING}'s mode bits (0) are + * returned. * * @param nth * tree to obtain the mode bits from. @@ -631,10 +942,11 @@ } /** - * Obtain the {@link FileMode} for the current entry. + * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry. *

    * Every added tree supplies a mode, even if the tree does not contain the - * current entry. In the latter case {@link FileMode#MISSING} is returned. + * current entry. In the latter case + * {@link org.eclipse.jgit.lib.FileMode#MISSING} is returned. * * @param nth * tree to obtain the mode from. @@ -645,6 +957,17 @@ } /** + * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry on + * the currentHead tree + * + * @return mode for the current entry of the currentHead tree. + * @since 4.3 + */ + public FileMode getFileMode() { + return FileMode.fromBits(currentHead.mode); + } + + /** * Obtain the ObjectId for the current entry. *

    * Using this method to compare ObjectId values between trees of this walker @@ -653,13 +976,16 @@ * whenever possible. *

    * Every tree supplies an object id, even if the tree does not contain the - * current entry. In the latter case {@link ObjectId#zeroId()} is returned. + * current entry. In the latter case + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is returned. * * @param nth * tree to obtain the object identifier from. * @return object identifier for the current tree entry. * @see #getObjectId(MutableObjectId, int) * @see #idEqual(int, int) + * @see #getObjectId(MutableObjectId, int) + * @see #idEqual(int, int) */ public ObjectId getObjectId(final int nth) { final AbstractTreeIterator t = trees[nth]; @@ -671,7 +997,8 @@ * Obtain the ObjectId for the current entry. *

    * Every tree supplies an object id, even if the tree does not contain the - * current entry. In the latter case {@link ObjectId#zeroId()} is supplied. + * current entry. In the latter case + * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is supplied. *

    * Applications should try to use {@link #idEqual(int, int)} when possible * as it avoids conversion overheads. @@ -769,6 +1096,8 @@ } /** + * Get the path length of the current entry. + * * @return The path length of the current entry. */ public int getPathLength() { @@ -778,11 +1107,68 @@ /** * Test if the supplied path matches the current entry's path. *

    + * This method detects if the supplied path is equal to, a subtree of, or + * not similar at all to the current entry. It is faster to use this + * method than to use {@link #getPathString()} to first create a String + * object, then test startsWith or some other type of string + * match function. + *

    + * If the current entry is a subtree, then all paths within the subtree + * are considered to match it. + * + * @param p + * path buffer to test. Callers should ensure the path does not + * end with '/' prior to invocation. + * @param pLen + * number of bytes from buf to test. + * @return -1 if the current path is a parent to p; 0 if p matches the current + * path; 1 if the current path is different and will never match + * again on this tree walk. + * @since 4.7 + */ + public int isPathMatch(final byte[] p, final int pLen) { + final AbstractTreeIterator t = currentHead; + final byte[] c = t.path; + final int cLen = t.pathLen; + int ci; + + for (ci = 0; ci < cLen && ci < pLen; ci++) { + final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff); + if (c_value != 0) { + // Paths do not and will never match + return 1; + } + } + + if (ci < cLen) { + // Ran out of pattern but we still had current data. + // If c[ci] == '/' then pattern matches the subtree. + // Otherwise it is a partial match == miss + return c[ci] == '/' ? 0 : 1; + } + + if (ci < pLen) { + // Ran out of current, but we still have pattern data. + // If p[ci] == '/' then this subtree is a parent in the pattern, + // otherwise it's a miss. + return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? -1 : 1; + } + + // Both strings are identical. + return 0; + } + + /** + * Test if the supplied path matches the current entry's path. + *

    * This method tests that the supplied path is exactly equal to the current - * entry, or is one of its parent directories. It is faster to use this + * entry or is one of its parent directories. It is faster to use this * method then to use {@link #getPathString()} to first create a String * object, then test startsWith or some other type of string * match function. + *

    + * If the current entry is a subtree, then all paths within the subtree + * are considered to match it. * * @param p * path buffer to test. Callers should ensure the path does not @@ -818,7 +1204,7 @@ // If p[ci] == '/' then pattern matches this subtree, // otherwise we cannot be certain so we return -1. // - return p[ci] == '/' ? 0 : -1; + return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1; } // Both strings are identical. @@ -900,27 +1286,33 @@ * If the current entry is a subtree this method arranges for its children * to be returned before the next sibling following the subtree is returned. * - * @throws MissingObjectException + * @throws org.eclipse.jgit.errors.MissingObjectException * a subtree was found, but the subtree object does not exist in * this repository. The repository may be missing objects. - * @throws IncorrectObjectTypeException + * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * a subtree was found, and the subtree id does not denote a * tree, but instead names some other non-tree type of object. * The repository may have data corruption. - * @throws CorruptObjectException + * @throws org.eclipse.jgit.errors.CorruptObjectException * the contents of a tree did not appear to be a tree. The * repository may have data corruption. - * @throws IOException + * @throws java.io.IOException * a loose object or pack file could not be read. */ public void enterSubtree() throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { + attrs = null; final AbstractTreeIterator ch = currentHead; final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; for (int i = 0; i < trees.length; i++) { final AbstractTreeIterator t = trees[i]; final AbstractTreeIterator n; - if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode)) + // If we find a GITLINK when attempting to enter a subtree, then the + // GITLINK must exist as a TREE in the index, meaning the working tree + // entry should be treated as a TREE + if (t.matches == ch && !t.eof() && + (FileMode.TREE.equals(t.mode) + || (FileMode.GITLINK.equals(t.mode) && t.isWorkTree()))) n = t.createSubtreeIterator(reader, idBuffer); else n = t.createEmptyTreeIterator(); @@ -979,7 +1371,7 @@ } } - private void exitSubtree() { + void exitSubtree() { depth--; for (int i = 0; i < trees.length; i++) trees[i] = trees[i].parent; @@ -1008,4 +1400,99 @@ static String pathOf(final byte[] buf, int pos, int end) { return RawParseUtils.decode(Constants.CHARSET, buf, pos, end); } + + /** + * Get the tree of that type. + * + * @param type + * of the tree to be queried + * @return the tree of that type or null if none is present. + * @since 4.3 + * @param + * a tree type. + */ + public T getTree( + Class type) { + for (int i = 0; i < trees.length; i++) { + AbstractTreeIterator tree = trees[i]; + if (type.isInstance(tree)) { + return type.cast(tree); + } + } + return null; + } + + /** + * Inspect config and attributes to return a filtercommand applicable for + * the current path, but without expanding %f occurences + * + * @param filterCommandType + * which type of filterCommand should be executed. E.g. "clean", + * "smudge" + * @return a filter command + * @throws java.io.IOException + * @since 4.2 + */ + public String getFilterCommand(String filterCommandType) + throws IOException { + Attributes attributes = getAttributes(); + + Attribute f = attributes.get(Constants.ATTR_FILTER); + if (f == null) { + return null; + } + String filterValue = f.getValue(); + if (filterValue == null) { + return null; + } + + String filterCommand = getFilterCommandDefinition(filterValue, + filterCommandType); + if (filterCommand == null) { + return null; + } + return filterCommand.replaceAll("%f", //$NON-NLS-1$ + QuotedString.BOURNE.quote((getPathString()))); + } + + /** + * Get the filter command how it is defined in gitconfig. The returned + * string may contain "%f" which needs to be replaced by the current path + * before executing the filter command. These filter definitions are cached + * for better performance. + * + * @param filterDriverName + * The name of the filter driver as it is referenced in the + * gitattributes file. E.g. "lfs". For each filter driver there + * may be many commands defined in the .gitconfig + * @param filterCommandType + * The type of the filter command for a specific filter driver. + * May be "clean" or "smudge". + * @return the definition of the command to be executed for this filter + * driver and filter command + */ + private String getFilterCommandDefinition(String filterDriverName, + String filterCommandType) { + String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$ + String filterCommand = filterCommandsByNameDotType.get(key); + if (filterCommand != null) + return filterCommand; + filterCommand = config.getString(ConfigConstants.CONFIG_FILTER_SECTION, + filterDriverName, filterCommandType); + boolean useBuiltin = config.getBoolean( + ConfigConstants.CONFIG_FILTER_SECTION, + filterDriverName, ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false); + if (useBuiltin) { + String builtinFilterCommand = Constants.BUILTIN_FILTER_PREFIX + + filterDriverName + '/' + filterCommandType; + if (filterCommands != null + && filterCommands.contains(builtinFilterCommand)) { + filterCommand = builtinFilterCommand; + } + } + if (filterCommand != null) { + filterCommandsByNameDotType.put(key, filterCommand); + } + return filterCommand; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,16 +56,19 @@ import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; -import java.security.MessageDigest; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesRule; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.diff.RawText; -import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; @@ -77,6 +80,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.CoreConfig.CheckStat; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; @@ -84,23 +88,35 @@ import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; +import org.eclipse.jgit.util.Holder; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.Paths; import org.eclipse.jgit.util.RawParseUtils; -import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; +import org.eclipse.jgit.util.io.AutoLFInputStream; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; +import org.eclipse.jgit.util.sha1.SHA1; /** - * Walks a working directory tree as part of a {@link TreeWalk}. + * Walks a working directory tree as part of a + * {@link org.eclipse.jgit.treewalk.TreeWalk}. *

    * Most applications will want to use the standard implementation of this - * iterator, {@link FileTreeIterator}, as that does all IO through the standard - * java.io package. Plugins for a Java based IDE may however wish - * to create their own implementations of this class to allow traversal of the - * IDE's project space, as well as benefit from any caching the IDE may have. + * iterator, {@link org.eclipse.jgit.treewalk.FileTreeIterator}, as that does + * all IO through the standard java.io package. Plugins for a Java + * based IDE may however wish to create their own implementations of this class + * to allow traversal of the IDE's project space, as well as benefit from any + * caching the IDE may have. * * @see FileTreeIterator */ public abstract class WorkingTreeIterator extends AbstractTreeIterator { + private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024; + /** An empty entry array, suitable for {@link #init(Entry[])}. */ protected static final Entry[] EOF = {}; @@ -134,8 +150,17 @@ /** If there is a .gitignore file present, the parsed rules from it. */ private IgnoreNode ignoreNode; - /** If there is a .gitattributes file present, the parsed rules from it. */ - private AttributesNode attributesNode; + /** + * cached clean filter command. Use a Ref in order to distinguish between + * the ref not cached yet and the value null + */ + private Holder cleanFilterCommandHolder; + + /** + * cached eol stream type. Use a Ref in order to distinguish between the ref + * not cached yet and the value null + */ + private Holder eolStreamTypeHolder; /** Repository that is the root level being iterated over */ protected Repository repository; @@ -147,19 +172,6 @@ private int contentIdOffset; /** - * Holds the {@link AttributesNode} that is stored in - * $GIT_DIR/info/attributes file. - */ - private AttributesNode infoAttributeNode; - - /** - * Holds the {@link AttributesNode} that is stored in global attribute file. - * - * @see CoreConfig#getAttributesFile() - */ - private AttributesNode globalAttributeNode; - - /** * Create a new iterator with no parent. * * @param options @@ -202,8 +214,7 @@ protected WorkingTreeIterator(final WorkingTreeIterator p) { super(p); state = p.state; - infoAttributeNode = p.infoAttributeNode; - globalAttributeNode = p.globalAttributeNode; + repository = p.repository; } /** @@ -223,14 +234,11 @@ else entry = null; ignoreNode = new RootIgnoreNode(entry, repo); - - infoAttributeNode = new InfoAttributesNode(repo); - - globalAttributeNode = new GlobalAttributesNode(repo); } /** - * Define the matching {@link DirCacheIterator}, to optimize ObjectIds. + * Define the matching {@link org.eclipse.jgit.dircache.DirCacheIterator}, + * to optimize ObjectIds. * * Once the DirCacheIterator has been set this iterator must only be * advanced by the TreeWalk that is supplied, as it assumes that itself and @@ -240,13 +248,15 @@ * @param walk * the walk that will be advancing this iterator. * @param treeId - * index of the matching {@link DirCacheIterator}. + * index of the matching + * {@link org.eclipse.jgit.dircache.DirCacheIterator}. */ public void setDirCacheIterator(TreeWalk walk, int treeId) { state.walk = walk; state.dirCacheTree = treeId; } + /** {@inheritDoc} */ @Override public boolean hasId() { if (contentIdFromPtr == ptr) @@ -254,6 +264,7 @@ return (mode & FileMode.TYPE_MASK) == FileMode.TYPE_FILE; } + /** {@inheritDoc} */ @Override public byte[] idBuffer() { if (contentIdFromPtr == ptr) @@ -263,12 +274,13 @@ // If there is a matching DirCacheIterator, we can reuse // its idBuffer, but only if we appear to be clean against // the cached index information for the path. - // DirCacheIterator i = state.walk.getTree(state.dirCacheTree, - DirCacheIterator.class); + DirCacheIterator.class); if (i != null) { DirCacheEntry ent = i.getDirCacheEntry(); - if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL) { + if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL + && ((ent.getFileMode().getBits() + & FileMode.TYPE_MASK) != FileMode.TYPE_GITLINK)) { contentIdOffset = i.idOffset(); contentIdFromPtr = ptr; return contentId = i.idBuffer(); @@ -290,10 +302,18 @@ return zeroid; } + /** {@inheritDoc} */ + @Override + public boolean isWorkTree() { + return true; + } + /** * Get submodule id for given entry. * * @param e + * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry} + * object. * @return non-null submodule id */ protected byte[] idSubmodule(Entry e) { @@ -313,14 +333,18 @@ * relative to the directory. * * @param directory + * a {@link java.io.File} object. * @param e + * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry} + * object. * @return non-null submodule id */ protected byte[] idSubmodule(File directory, Entry e) { final Repository submoduleRepo; try { submoduleRepo = SubmoduleWalk.getSubmoduleRepository(directory, - e.getName()); + e.getName(), + repository != null ? repository.getFS() : FS.DETECTED); } catch (IOException exception) { return zeroid; } @@ -354,10 +378,11 @@ if (is == null) return zeroid; try { - state.initializeDigestAndReadBuffer(); + state.initializeReadBuffer(); final long len = e.getLength(); - InputStream filteredIs = possiblyFilteredInputStream(e, is, len); + InputStream filteredIs = possiblyFilteredInputStream(e, is, len, + OperationType.CHECKIN_OP); return computeHash(filteredIs, canonLen); } finally { safeClose(is); @@ -370,36 +395,39 @@ private InputStream possiblyFilteredInputStream(final Entry e, final InputStream is, final long len) throws IOException { - if (!mightNeedCleaning()) { + return possiblyFilteredInputStream(e, is, len, null); + + } + + private InputStream possiblyFilteredInputStream(final Entry e, + final InputStream is, final long len, OperationType opType) + throws IOException { + if (getCleanFilterCommand() == null + && getEolStreamType(opType) == EolStreamType.DIRECT) { canonLen = len; return is; } if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) { ByteBuffer rawbuf = IO.readWholeStream(is, (int) len); - byte[] raw = rawbuf.array(); - int n = rawbuf.limit(); - if (!isBinary(raw, n)) { - rawbuf = filterClean(raw, n); - raw = rawbuf.array(); - n = rawbuf.limit(); - } - canonLen = n; - return new ByteArrayInputStream(raw, 0, n); + rawbuf = filterClean(rawbuf.array(), rawbuf.limit(), opType); + canonLen = rawbuf.limit(); + return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen); } - if (isBinary(e)) { - canonLen = len; - return is; - } + if (getCleanFilterCommand() == null && isBinary(e)) { + canonLen = len; + return is; + } - final InputStream lenIs = filterClean(e.openInputStream()); + final InputStream lenIs = filterClean(e.openInputStream(), + opType); try { canonLen = computeLength(lenIs); } finally { safeClose(lenIs); } - return filterClean(is); + return filterClean(is, opType); } private static void safeClose(final InputStream in) { @@ -412,22 +440,6 @@ } } - private boolean mightNeedCleaning() { - switch (getOptions().getAutoCRLF()) { - case FALSE: - default: - return false; - - case TRUE: - case INPUT: - return true; - } - } - - private static boolean isBinary(byte[] content, int sz) { - return RawText.isBinary(content, sz); - } - private static boolean isBinary(Entry entry) throws IOException { InputStream in = entry.openInputStream(); try { @@ -437,18 +449,64 @@ } } - private static ByteBuffer filterClean(byte[] src, int n) + private ByteBuffer filterClean(byte[] src, int n, OperationType opType) throws IOException { InputStream in = new ByteArrayInputStream(src); try { - return IO.readWholeStream(filterClean(in), n); + return IO.readWholeStream(filterClean(in, opType), n); } finally { safeClose(in); } } - private static InputStream filterClean(InputStream in) { - return new EolCanonicalizingInputStream(in, true); + private InputStream filterClean(InputStream in) throws IOException { + return filterClean(in, null); + } + + private InputStream filterClean(InputStream in, OperationType opType) + throws IOException { + in = handleAutoCRLF(in, opType); + String filterCommand = getCleanFilterCommand(); + if (filterCommand != null) { + if (FilterCommandRegistry.isRegistered(filterCommand)) { + LocalFile buffer = new TemporaryBuffer.LocalFile(null); + FilterCommand command = FilterCommandRegistry + .createFilterCommand(filterCommand, repository, in, + buffer); + while (command.run() != -1) { + // loop as long as command.run() tells there is work to do + } + return buffer.openInputStreamWithAutoDestroy(); + } + FS fs = repository.getFS(); + ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand, + new String[0]); + filterProcessBuilder.directory(repository.getWorkTree()); + filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, + repository.getDirectory().getAbsolutePath()); + ExecutionResult result; + try { + result = fs.execute(filterProcessBuilder, in); + } catch (IOException | InterruptedException e) { + throw new IOException(new FilterFailedException(e, + filterCommand, getEntryPathString())); + } + int rc = result.getRc(); + if (rc != 0) { + throw new IOException(new FilterFailedException(rc, + filterCommand, getEntryPathString(), + result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), + RawParseUtils.decode(result.getStderr() + .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + } + return result.getStdout().openInputStreamWithAutoDestroy(); + } + return in; + } + + private InputStream handleAutoCRLF(InputStream in, OperationType opType) + throws IOException { + return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType(opType)); } /** @@ -460,11 +518,13 @@ return state.options; } + /** {@inheritDoc} */ @Override public int idOffset() { return contentIdOffset; } + /** {@inheritDoc} */ @Override public void reset() { if (!first()) { @@ -474,16 +534,19 @@ } } + /** {@inheritDoc} */ @Override public boolean first() { return ptr == 0; } + /** {@inheritDoc} */ @Override public boolean eof() { return ptr == entryCnt; } + /** {@inheritDoc} */ @Override public void next(final int delta) throws CorruptObjectException { ptr += delta; @@ -492,6 +555,7 @@ } } + /** {@inheritDoc} */ @Override public void back(final int delta) throws CorruptObjectException { ptr -= delta; @@ -507,6 +571,8 @@ System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen); pathLen = pathOffset + nameLen; canonLen = -1; + cleanFilterCommandHolder = null; + eolStreamTypeHolder = null; } /** @@ -522,7 +588,7 @@ * Get the filtered input length of this entry * * @return size of the content, in bytes - * @throws IOException + * @throws java.io.IOException */ public long getEntryContentLength() throws IOException { if (canonLen == -1) { @@ -564,22 +630,23 @@ * The caller will close the stream once complete. * * @return a stream to read from the file. - * @throws IOException + * @throws java.io.IOException * the file could not be opened for reading. */ public InputStream openEntryStream() throws IOException { InputStream rawis = current().openInputStream(); - if (mightNeedCleaning()) - return filterClean(rawis); - else + if (getCleanFilterCommand() == null + && getEolStreamType() == EolStreamType.DIRECT) return rawis; + else + return filterClean(rawis); } /** * Determine if the current entry path is ignored by an ignore rule. * * @return true if the entry was ignored by an ignore rule file. - * @throws IOException + * @throws java.io.IOException * a relevant ignore rule file exists but cannot be read. */ public boolean isEntryIgnored() throws IOException { @@ -592,58 +659,64 @@ * @param pLen * the length of the path in the path buffer. * @return true if the entry is ignored by an ignore rule. - * @throws IOException + * @throws java.io.IOException * a relevant ignore rule file exists but cannot be read. */ protected boolean isEntryIgnored(final int pLen) throws IOException { - return isEntryIgnored(pLen, mode, false); + return isEntryIgnored(pLen, mode); } /** - * Determine if the entry path is ignored by an ignore rule. Consider - * possible rule negation from child iterator. + * Determine if the entry path is ignored by an ignore rule. * * @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, int fileMode, - boolean negatePrevious) + private boolean isEntryIgnored(final int pLen, int fileMode) throws IOException { - IgnoreNode rules = getIgnoreNode(); - if (rules != null) { - // The ignore code wants path to start with a '/' if possible. - // If we have the '/' in our path buffer because we are inside - // a subdirectory include it in the range we convert to string. - // - int pOff = pathOffset; - if (0 < pOff) - pOff--; - String p = TreeWalk.pathOf(path, pOff, pLen); - switch (rules.isIgnored(p, FileMode.TREE.equals(fileMode), - negatePrevious)) { - case IGNORED: - return true; - case NOT_IGNORED: - return false; - case CHECK_PARENT: - negatePrevious = false; - break; - case CHECK_PARENT_NEGATE_FIRST_MATCH: - negatePrevious = true; - break; - } + // The ignore code wants path to start with a '/' if possible. + // If we have the '/' in our path buffer because we are inside + // a sub-directory include it in the range we convert to string. + // + final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset; + String pathRel = TreeWalk.pathOf(this.path, pOff, pLen); + String parentRel = getParentPath(pathRel); + + // CGit is processing .gitignore files by starting at the root of the + // repository and then recursing into subdirectories. With this + // approach, top-level ignored directories will be processed first which + // allows to skip entire subtrees and further .gitignore-file processing + // within these subtrees. + // + // We will follow the same approach by marking directories as "ignored" + // here. This allows to have a simplified FastIgnore.checkIgnore() + // implementation (both in terms of code and computational complexity): + // + // Without the "ignored" flag, we would have to apply the ignore-check + // to a path and all of its parents always(!), to determine whether a + // path is ignored directly or by one of its parent directories; with + // the "ignored" flag, we know at this point that the parent directory + // is definitely not ignored, thus the path can only become ignored if + // there is a rule matching the path itself. + if (isDirectoryIgnored(parentRel)) { + return true; } - if (parent instanceof WorkingTreeIterator) - return ((WorkingTreeIterator) parent).isEntryIgnored(pLen, fileMode, - negatePrevious); - return false; + + IgnoreNode rules = getIgnoreNode(); + final Boolean ignored = rules != null + ? rules.checkIgnored(pathRel, FileMode.TREE.equals(fileMode)) + : null; + if (ignored != null) { + return ignored.booleanValue(); + } + return parent instanceof WorkingTreeIterator + && ((WorkingTreeIterator) parent).isEntryIgnored(pLen, + fileMode); } private IgnoreNode getIgnoreNode() throws IOException { @@ -653,12 +726,12 @@ } /** - * Retrieves the {@link AttributesNode} for the current entry. + * Retrieves the {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. * - * @return {@link AttributesNode} for the current entry. + * @return the {@link org.eclipse.jgit.attributes.AttributesNode} for the + * current entry. * @throws IOException - * if an error is raised while parsing the .gitattributes file - * @since 3.7 */ public AttributesNode getEntryAttributesNode() throws IOException { if (attributesNode instanceof PerDirectoryAttributesNode) @@ -667,67 +740,15 @@ return attributesNode; } - /** - * Retrieves the {@link AttributesNode} that holds the information located - * in $GIT_DIR/info/attributes file. - * - * @return the {@link AttributesNode} that holds the information located in - * $GIT_DIR/info/attributes file. - * @throws IOException - * if an error is raised while parsing the attributes file - * @since 3.7 - */ - public AttributesNode getInfoAttributesNode() throws IOException { - if (infoAttributeNode instanceof InfoAttributesNode) - infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); - return infoAttributeNode; - } - - /** - * Retrieves the {@link AttributesNode} that holds the information located - * in system-wide file. - * - * @return the {@link AttributesNode} that holds the information located in - * system-wide file. - * @throws IOException - * IOException if an error is raised while parsing the - * attributes file - * @see CoreConfig#getAttributesFile() - * @since 3.7 - */ - public AttributesNode getGlobalAttributesNode() throws IOException { - if (globalAttributeNode instanceof GlobalAttributesNode) - globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) - .load(); - return globalAttributeNode; - } - private static final Comparator ENTRY_CMP = new Comparator() { - public int compare(final Entry o1, final Entry o2) { - final byte[] a = o1.encodedName; - final byte[] b = o2.encodedName; - final int aLen = o1.encodedNameLen; - final int bLen = o2.encodedNameLen; - int cPos; - - for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) { - final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff); - if (cmp != 0) - return cmp; - } - - if (cPos < aLen) - return (a[cPos] & 0xff) - lastPathChar(o2); - if (cPos < bLen) - return lastPathChar(o1) - (b[cPos] & 0xff); - return lastPathChar(o1) - lastPathChar(o2); + @Override + public int compare(Entry a, Entry b) { + return Paths.compare( + a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(), + b.encodedName, 0, b.encodedNameLen, b.getMode().getBits()); } }; - static int lastPathChar(final Entry e) { - return e.getMode() == FileMode.TREE ? '/' : '\0'; - } - /** * Constructor helper. * @@ -814,6 +835,7 @@ * Is the file mode of the current entry different than the given raw mode? * * @param rawMode + * an int. * @return true if different, false otherwise */ public boolean isModeDifferent(final int rawMode) { @@ -840,12 +862,14 @@ /** * Compare the metadata (mode, length, modification-timestamp) of the - * current entry and a {@link DirCacheEntry} + * current entry and a {@link org.eclipse.jgit.dircache.DirCacheEntry} * * @param entry - * the {@link DirCacheEntry} to compare with - * @return a {@link MetadataDiff} which tells whether and how the entries - * metadata differ + * the {@link org.eclipse.jgit.dircache.DirCacheEntry} to compare + * with + * @return a + * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff} + * which tells whether and how the entries metadata differ */ public MetadataDiff compareMetadata(DirCacheEntry entry) { if (entry.isAssumeValid()) @@ -854,10 +878,15 @@ if (entry.isUpdateNeeded()) return MetadataDiff.DIFFER_BY_METADATA; - if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength()) + if (isModeDifferent(entry.getRawMode())) return MetadataDiff.DIFFER_BY_METADATA; - if (isModeDifferent(entry.getRawMode())) + // Don't check for length or lastmodified on folders + int type = mode & FileMode.TYPE_MASK; + if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK) + return MetadataDiff.EQUAL; + + if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength()) return MetadataDiff.DIFFER_BY_METADATA; // Git under windows only stores seconds so we round the timestamp @@ -890,7 +919,7 @@ /** * Checks whether this entry differs from a given entry from the - * {@link DirCache}. + * {@link org.eclipse.jgit.dircache.DirCache}. * * File status information is used and if status is same we consider the * file identical to the state in the working directory. Native git uses @@ -904,7 +933,7 @@ * @param reader * access to repository objects if necessary. Should not be null. * @return true if content is most likely different. - * @throws IOException + * @throws java.io.IOException * @since 3.3 */ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck, @@ -926,9 +955,24 @@ // Lets do a content check return contentCheck(entry, reader); case EQUAL: + if (mode == FileMode.SYMLINK.getBits()) { + return contentCheck(entry, reader); + } return false; case DIFFER_BY_METADATA: - if (mode == FileMode.SYMLINK.getBits()) + if (mode == FileMode.TREE.getBits() + && entry.getFileMode().equals(FileMode.GITLINK)) { + byte[] idBuffer = idBuffer(); + int idOffset = idOffset(); + if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) { + return true; + } else if (ObjectId.zeroId().compareTo(idBuffer, + idOffset) == 0) { + return new File(repository.getWorkTree(), + entry.getPathString()).list().length > 0; + } + return false; + } else if (mode == FileMode.SYMLINK.getBits()) return contentCheck(entry, reader); return true; default: @@ -942,24 +986,39 @@ * in the index. * * @param indexIter - * {@link DirCacheIterator} positioned at the same entry as this - * iterator or null if no {@link DirCacheIterator} is available - * at this iterator's current entry + * {@link org.eclipse.jgit.dircache.DirCacheIterator} positioned + * at the same entry as this iterator or null if no + * {@link org.eclipse.jgit.dircache.DirCacheIterator} is + * available at this iterator's current entry * @return index file mode */ public FileMode getIndexFileMode(final DirCacheIterator indexIter) { final FileMode wtMode = getEntryFileMode(); - if (indexIter == null) - return wtMode; - if (getOptions().isFileMode()) + if (indexIter == null) { return wtMode; + } final FileMode iMode = indexIter.getEntryFileMode(); - if (FileMode.REGULAR_FILE == wtMode - && FileMode.EXECUTABLE_FILE == iMode) + if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) { + return wtMode; + } + if (!getOptions().isFileMode()) { + if (FileMode.REGULAR_FILE == wtMode + && FileMode.EXECUTABLE_FILE == iMode) { + return iMode; + } + if (FileMode.EXECUTABLE_FILE == wtMode + && FileMode.REGULAR_FILE == iMode) { + return iMode; + } + } + if (FileMode.GITLINK == iMode + && FileMode.TREE == wtMode) { return iMode; - if (FileMode.EXECUTABLE_FILE == wtMode - && FileMode.REGULAR_FILE == iMode) + } + if (FileMode.TREE == iMode + && FileMode.GITLINK == wtMode) { return iMode; + } return wtMode; } @@ -992,17 +1051,18 @@ return false; } else { - if (mode == FileMode.SYMLINK.getBits()) - return !new File(readContentAsNormalizedString(current())) - .equals(new File((readContentAsNormalizedString(entry, - reader)))); + if (mode == FileMode.SYMLINK.getBits()) { + return !new File(readSymlinkTarget(current())).equals( + new File(readContentAsNormalizedString(entry, reader))); + } // Content differs: that's a real change, perhaps if (reader == null) // deprecated use, do no further checks return true; - switch (getOptions().getAutoCRLF()) { - case INPUT: - case TRUE: - InputStream dcIn = null; + + switch (getEolStreamType()) { + case DIRECT: + return true; + default: try { ObjectLoader loader = reader.open(entry.getObjectId()); if (loader == null) @@ -1010,37 +1070,26 @@ // We need to compute the length, but only if it is not // a binary stream. - dcIn = new EolCanonicalizingInputStream( - loader.openStream(), true, true /* abort if binary */); long dcInLen; - try { + try (InputStream dcIn = new AutoLFInputStream( + loader.openStream(), true, + true /* abort if binary */)) { dcInLen = computeLength(dcIn); - } catch (EolCanonicalizingInputStream.IsBinaryException e) { + } catch (AutoLFInputStream.IsBinaryException e) { return true; - } finally { - dcIn.close(); } - dcIn = new EolCanonicalizingInputStream( - loader.openStream(), true); - byte[] autoCrLfHash = computeHash(dcIn, dcInLen); - boolean changed = getEntryObjectId().compareTo( - autoCrLfHash, 0) != 0; - return changed; + try (InputStream dcIn = new AutoLFInputStream( + loader.openStream(), true)) { + byte[] autoCrLfHash = computeHash(dcIn, dcInLen); + boolean changed = getEntryObjectId() + .compareTo(autoCrLfHash, 0) != 0; + return changed; + } } catch (IOException e) { return true; - } finally { - if (dcIn != null) - try { - dcIn.close(); - } catch (IOException e) { - // empty - } } - case FALSE: - break; } - return true; } } @@ -1051,12 +1100,30 @@ return FS.detect().normalize(RawParseUtils.decode(cachedBytes)); } - private static String readContentAsNormalizedString(Entry entry) throws IOException { + /** + * Reads the target of a symlink as a string. This default implementation + * fully reads the entry's input stream and converts it to a normalized + * string. Subclasses may override to provide more specialized + * implementations. + * + * @param entry + * to read + * @return the entry's content as a normalized string + * @throws java.io.IOException + * if the entry cannot be read or does not denote a symlink + * @since 4.6 + */ + protected String readSymlinkTarget(Entry entry) throws IOException { + if (!entry.getMode().equals(FileMode.SYMLINK)) { + throw new java.nio.file.NotLinkException(entry.getName()); + } long length = entry.getLength(); byte[] content = new byte[(int) length]; - InputStream is = entry.openInputStream(); - IO.readFully(is, content, 0, (int) length); - return FS.detect().normalize(RawParseUtils.decode(content)); + try (InputStream is = entry.openInputStream()) { + int bytesRead = IO.readFully(is, content, 0); + return FS.detect() + .normalize(RawParseUtils.decode(content, 0, bytesRead)); + } } private static long computeLength(InputStream in) throws IOException { @@ -1074,10 +1141,9 @@ } private byte[] computeHash(InputStream in, long length) throws IOException { - final MessageDigest contentDigest = state.contentDigest; + SHA1 contentDigest = SHA1.newInstance(); final byte[] contentReadBuffer = state.contentReadBuffer; - contentDigest.reset(); contentDigest.update(hblob); contentDigest.update((byte) ' '); @@ -1130,6 +1196,7 @@ b.get(encodedName = new byte[encodedNameLen]); } + @Override public String toString() { return getMode().toString() + " " + getName(); //$NON-NLS-1$ } @@ -1296,68 +1363,6 @@ } } - /** - * Attributes node loaded from global system-wide file. - */ - private static class GlobalAttributesNode extends AttributesNode { - final Repository repository; - - GlobalAttributesNode(Repository repository) { - this.repository = repository; - } - - AttributesNode load() throws IOException { - AttributesNode r = new AttributesNode(); - - FS fs = repository.getFS(); - String path = repository.getConfig().get(CoreConfig.KEY) - .getAttributesFile(); - if (path != null) { - File attributesFile; - if (path.startsWith("~/")) //$NON-NLS-1$ - attributesFile = fs.resolve(fs.userHome(), - path.substring(2)); - else - attributesFile = fs.resolve(null, path); - loadRulesFromFile(r, attributesFile); - } - return r.getRules().isEmpty() ? null : r; - } - } - - /** Magic type indicating there may be rules for the top level. */ - private static class InfoAttributesNode extends AttributesNode { - final Repository repository; - - InfoAttributesNode(Repository repository) { - this.repository = repository; - } - - AttributesNode load() throws IOException { - AttributesNode r = new AttributesNode(); - - FS fs = repository.getFS(); - - File attributes = fs.resolve(repository.getDirectory(), - "info/attributes"); //$NON-NLS-1$ - loadRulesFromFile(r, attributes); - - return r.getRules().isEmpty() ? null : r; - } - - } - - private static void loadRulesFromFile(AttributesNode r, File attrs) - throws FileNotFoundException, IOException { - if (attrs.exists()) { - FileInputStream in = new FileInputStream(attrs); - try { - r.parse(in); - } finally { - in.close(); - } - } - } private static final class IteratorState { /** Options used to process the working tree. */ @@ -1366,9 +1371,6 @@ /** File name character encoder. */ final CharsetEncoder nameEncoder; - /** Digest computer for {@link #contentId} computations. */ - MessageDigest contentDigest; - /** Buffer used to perform {@link #contentId} computations. */ byte[] contentReadBuffer; @@ -1378,16 +1380,145 @@ /** Position of the matching {@link DirCacheIterator}. */ int dirCacheTree; + final Map directoryToIgnored = new HashMap<>(); + IteratorState(WorkingTreeOptions options) { this.options = options; this.nameEncoder = Constants.CHARSET.newEncoder(); } - void initializeDigestAndReadBuffer() { - if (contentDigest == null) { - contentDigest = Constants.newMessageDigest(); + void initializeReadBuffer() { + if (contentReadBuffer == null) { contentReadBuffer = new byte[BUFFER_SIZE]; } } } + + /** + * Get the clean filter command for the current entry. + * + * @return the clean filter command for the current entry or + * null if no such command is defined + * @throws java.io.IOException + * @since 4.2 + */ + public String getCleanFilterCommand() throws IOException { + if (cleanFilterCommandHolder == null) { + String cmd = null; + if (state.walk != null) { + cmd = state.walk + .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN); + } + cleanFilterCommandHolder = new Holder<>(cmd); + } + return cleanFilterCommandHolder.get(); + } + + /** + * Get the eol stream type for the current entry. + * + * @return the eol stream type for the current entry or null if + * it cannot be determined. When state or state.walk is null or the + * {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on a + * {@link org.eclipse.jgit.lib.Repository} then null is returned. + * @throws java.io.IOException + * @since 4.3 + */ + public EolStreamType getEolStreamType() throws IOException { + return getEolStreamType(null); + } + + /** + * @param opType + * The operationtype (checkin/checkout) which should be used + * @return the eol stream type for the current entry or null if + * it cannot be determined. When state or state.walk is null or the + * {@link TreeWalk} is not based on a {@link Repository} then null + * is returned. + * @throws IOException + */ + private EolStreamType getEolStreamType(OperationType opType) + throws IOException { + if (eolStreamTypeHolder == null) { + EolStreamType type=null; + if (state.walk != null) { + type = state.walk.getEolStreamType(opType); + } else { + switch (getOptions().getAutoCRLF()) { + case FALSE: + type = EolStreamType.DIRECT; + break; + case TRUE: + case INPUT: + type = EolStreamType.AUTO_LF; + break; + } + } + eolStreamTypeHolder = new Holder<>(type); + } + return eolStreamTypeHolder.get(); + } + + private boolean isDirectoryIgnored(String pathRel) throws IOException { + final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset; + final String base = TreeWalk.pathOf(this.path, 0, pOff); + final String pathAbs = concatPath(base, pathRel); + return isDirectoryIgnored(pathRel, pathAbs); + } + + private boolean isDirectoryIgnored(String pathRel, String pathAbs) + throws IOException { + assert pathRel.length() == 0 || (pathRel.charAt(0) != '/' + && pathRel.charAt(pathRel.length() - 1) != '/'); + assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/' + && pathAbs.charAt(pathAbs.length() - 1) != '/'); + assert pathAbs.endsWith(pathRel); + + Boolean ignored = state.directoryToIgnored.get(pathAbs); + if (ignored != null) { + return ignored.booleanValue(); + } + + final String parentRel = getParentPath(pathRel); + if (parentRel != null && isDirectoryIgnored(parentRel)) { + state.directoryToIgnored.put(pathAbs, Boolean.TRUE); + return true; + } + + final IgnoreNode node = getIgnoreNode(); + for (String p = pathRel; node != null + && !"".equals(p); p = getParentPath(p)) { //$NON-NLS-1$ + ignored = node.checkIgnored(p, true); + if (ignored != null) { + state.directoryToIgnored.put(pathAbs, ignored); + return ignored.booleanValue(); + } + } + + if (!(this.parent instanceof WorkingTreeIterator)) { + state.directoryToIgnored.put(pathAbs, Boolean.FALSE); + return false; + } + + final WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent; + final String parentRelPath = concatPath( + TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1), + pathRel); + assert concatPath(TreeWalk.pathOf(wtParent.path, 0, + Math.max(0, wtParent.pathOffset - 1)), parentRelPath) + .equals(pathAbs); + return wtParent.isDirectoryIgnored(parentRelPath, pathAbs); + } + + private static String getParentPath(String path) { + final int slashIndex = path.lastIndexOf('/', path.length() - 2); + if (slashIndex > 0) { + return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex); + } + return path.length() > 0 ? "" : null; //$NON-NLS-1$ + } + + private static String concatPath(String p1, String p2) { + return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2; //$NON-NLS-1$ //$NON-NLS-2$ + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,37 +44,43 @@ package org.eclipse.jgit.treewalk; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.CoreConfig.CheckStat; +import org.eclipse.jgit.lib.CoreConfig.EOL; import org.eclipse.jgit.lib.CoreConfig.HideDotFiles; import org.eclipse.jgit.lib.CoreConfig.SymLinks; -/** Options used by the {@link WorkingTreeIterator}. */ +/** + * Options used by the {@link org.eclipse.jgit.treewalk.WorkingTreeIterator}. + */ public class WorkingTreeOptions { /** Key for {@link Config#get(SectionParser)}. */ - public static final Config.SectionParser KEY = new SectionParser() { - public WorkingTreeOptions parse(final Config cfg) { - return new WorkingTreeOptions(cfg); - } - }; + public static final Config.SectionParser KEY = + WorkingTreeOptions::new; private final boolean fileMode; private final AutoCRLF autoCRLF; + private final EOL eol; + private final CheckStat checkStat; private final SymLinks symlinks; private final HideDotFiles hideDotFiles; + private final boolean dirNoGitLinks; + private WorkingTreeOptions(final Config rc) { fileMode = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, ConfigConstants.CONFIG_KEY_FILEMODE, true); autoCRLF = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE); + eol = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_EOL, EOL.NATIVE); checkStat = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_CHECKSTAT, CheckStat.DEFAULT); symlinks = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, @@ -82,20 +88,45 @@ hideDotFiles = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_HIDEDOTFILES, HideDotFiles.DOTGITONLY); + dirNoGitLinks = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, + false); } /** @return true if the execute bit on working files should be trusted. */ + /** + * Whether the execute bit on working files should be trusted. + * + * @return {@code true} if the execute bit on working files should be + * trusted. + */ public boolean isFileMode() { return fileMode; } - /** @return how automatic CRLF conversion has been configured. */ + /** + * Get automatic CRLF conversion configuration. + * + * @return how automatic CRLF conversion has been configured. + */ public AutoCRLF getAutoCRLF() { return autoCRLF; } /** - * @return how stat data is compared + * Get how text line endings should be normalized. + * + * @return how text line endings should be normalized. + * @since 4.3 + */ + public EOL getEOL() { + return eol; + } + + /** + * Get how stat data is compared. + * + * @return how stat data is compared. * @since 3.0 */ public CheckStat getCheckStat() { @@ -103,6 +134,8 @@ } /** + * Get how we handle symbolic links + * * @return how we handle symbolic links * @since 3.3 */ @@ -111,10 +144,21 @@ } /** + * Get how we create '.'-files (on Windows) + * * @return how we create '.'-files (on Windows) * @since 3.5 */ public HideDotFiles getHideDotFiles() { return hideDotFiles; } + + /** + * Whether or not we treat nested repos as directories. + * + * @return whether or not we treat nested repos as directories. If true, + * folders containing .git entries will not be treated as gitlinks. + * @since 4.3 + */ + public boolean isDirNoGitLinks() { return dirNoGitLinks; } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java 2019-09-03 12:37:49.000000000 +0000 @@ -6,7 +6,9 @@ package org.eclipse.jgit.util; -import java.io.UnsupportedEncodingException; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.Arrays; @@ -17,14 +19,13 @@ *

    * I am placing this code in the Public Domain. Do with it as you will. This * software comes with no guarantees or warranties but with plenty of - * well-wishing instead! Please visit http://iharder.net/base64 periodically - * to check for updates or to contribute improvements. + * well-wishing instead! Please visit + * http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. *

    * * @author Robert Harder * @author rob@iharder.net - * @version 2.1, stripped to minimum feature set used by JGit. */ public class Base64 { /** The equals sign (=) as a byte. */ @@ -39,9 +40,6 @@ /** Indicates an invalid byte during decoding. */ private final static byte INVALID_DEC = -3; - /** Preferred encoding. */ - private final static String UTF_8 = "UTF-8"; //$NON-NLS-1$ - /** The 64 valid Base64 values. */ private final static byte[] ENC; @@ -53,15 +51,11 @@ private final static byte[] DEC; static { - try { - ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // //$NON-NLS-1$ - + "abcdefghijklmnopqrstuvwxyz" // //$NON-NLS-1$ - + "0123456789" // //$NON-NLS-1$ - + "+/" // //$NON-NLS-1$ - ).getBytes(UTF_8); - } catch (UnsupportedEncodingException uee) { - throw new RuntimeException(uee.getMessage(), uee); - } + ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // //$NON-NLS-1$ + + "abcdefghijklmnopqrstuvwxyz" // //$NON-NLS-1$ + + "0123456789" // //$NON-NLS-1$ + + "+/" // //$NON-NLS-1$ + ).getBytes(UTF_8); DEC = new byte[128]; Arrays.fill(DEC, INVALID_DEC); @@ -184,11 +178,7 @@ e += 4; } - try { - return new String(outBuff, 0, e, UTF_8); - } catch (UnsupportedEncodingException uue) { - return new String(outBuff, 0, e); - } + return new String(outBuff, 0, e, StandardCharsets.UTF_8); } /** @@ -257,7 +247,7 @@ * @param len * The length of characters to decode * @return decoded data - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * the input is not a valid Base64 sequence. */ public static byte[] decode(byte[] source, int off, int len) { @@ -304,12 +294,7 @@ * @return the decoded data */ public static byte[] decode(String s) { - byte[] bytes; - try { - bytes = s.getBytes(UTF_8); - } catch (UnsupportedEncodingException uee) { - bytes = s.getBytes(); - } + byte[] bytes = s.getBytes(UTF_8); return decode(bytes, 0, bytes.length); } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,9 +74,9 @@ private static final int BLOCK_MASK = BLOCK_SIZE - 1; - private T[][] directory; + T[][] directory; - private int size; + int size; private int tailDirIdx; @@ -84,7 +84,9 @@ private T[] tailBlock; - /** Initialize an empty list. */ + /** + * Initialize an empty list. + */ public BlockList() { directory = BlockList. newDirectory(256); directory[0] = BlockList. newBlock(); @@ -106,11 +108,13 @@ tailBlock = directory[0]; } + /** {@inheritDoc} */ @Override public int size() { return size; } + /** {@inheritDoc} */ @Override public void clear() { for (T[] block : directory) { @@ -123,6 +127,7 @@ tailBlock = directory[0]; } + /** {@inheritDoc} */ @Override public T get(int index) { if (index < 0 || size <= index) @@ -130,6 +135,7 @@ return directory[toDirectoryIndex(index)][toBlockIndex(index)]; } + /** {@inheritDoc} */ @Override public T set(int index, T element) { if (index < 0 || size <= index) @@ -187,6 +193,7 @@ } } + /** {@inheritDoc} */ @Override public boolean add(T element) { int i = tailBlkIdx; @@ -217,6 +224,7 @@ return true; } + /** {@inheritDoc} */ @Override public void add(int index, T element) { if (index == size) { @@ -238,6 +246,7 @@ } } + /** {@inheritDoc} */ @Override public T remove(int index) { if (index == size - 1) { @@ -277,16 +286,17 @@ tailBlock = directory[tailDirIdx]; } + /** {@inheritDoc} */ @Override public Iterator iterator() { return new MyIterator(); } - private static final int toDirectoryIndex(int index) { + static final int toDirectoryIndex(int index) { return index >>> BLOCK_BITS; } - private static final int toBlockIndex(int index) { + static final int toBlockIndex(int index) { return index & BLOCK_MASK; } @@ -309,10 +319,12 @@ private T[] block = directory[0]; + @Override public boolean hasNext() { return index < size; } + @Override public T next() { if (size <= index) throw new NoSuchElementException(); @@ -329,6 +341,7 @@ return res; } + @Override public void remove() { if (index == 0) throw new IllegalStateException(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,9 +49,11 @@ import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; -/** Abstract authenticator which remembers prior authentications. */ +/** + * Abstract authenticator which remembers prior authentications. + */ public abstract class CachedAuthenticator extends Authenticator { - private static final Collection cached = new CopyOnWriteArrayList(); + private static final Collection cached = new CopyOnWriteArrayList<>(); /** * Add a cached authentication for future use. @@ -63,6 +65,7 @@ cached.add(ca); } + /** {@inheritDoc} */ @Override protected final PasswordAuthentication getPasswordAuthentication() { final String host = getRequestingHost(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,12 +42,11 @@ */ package org.eclipse.jgit.util; -import java.io.IOException; import java.util.regex.Pattern; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; /** @@ -83,19 +82,19 @@ * @param firstParentId * parent id of previous commit or null * @param author - * the {@link PersonIdent} for the presumed author and time + * the {@link org.eclipse.jgit.lib.PersonIdent} for the presumed + * author and time * @param committer - * the {@link PersonIdent} for the presumed committer and time + * the {@link org.eclipse.jgit.lib.PersonIdent} for the presumed + * committer and time * @param message * The commit message * @return the change id SHA1 string (without the 'I') or null if the * message is not complete enough - * @throws IOException */ public static ObjectId computeChangeId(final ObjectId treeId, final ObjectId firstParentId, final PersonIdent author, - final PersonIdent committer, final String message) - throws IOException { + final PersonIdent committer, final String message) { String cleanMessage = clean(message); if (cleanMessage.length() == 0) return null; @@ -116,8 +115,7 @@ b.append("\n\n"); //$NON-NLS-1$ b.append(cleanMessage); try (ObjectInserter f = new ObjectInserter.Formatter()) { - return f.idFor(Constants.OBJ_COMMIT, // - b.toString().getBytes(Constants.CHARACTER_ENCODING)); + return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString())); } } @@ -142,7 +140,9 @@ * line. * * @param message + * a message. * @param changeId + * a Change-Id. * @return a commit message with an inserted Change-Id line */ public static String insertId(String message, ObjectId changeId) { @@ -152,18 +152,21 @@ /** * Find the right place to insert a Change-Id and return it. *

    - * If no Change-Id is found the Change-Id is inserted before - * the first footer line but after a Bug line. + * If no Change-Id is found the Change-Id is inserted before the first + * footer line but after a Bug line. * - * If Change-Id is found and replaceExisting is set to false, - * the message is unchanged. + * If Change-Id is found and replaceExisting is set to false, the message is + * unchanged. * - * If Change-Id is found and replaceExisting is set to true, - * the Change-Id is replaced with {@code changeId}. + * If Change-Id is found and replaceExisting is set to true, the Change-Id + * is replaced with {@code changeId}. * * @param message + * a message. * @param changeId + * a Change-Id. * @param replaceExisting + * a boolean. * @return a commit message with an inserted Change-Id line */ public static String insertId(String message, ObjectId changeId, @@ -223,6 +226,7 @@ * only lines matching {@code footerPattern}. * * @param message + * a message. * @param delimiter * the line delimiter, like "\n" or "\r\n", needed to find the * footer diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import org.eclipse.jgit.util.FS.Attributes; @@ -56,10 +55,14 @@ public class FileUtil { /** + * Read target path of a symlink. + * * @param path - * @return target path of the symlink - * @throws IOException - * @deprecated use {@link FileUtils#readSymLink(File)} instead + * a {@link java.io.File}. + * @return target path of the symlink. + * @throws java.io.IOException + * @deprecated use {@link org.eclipse.jgit.util.FileUtils#readSymLink(File)} + * instead */ @Deprecated public static String readSymlink(File path) throws IOException { @@ -67,12 +70,16 @@ } /** + * Create a symlink + * * @param path * path of the symlink to be created * @param target * target of the symlink to be created - * @throws IOException - * @deprecated use {@link FileUtils#createSymLink(File, String)} instead + * @throws java.io.IOException + * @deprecated use + * {@link org.eclipse.jgit.util.FileUtils#createSymLink(File, String)} + * instead */ @Deprecated public static void createSymLink(File path, String target) @@ -81,9 +88,14 @@ } /** + * Whether the passed file is a symlink + * * @param path + * a {@link java.io.File} object. * @return {@code true} if the passed path is a symlink - * @deprecated Use {@link Files#isSymbolicLink(java.nio.file.Path)} instead + * @deprecated Use + * {@link java.nio.file.Files#isSymbolicLink(java.nio.file.Path)} + * instead */ @Deprecated public static boolean isSymlink(File path) { @@ -91,11 +103,14 @@ } /** + * Get lastModified attribute for given path + * * @param path + * a {@link java.io.File}. * @return lastModified attribute for given path - * @throws IOException + * @throws java.io.IOException * @deprecated Use - * {@link Files#getLastModifiedTime(java.nio.file.Path, java.nio.file.LinkOption...)} + * {@link java.nio.file.Files#getLastModifiedTime(java.nio.file.Path, java.nio.file.LinkOption...)} * instead */ @Deprecated @@ -104,11 +119,15 @@ } /** + * Set lastModified attribute for given path + * * @param path + * a {@link java.io.File}. * @param time - * @throws IOException + * a long. + * @throws java.io.IOException * @deprecated Use - * {@link Files#setLastModifiedTime(java.nio.file.Path, java.nio.file.attribute.FileTime)} + * {@link java.nio.file.Files#setLastModifiedTime(java.nio.file.Path, java.nio.file.attribute.FileTime)} * instead */ @Deprecated @@ -117,10 +136,13 @@ } /** + * Whether this file exists + * * @param path + * a {@link java.io.File}. * @return {@code true} if the given path exists * @deprecated Use - * {@link Files#exists(java.nio.file.Path, java.nio.file.LinkOption...)} + * {@link java.nio.file.Files#exists(java.nio.file.Path, java.nio.file.LinkOption...)} * instead */ @Deprecated @@ -129,10 +151,14 @@ } /** + * Whether this file is hidden + * * @param path + * a {@link java.io.File}. * @return {@code true} if the given path is hidden - * @throws IOException - * @deprecated Use {@link Files#isHidden(java.nio.file.Path)} instead + * @throws java.io.IOException + * @deprecated Use {@link java.nio.file.Files#isHidden(java.nio.file.Path)} + * instead */ @Deprecated public static boolean isHidden(File path) throws IOException { @@ -140,10 +166,16 @@ } /** + * Set this file hidden + * * @param path + * a {@link java.io.File}. * @param hidden - * @throws IOException - * @deprecated Use {@link FileUtils#setHidden(File,boolean)} instead + * a boolean. + * @throws java.io.IOException + * @deprecated Use + * {@link org.eclipse.jgit.util.FileUtils#setHidden(File,boolean)} + * instead */ @Deprecated public static void setHidden(File path, boolean hidden) throws IOException { @@ -151,10 +183,14 @@ } /** + * Get file length + * * @param path + * a {@link java.io.File}. * @return length of the given file - * @throws IOException - * @deprecated Use {@link FileUtils#getLength(File)} instead + * @throws java.io.IOException + * @deprecated Use {@link org.eclipse.jgit.util.FileUtils#getLength(File)} + * instead */ @Deprecated public static long getLength(File path) throws IOException { @@ -162,10 +198,13 @@ } /** + * Whether the given File is a directory + * * @param path - * @return {@code true} if the given file a directory + * a {@link java.io.File} object. + * @return {@code true} if the given file is a directory * @deprecated Use - * {@link Files#isDirectory(java.nio.file.Path, java.nio.file.LinkOption...)} + * {@link java.nio.file.Files#isDirectory(java.nio.file.Path, java.nio.file.LinkOption...)} * instead */ @Deprecated @@ -174,10 +213,13 @@ } /** + * Whether the given File is a file + * * @param path + * a {@link java.io.File} object. * @return {@code true} if the given file is a file * @deprecated Use - * {@link Files#isRegularFile(java.nio.file.Path, java.nio.file.LinkOption...)} + * {@link java.nio.file.Files#isRegularFile(java.nio.file.Path, java.nio.file.LinkOption...)} * instead */ @Deprecated @@ -186,9 +228,13 @@ } /** + * Whether the given file can be executed + * * @param path + * a {@link java.io.File} object. * @return {@code true} if the given file can be executed - * @deprecated Use {@link FileUtils#canExecute(File)} instead + * @deprecated Use {@link org.eclipse.jgit.util.FileUtils#canExecute(File)} + * instead */ @Deprecated public static boolean canExecute(File path) { @@ -196,9 +242,12 @@ } /** + * Delete the given file + * * @param path - * @throws IOException - * @deprecated use {@link FileUtils#delete(File)} + * a {@link java.io.File} object. + * @throws java.io.IOException + * @deprecated use {@link org.eclipse.jgit.util.FileUtils#delete(File)} */ @Deprecated public static void delete(File path) throws IOException { @@ -206,10 +255,16 @@ } /** + * Get file system attributes for the given file + * * @param fs + * a {@link org.eclipse.jgit.util.FS} object. * @param path + * a {@link java.io.File} object. * @return file system attributes for the given file - * @deprecated Use {@link FileUtils#getFileAttributesPosix(FS,File)} instead + * @deprecated Use + * {@link org.eclipse.jgit.util.FileUtils#getFileAttributesPosix(FS,File)} + * instead */ @Deprecated public static Attributes getFileAttributesPosix(FS fs, File path) { @@ -217,9 +272,14 @@ } /** + * NFC normalize File (on Mac), otherwise do nothing + * * @param file - * @return on Mac: NFC normalized {@link File}, otherwise the passed file - * @deprecated Use {@link FileUtils#normalize(File)} instead + * a {@link java.io.File}. + * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed + * file + * @deprecated Use {@link org.eclipse.jgit.util.FileUtils#normalize(File)} + * instead */ @Deprecated public static File normalize(File file) { @@ -227,9 +287,13 @@ } /** + * NFC normalize file name (on Mac), otherwise do nothing + * * @param name + * a {@link java.lang.String} object. * @return on Mac: NFC normalized form of given name - * @deprecated Use {@link FileUtils#normalize(String)} instead + * @deprecated Use {@link org.eclipse.jgit.util.FileUtils#normalize(String)} + * instead */ @Deprecated public static String normalize(String name) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,10 +47,10 @@ 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.InvalidPathException; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -65,6 +65,7 @@ import java.text.Normalizer.Form; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.regex.Pattern; import org.eclipse.jgit.internal.JGitText; @@ -111,15 +112,34 @@ public static final int EMPTY_DIRECTORIES_ONLY = 16; /** + * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}. + * + * @param f + * {@code File} to be converted to {@code Path} + * @return the path represented by the file + * @throws java.io.IOException + * in case the path represented by the file is not valid ( + * {@link java.nio.file.InvalidPathException}) + * @since 4.10 + */ + public static Path toPath(final File f) throws IOException { + try { + return f.toPath(); + } catch (InvalidPathException ex) { + throw new IOException(ex); + } + } + + /** * Delete file or empty folder * * @param f * {@code File} to be deleted - * @throws IOException + * @throws java.io.IOException * if deletion of {@code f} fails. This may occur if {@code f} * didn't exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to delete the same file. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to delete the same file. */ public static void delete(final File f) throws IOException { delete(f, NONE); @@ -135,12 +155,12 @@ * a subtree, {@code RETRY} to retry when deletion failed. * Retrying may help if the underlying file system doesn't allow * deletion of files being read by another thread. - * @throws IOException + * @throws java.io.IOException * if deletion of {@code f} fails. This may occur if {@code f} * didn't exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to delete the same file. This - * exception is not thrown when IGNORE_ERRORS is set. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to delete the same file. + * This exception is not thrown when IGNORE_ERRORS is set. */ public static void delete(final File f, int options) throws IOException { FS fs = FS.DETECTED; @@ -150,8 +170,8 @@ if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) { final File[] items = f.listFiles(); if (items != null) { - List files = new ArrayList(); - List dirs = new ArrayList(); + List files = new ArrayList<>(); + List dirs = new ArrayList<>(); for (File c : items) if (c.isFile()) files.add(c); @@ -217,7 +237,7 @@ * the old {@code File} * @param dst * the new {@code File} - * @throws IOException + * @throws java.io.IOException * if the rename has failed * @since 3.0 */ @@ -227,18 +247,20 @@ } /** - * 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} + * Rename a file or folder using the passed + * {@link java.nio.file.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 java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set. + * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the + * rename will be done atomically or fail with an + * {@link java.nio.file.AtomicMoveNotSupportedException} * * @param src * the old file @@ -246,10 +268,10 @@ * the new file * @param options * options to pass to - * {@link Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} - * @throws AtomicMoveNotSupportedException + * {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} + * @throws java.nio.file.AtomicMoveNotSupportedException * if file cannot be moved as an atomic file system operation - * @throws IOException + * @throws java.io.IOException * @since 4.1 */ public static void rename(final File src, final File dst, @@ -258,7 +280,7 @@ int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1; while (--attempts >= 0) { try { - Files.move(src.toPath(), dst.toPath(), options); + Files.move(toPath(src), toPath(dst), options); return; } catch (AtomicMoveNotSupportedException e) { throw e; @@ -268,7 +290,7 @@ delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); } // On *nix there is no try, you do or do not - Files.move(src.toPath(), dst.toPath(), options); + Files.move(toPath(src), toPath(dst), options); return; } catch (IOException e2) { // ignore and continue retry @@ -292,11 +314,12 @@ * * @param d * directory to be created - * @throws IOException + * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to create the same directory. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to create the same + * directory. */ public static void mkdir(final File d) throws IOException { @@ -311,11 +334,12 @@ * @param skipExisting * if {@code true} skip creation of the given directory if it * already exists in the file system - * @throws IOException + * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to create the same directory. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to create the same + * directory. */ public static void mkdir(final File d, boolean skipExisting) throws IOException { @@ -335,11 +359,12 @@ * * @param d * directory to be created - * @throws IOException + * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to create the same directory. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to create the same + * directory. */ public static void mkdirs(final File d) throws IOException { mkdirs(d, false); @@ -356,11 +381,12 @@ * @param skipExisting * if {@code true} skip creation of the given directory if it * already exists in the file system - * @throws IOException + * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore - * cause IOExceptions during race conditions when multiple - * concurrent threads all try to create the same directory. + * cause java.io.IOExceptions during race conditions when + * multiple concurrent threads all try to create the same + * directory. */ public static void mkdirs(final File d, boolean skipExisting) throws IOException { @@ -380,12 +406,12 @@ * filesystem activities that might affect the file. *

    * Note: this method should not be used for file-locking, as the resulting - * protocol cannot be made to work reliably. The {@link FileLock} facility - * should be used instead. + * protocol cannot be made to work reliably. The + * {@link java.nio.channels.FileLock} facility should be used instead. * * @param f * the file to be created - * @throws IOException + * @throws java.io.IOException * if the named file already exists or if an I/O error occurred */ public static void createNewFile(File f) throws IOException { @@ -398,31 +424,43 @@ * Create a symbolic link * * @param path + * the path of the symbolic link to create * @param target - * @throws IOException - * @since 3.0 + * the target of the symbolic link + * @return the path to the symbolic link + * @throws java.io.IOException + * @since 4.2 */ - public static void createSymLink(File path, String target) + public static Path createSymLink(File path, String target) throws IOException { - Path nioPath = path.toPath(); + Path nioPath = toPath(path); if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { - Files.delete(nioPath); + BasicFileAttributes attrs = Files.readAttributes(nioPath, + BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + if (attrs.isRegularFile() || attrs.isSymbolicLink()) { + delete(path); + } else { + delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE); + } } if (SystemReader.getInstance().isWindows()) { target = target.replace('/', '\\'); } - Path nioTarget = new File(target).toPath(); - Files.createSymbolicLink(nioPath, nioTarget); + Path nioTarget = toPath(new File(target)); + return Files.createSymbolicLink(nioPath, nioTarget); } /** + * Read target path of the symlink. + * * @param path + * a {@link java.io.File} object. * @return target path of the symlink, or null if it is not a symbolic link - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public static String readSymLink(File path) throws IOException { - Path nioPath = path.toPath(); + Path nioPath = toPath(path); Path target = Files.readSymbolicLink(nioPath); String targetString = target.toString(); if (SystemReader.getInstance().isWindows()) { @@ -437,11 +475,13 @@ * Create a temporary directory. * * @param prefix + * prefix string * @param suffix + * suffix string * @param dir * The parent dir, can be null to use system default temp dir. * @return the temp dir created. - * @throws IOException + * @throws java.io.IOException * @since 3.4 */ public static File createTempDir(String prefix, String suffix, File dir) @@ -458,10 +498,76 @@ throw new IOException(JGitText.get().cannotCreateTempDir); } + + /** + * Relativize a path + * + * @deprecated Use the more-clearly-named + * {@link org.eclipse.jgit.util.FileUtils#relativizeNativePath(String, String)} + * instead, or directly call + * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)} + * + * Expresses other as a relative file path from + * base. File-separator and case sensitivity are + * based on the current file system. + * + * See also + * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 3.7 + */ + @Deprecated + public static String relativize(String base, String other) { + return relativizeNativePath(base, other); + } + /** - * This will try and make a given path relative to another. + * Expresses other as a relative file path from + * base. File-separator and case sensitivity are based on the + * current file system. + * + * See also + * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 4.8 + */ + public static String relativizeNativePath(String base, String other) { + return FS.DETECTED.relativize(base, other); + } + + /** + * Expresses other as a relative file path from + * base. File-separator and case sensitivity are based on Git's + * internal representation of files (which matches Unix). + * + * See also + * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 4.8 + */ + public static String relativizeGitPath(String base, String other) { + return relativizePath(base, other, "/", false); //$NON-NLS-1$ + } + + + /** + * Expresses other as a relative file path from base *

    - * For example, if this is called with the two following paths : + * For example, if called with the two following paths : * *

     	 * base = "c:\\Users\\jdoe\\eclipse\\git\\project"
    @@ -469,10 +575,7 @@
     	 * 
    * * This will return "..\\another_project\\pom.xml". - *

    - *

    - * This method uses {@link File#separator} to split the paths into segments. - *

    + * *

    * Note that this will return the empty String if base * and other are equal. @@ -484,29 +587,32 @@ * folder and not a file. * @param other * The path that will be made relative to base. + * @param dirSeparator + * A string that separates components of the path. In practice, this is "/" or "\\". + * @param caseSensitive + * Whether to consider differently-cased directory names as distinct * @return A relative path that, when resolved against base, * will yield the original other. - * @since 3.7 + * @since 4.8 */ - public static String relativize(String base, String other) { + public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) { if (base.equals(other)) return ""; //$NON-NLS-1$ - final boolean ignoreCase = !FS.DETECTED.isCaseSensitive(); - final String[] baseSegments = base.split(Pattern.quote(File.separator)); + final String[] baseSegments = base.split(Pattern.quote(dirSeparator)); final String[] otherSegments = other.split(Pattern - .quote(File.separator)); + .quote(dirSeparator)); int commonPrefix = 0; while (commonPrefix < baseSegments.length && commonPrefix < otherSegments.length) { - if (ignoreCase + if (caseSensitive && baseSegments[commonPrefix] - .equalsIgnoreCase(otherSegments[commonPrefix])) + .equals(otherSegments[commonPrefix])) commonPrefix++; - else if (!ignoreCase + else if (!caseSensitive && baseSegments[commonPrefix] - .equals(otherSegments[commonPrefix])) + .equalsIgnoreCase(otherSegments[commonPrefix])) commonPrefix++; else break; @@ -514,11 +620,11 @@ final StringBuilder builder = new StringBuilder(); for (int i = commonPrefix; i < baseSegments.length; i++) - builder.append("..").append(File.separator); //$NON-NLS-1$ + builder.append("..").append(dirSeparator); //$NON-NLS-1$ for (int i = commonPrefix; i < otherSegments.length; i++) { builder.append(otherSegments[i]); if (i < otherSegments.length - 1) - builder.append(File.separator); + builder.append(dirSeparator); } return builder.toString(); } @@ -527,13 +633,36 @@ * Determine if an IOException is a Stale NFS File Handle * * @param ioe + * an {@link java.io.IOException} object. * @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$ + && msg.toLowerCase(Locale.ROOT) + .matches("stale .*file .*handle"); //$NON-NLS-1$ + } + + /** + * Determine if a throwable or a cause in its causal chain is a Stale NFS + * File Handle + * + * @param throwable + * a {@link java.lang.Throwable} object. + * @return a boolean true if the throwable or a cause in its causal chain is + * a Stale NFS File Handle + * @since 4.7 + */ + public static boolean isStaleFileHandleInCausalChain(Throwable throwable) { + while (throwable != null) { + if (throwable instanceof IOException + && isStaleFileHandle((IOException) throwable)) { + return true; + } + throwable = throwable.getCause(); + } + return false; } /** @@ -551,17 +680,30 @@ * @throws IOException */ static long lastModified(File file) throws IOException { - return Files.getLastModifiedTime(file.toPath(), LinkOption.NOFOLLOW_LINKS) + return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) .toMillis(); } /** + * Return all the attributes of a file, without following symbolic links. + * + * @param file + * @return {@link BasicFileAttributes} of the file + * @throws IOException in case of any I/O errors accessing the file + * + * @since 4.5.6 + */ + static BasicFileAttributes fileAttributes(File file) throws IOException { + return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + } + + /** * @param file * @param time * @throws IOException */ static void setLastModified(File file, long time) throws IOException { - Files.setLastModifiedTime(file.toPath(), FileTime.fromMillis(time)); + Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); } /** @@ -579,28 +721,35 @@ * @throws IOException */ static boolean isHidden(File file) throws IOException { - return Files.isHidden(file.toPath()); + return Files.isHidden(toPath(file)); } /** + * Set a file hidden (on Windows) + * * @param file + * a {@link java.io.File} object. * @param hidden - * @throws IOException + * a boolean. + * @throws java.io.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$ + Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$ LinkOption.NOFOLLOW_LINKS); } /** + * Get file length + * * @param file + * a {@link java.io.File}. * @return length of the given file - * @throws IOException + * @throws java.io.IOException * @since 4.1 */ public static long getLength(File file) throws IOException { - Path nioPath = file.toPath(); + Path nioPath = toPath(file); if (Files.isSymbolicLink(nioPath)) return Files.readSymbolicLink(nioPath).toString() .getBytes(Constants.CHARSET).length; @@ -626,8 +775,11 @@ } /** + * Whether the given file can be executed. + * * @param file - * @return {@code true} if the given file can be executed + * a {@link java.io.File} object. + * @return {@code true} if the given file can be executed. * @since 4.1 */ public static boolean canExecute(File file) { @@ -644,7 +796,7 @@ */ static Attributes getFileAttributesBasic(FS fs, File file) { try { - Path nioPath = file.toPath(); + Path nioPath = toPath(file); BasicFileAttributes readAttributes = nioPath .getFileSystem() .provider() @@ -669,14 +821,18 @@ } /** + * Get file system attributes for the given file. + * * @param fs + * a {@link org.eclipse.jgit.util.FS} object. * @param file - * @return file system attributes for the given file + * a {@link java.io.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(); + Path nioPath = toPath(file); PosixFileAttributes readAttributes = nioPath .getFileSystem() .provider() @@ -702,8 +858,12 @@ } /** + * NFC normalize a file (on Mac), otherwise do nothing + * * @param file - * @return on Mac: NFC normalized {@link File}, otherwise the passed file + * a {@link java.io.File}. + * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed + * file * @since 4.1 */ public static File normalize(File file) { @@ -718,7 +878,10 @@ } /** + * On Mac: get NFC normalized form of given name, otherwise the given name. + * * @param name + * a {@link java.lang.String} object. * @return on Mac: NFC normalized form of given name * @since 4.1 */ @@ -730,4 +893,44 @@ } return name; } + + /** + * Best-effort variation of {@link java.io.File#getCanonicalFile()} + * returning the input file if the file cannot be canonicalized instead of + * throwing {@link java.io.IOException}. + * + * @param file + * to be canonicalized; may be {@code null} + * @return canonicalized file, or the unchanged input file if + * canonicalization failed or if {@code file == null} + * @throws java.lang.SecurityException + * if {@link java.io.File#getCanonicalFile()} throws one + * @since 4.2 + */ + public static File canonicalize(File file) { + if (file == null) { + return null; + } + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return file; + } + } + + /** + * Convert a path to String, replacing separators as necessary. + * + * @param file + * a {@link java.io.File}. + * @return file's path as a String + * @since 4.10 + */ + public static String pathToString(File file) { + final String path = file.getPath(); + if (SystemReader.getInstance().isWindows()) { + return path.replace('\\', '/'); + } + return path; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,29 +44,35 @@ package org.eclipse.jgit.util; import java.io.BufferedReader; -import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintStream; -import java.io.PrintWriter; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Callable; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; @@ -74,8 +80,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Abstraction to support various file system operations not in Java. */ +/** + * Abstraction to support various file system operations not in Java. + */ public abstract class FS { + private static final Logger LOG = LoggerFactory.getLogger(FS.class); + /** * This class creates FS instances. It will be overridden by a Java7 variant * if such can be detected in {@link #detect(Boolean)}. @@ -110,12 +120,57 @@ } } - private final static Logger LOG = LoggerFactory.getLogger(FS.class); + /** + * Result of an executed process. The caller is responsible to close the + * contained {@link TemporaryBuffer}s + * + * @since 4.2 + */ + public static class ExecutionResult { + private TemporaryBuffer stdout; + + private TemporaryBuffer stderr; + + private int rc; + + /** + * @param stdout + * @param stderr + * @param rc + */ + public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, + int rc) { + this.stdout = stdout; + this.stderr = stderr; + this.rc = rc; + } + + /** + * @return buffered standard output stream + */ + public TemporaryBuffer getStdout() { + return stdout; + } + + /** + * @return buffered standard error stream + */ + public TemporaryBuffer getStderr() { + return stderr; + } + + /** + * @return the return code of the process + */ + public int getRc() { + return rc; + } + } /** The auto-detected implementation selected for this operating system and JRE. */ public static final FS DETECTED = detect(); - private static FSFactory factory; + private volatile static FSFactory factory; /** * Auto-detect the appropriate file system abstraction. @@ -144,7 +199,6 @@ * * * Note: this parameter is only relevant on Windows. - * * @return detected file system abstraction */ public static FS detect(Boolean cygwinUsed) { @@ -176,7 +230,11 @@ gitSystemConfig = src.gitSystemConfig; } - /** @return a new instance of the same type of FS. */ + /** + * Create a new instance of the same type of FS. + * + * @return a new instance of the same type of FS. + */ public abstract FS newInstance(); /** @@ -188,6 +246,21 @@ public abstract boolean supportsExecute(); /** + * Does this file system support atomic file creation via + * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is + * not guaranteed that when two file system clients run createNewFile() in + * parallel only one will succeed. In such cases both clients may think they + * created a new file. + * + * @return true if this implementation support atomic creation of new Files + * by {@link java.io.File#createNewFile()} + * @since 4.5 + */ + public boolean supportsAtomicCreateNewFile() { + return true; + } + + /** * Does this operating system and JRE supports symbolic links. The * capability to handle symbolic links is detected at runtime. * @@ -242,8 +315,9 @@ * than that of the link target. * * @param f + * a {@link java.io.File} object. * @return last modified time of f - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public long lastModified(File f) throws IOException { @@ -255,8 +329,10 @@ * symbolic links, the link is modified, not the target, * * @param f + * a {@link java.io.File} object. * @param time - * @throws IOException + * last modified time + * @throws java.io.IOException * @since 3.0 */ public void setLastModified(File f, long time) throws IOException { @@ -268,8 +344,9 @@ * it's the length of the link, else the length of the target. * * @param path + * a {@link java.io.File} object. * @return length of a file - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public long length(File path) throws IOException { @@ -280,7 +357,8 @@ * Delete a file. Throws an exception if delete fails. * * @param f - * @throws IOException + * a {@link java.io.File} object. + * @throws java.io.IOException * this may be a Java7 subclass with detailed information * @since 3.3 */ @@ -327,7 +405,7 @@ public File userHome() { Holder p = userHome; if (p == null) { - p = new Holder(userHomeImpl()); + p = new Holder<>(userHomeImpl()); userHome = p; } return p.value; @@ -342,7 +420,7 @@ * @return {@code this}. */ public FS setUserHome(File path) { - userHome = new Holder(path); + userHome = new Holder<>(path); return this; } @@ -354,6 +432,19 @@ public abstract boolean retryFailedLockFileCommit(); /** + * Return all the attributes of a file, without following symbolic links. + * + * @param file + * @return {@link BasicFileAttributes} of the file + * @throws IOException in case of any I/O errors accessing the file + * + * @since 4.5.6 + */ + public BasicFileAttributes fileAttributes(File file) throws IOException { + return FileUtils.fileAttributes(file); + } + + /** * Determine the user's home directory (location where preferences are). * * @return the user's home directory; null if the user does not have one. @@ -361,6 +452,7 @@ protected File userHomeImpl() { final String home = AccessController .doPrivileged(new PrivilegedAction() { + @Override public String run() { return System.getProperty("user.home"); //$NON-NLS-1$ } @@ -380,7 +472,7 @@ * Files to search for in the given path * @return the first match found, or null * @since 3.0 - **/ + */ protected static File searchPath(final String path, final String... lookFor) { if (path == null) return null; @@ -404,9 +496,14 @@ * as component array * @param encoding * to be used to parse the command's output - * @return the one-line output of the command - */ - protected static String readPipe(File dir, String[] command, String encoding) { + * @return the one-line output of the command or {@code null} if there is + * none + * @throws org.eclipse.jgit.errors.CommandFailedException + * thrown when the command failed (return code was non-zero) + */ + @Nullable + protected static String readPipe(File dir, String[] command, + String encoding) throws CommandFailedException { return readPipe(dir, command, encoding, null); } @@ -422,10 +519,16 @@ * @param env * Map of environment variables to be merged with those of the * current process - * @return the one-line output of the command + * @return the one-line output of the command or {@code null} if there is + * none + * @throws org.eclipse.jgit.errors.CommandFailedException + * thrown when the command failed (return code was non-zero) * @since 4.0 */ - protected static String readPipe(File dir, String[] command, String encoding, Map env) { + @Nullable + protected static String readPipe(File dir, String[] command, + String encoding, Map env) + throws CommandFailedException { final boolean debug = LOG.isDebugEnabled(); try { if (debug) { @@ -437,47 +540,50 @@ if (env != null) { pb.environment().putAll(env); } - Process p = pb.start(); - BufferedReader lineRead = new BufferedReader( - new InputStreamReader(p.getInputStream(), encoding)); + Process p; + try { + p = pb.start(); + } catch (IOException e) { + // Process failed to start + throw new CommandFailedException(-1, e.getMessage(), e); + } p.getOutputStream().close(); GobblerThread gobbler = new GobblerThread(p, command, dir); gobbler.start(); String r = null; - try { + try (BufferedReader lineRead = new BufferedReader( + new InputStreamReader(p.getInputStream(), encoding))) { r = lineRead.readLine(); if (debug) { LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - LOG.debug("(ignoring remaing output:"); //$NON-NLS-1$ - } - String l; - while ((l = lineRead.readLine()) != null) { - if (debug) { + LOG.debug("remaining output:\n"); //$NON-NLS-1$ + String l; + while ((l = lineRead.readLine()) != null) { LOG.debug(l); } } - } finally { - p.getErrorStream().close(); - lineRead.close(); } for (;;) { try { int rc = p.waitFor(); gobbler.join(); - if (rc == 0 && r != null && r.length() > 0 - && !gobbler.fail.get()) + if (rc == 0 && !gobbler.fail.get()) { return r; - if (debug) { - LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$ + } else { + if (debug) { + LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$ + } + throw new CommandFailedException(rc, + gobbler.errorMessage.get(), + gobbler.exception.get()); } - break; } catch (InterruptedException ie) { // Stop bothering me, I have a zombie to reap. } } } catch (IOException e) { - LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ + LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ @@ -486,59 +592,76 @@ } private static class GobblerThread extends Thread { + + /* The process has 5 seconds to exit after closing stderr */ + private static final int PROCESS_EXIT_TIMEOUT = 5; + private final Process p; private final String desc; private final String dir; - private final boolean debug = LOG.isDebugEnabled(); - private final AtomicBoolean fail = new AtomicBoolean(); + final AtomicBoolean fail = new AtomicBoolean(); + final AtomicReference errorMessage = new AtomicReference<>(); + final AtomicReference exception = new AtomicReference<>(); - private GobblerThread(Process p, String[] command, File dir) { + GobblerThread(Process p, String[] command, File dir) { this.p = p; - if (debug) { - this.desc = Arrays.asList(command).toString(); - this.dir = dir.toString(); - } else { - this.desc = null; - this.dir = null; - } + this.desc = Arrays.toString(command); + this.dir = Objects.toString(dir); } + @Override public void run() { - InputStream is = p.getErrorStream(); - try { + StringBuilder err = new StringBuilder(); + try (InputStream is = p.getErrorStream()) { int ch; - if (debug) { - while ((ch = is.read()) != -1) { - System.err.print((char) ch); - } + while ((ch = is.read()) != -1) { + err.append((char) ch); + } + } catch (IOException e) { + if (waitForProcessCompletion(e) && p.exitValue() != 0) { + setError(e, e.getMessage(), p.exitValue()); + fail.set(true); } else { - while (is.read() != -1) { - // ignore + // ignore. command terminated faster and stream was just closed + // or the process didn't terminate within timeout + } + } finally { + if (waitForProcessCompletion(null) && err.length() > 0) { + setError(null, err.toString(), p.exitValue()); + if (p.exitValue() != 0) { + fail.set(true); } } - } catch (IOException e) { - logError(e); - fail.set(true); } + } + + @SuppressWarnings("boxing") + private boolean waitForProcessCompletion(IOException originalError) { try { - is.close(); - } catch (IOException e) { - logError(e); - fail.set(true); + if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) { + setError(originalError, MessageFormat.format( + JGitText.get().commandClosedStderrButDidntExit, + desc, PROCESS_EXIT_TIMEOUT), -1); + fail.set(true); + } + } catch (InterruptedException e) { + LOG.error(MessageFormat.format( + JGitText.get().threadInterruptedWhileRunning, desc), e); } + return false; } - private void logError(Throwable t) { - if (!debug) { - return; - } - String msg = MessageFormat.format( - JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir); - LOG.debug(msg, t); + private void setError(IOException e, String message, int exitCode) { + exception.set(e); + errorMessage.set(MessageFormat.format( + JGitText.get().exceptionCaughtDuringExecutionOfCommand, + desc, dir, Integer.valueOf(exitCode), message)); } } /** + * Discover the path to the Git executable. + * * @return the path to the Git executable or {@code null} if it cannot be * determined. * @since 4.0 @@ -546,6 +669,8 @@ protected abstract File discoverGitExe(); /** + * Discover the path to the system-wide Git configuration file + * * @return the path to the system-wide Git configuration file or * {@code null} if it cannot be determined. * @since 4.0 @@ -556,14 +681,35 @@ return null; } + // Bug 480782: Check if the discovered git executable is JGit CLI + String v; + try { + v = readPipe(gitExe.getParentFile(), + new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$ + Charset.defaultCharset().name()); + } catch (CommandFailedException e) { + LOG.warn(e.getMessage()); + return null; + } + if (StringUtils.isEmptyOrNull(v) + || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$ + return null; + } + // Trick Git into printing the path to the config file by using "echo" // as the editor. Map env = new HashMap<>(); env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$ - String w = readPipe(gitExe.getParentFile(), + String w; + try { + w = readPipe(gitExe.getParentFile(), new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name(), env); + } catch (CommandFailedException e) { + LOG.warn(e.getMessage()); + return null; + } if (StringUtils.isEmptyOrNull(w)) { return null; } @@ -572,13 +718,15 @@ } /** - * @return the currently used path to the system-wide Git configuration - * file or {@code null} if none has been set. + * Get the currently used path to the system-wide Git configuration file. + * + * @return the currently used path to the system-wide Git configuration file + * or {@code null} if none has been set. * @since 4.0 */ public File getGitSystemConfig() { if (gitSystemConfig == null) { - gitSystemConfig = new Holder(discoverGitSystemConfig()); + gitSystemConfig = new Holder<>(discoverGitSystemConfig()); } return gitSystemConfig.value; } @@ -592,12 +740,15 @@ * @since 4.0 */ public FS setGitSystemConfig(File configFile) { - gitSystemConfig = new Holder(configFile); + gitSystemConfig = new Holder<>(configFile); return this; } /** + * Get the parent directory of this file's parent directory + * * @param grandchild + * a {@link java.io.File} object. * @return the parent directory of this file's parent directory or * {@code null} in case there's no grandparent directory * @since 4.0 @@ -615,8 +766,9 @@ * Check if a file is a symbolic link and read it * * @param path + * a {@link java.io.File} object. * @return target of link or null - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public String readSymLink(File path) throws IOException { @@ -624,9 +776,12 @@ } /** + * Whether the path is a symbolic link (and we support these). + * * @param path + * a {@link java.io.File} object. * @return true if the path is a symbolic link (and we support these) - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public boolean isSymLink(File path) throws IOException { @@ -638,6 +793,7 @@ * target does not exist * * @param path + * a {@link java.io.File} object. * @return true if path exists * @since 3.0 */ @@ -650,6 +806,7 @@ * path is a symbolic link to a directory, this method returns false. * * @param path + * a {@link java.io.File} object. * @return true if file is a directory, * @since 3.0 */ @@ -662,6 +819,7 @@ * symbolic links the test returns false if path represents a symbolic link. * * @param path + * a {@link java.io.File} object. * @return true if path represents a regular file * @since 3.0 */ @@ -670,10 +828,14 @@ } /** + * Whether path is hidden, either starts with . on unix or has the hidden + * attribute in windows + * * @param path + * a {@link java.io.File} object. * @return true if path is hidden, either starts with . on unix or has the * hidden attribute in windows - * @throws IOException + * @throws java.io.IOException * @since 3.0 */ public boolean isHidden(File path) throws IOException { @@ -684,8 +846,10 @@ * Set the hidden attribute for file whose name starts with a period. * * @param path + * a {@link java.io.File} object. * @param hidden - * @throws IOException + * whether to set the file hidden + * @throws java.io.IOException * @since 3.0 */ public void setHidden(File path, boolean hidden) throws IOException { @@ -696,8 +860,10 @@ * Create a symbolic link * * @param path + * a {@link java.io.File} object. * @param target - * @throws IOException + * target path of the symlink + * @throws java.io.IOException * @since 3.0 */ public void createSymLink(File path, String target) throws IOException { @@ -705,7 +871,94 @@ } /** - * See {@link FileUtils#relativize(String, String)}. + * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses + * of this class may take care to provide a safe implementation for this + * even if {@link #supportsAtomicCreateNewFile()} is false + * + * @param path + * the file to be created + * @return true if the file was created, false if + * the file already existed + * @throws java.io.IOException + * @deprecated use {@link #createNewFileAtomic(File)} instead + * @since 4.5 + */ + @Deprecated + public boolean createNewFile(File path) throws IOException { + return path.createNewFile(); + } + + /** + * A token representing a file created by + * {@link #createNewFileAtomic(File)}. The token must be retained until the + * file has been deleted in order to guarantee that the unique file was + * created atomically. As soon as the file is no longer needed the lock + * token must be closed. + * + * @since 4.7 + */ + public static class LockToken implements Closeable { + private boolean isCreated; + + private Optional link; + + LockToken(boolean isCreated, Optional link) { + this.isCreated = isCreated; + this.link = link; + } + + /** + * @return {@code true} if the file was created successfully + */ + public boolean isCreated() { + return isCreated; + } + + @Override + public void close() { + if (!link.isPresent()) { + return; + } + Path p = link.get(); + if (!Files.exists(p)) { + return; + } + try { + Files.delete(p); + } catch (IOException e) { + LOG.error(MessageFormat + .format(JGitText.get().closeLockTokenFailed, this), e); + } + } + + @Override + public String toString() { + return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$ + ", link=" //$NON-NLS-1$ + + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$ + : "]"); //$NON-NLS-1$ + } + } + + /** + * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses + * of this class may take care to provide a safe implementation for this + * even if {@link #supportsAtomicCreateNewFile()} is false + * + * @param path + * the file to be created + * @return LockToken this token must be closed after the created file was + * deleted + * @throws IOException + * @since 4.7 + */ + public LockToken createNewFileAtomic(File path) throws IOException { + return new LockToken(path.createNewFile(), Optional.empty()); + } + + /** + * See + * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * The path against which other should be @@ -714,11 +967,11 @@ * The path that will be made relative to base. * @return A relative path that, when resolved against base, * will yield the original other. - * @see FileUtils#relativize(String, String) + * @see FileUtils#relativizePath(String, String, String, boolean) * @since 3.7 */ public String relativize(String base, String other) { - return FileUtils.relativize(base, other); + return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive()); } /** @@ -738,7 +991,7 @@ * Arguments to pass to this hook. Cannot be null, * but can be an empty array. * @return The ProcessResult describing this hook's execution. - * @throws JGitInternalException + * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 @@ -773,7 +1026,7 @@ * A string to pass on to the standard input of the hook. May be * null. * @return The ProcessResult describing this hook's execution. - * @throws JGitInternalException + * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 @@ -809,7 +1062,7 @@ * A string to pass on to the standard input of the hook. May be * null. * @return The ProcessResult describing this hook's execution. - * @throws JGitInternalException + * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 @@ -854,8 +1107,8 @@ * The repository within which to find a hook. * @param hookName * The name of the hook we're trying to find. - * @return The {@link File} containing this particular hook if it exists in - * the given repository, null otherwise. + * @return The {@link java.io.File} containing this particular hook if it + * exists in the given repository, null otherwise. * @since 4.0 */ public File findHook(Repository repository, final String hookName) { @@ -871,51 +1124,91 @@ * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * - * @param hookProcessBuilder - * The process builder configured for this hook. + * @param processBuilder + * The process builder configured for this process. * @param outRedirect - * A print stream on which to redirect the hook's stdout. Can be - * null, in which case the hook's standard output - * will be lost. + * A OutputStream on which to redirect the processes stdout. Can + * be null, in which case the processes standard + * output will be lost. * @param errRedirect - * A print stream on which to redirect the hook's stderr. Can be - * null, in which case the hook's standard error - * will be lost. + * A OutputStream on which to redirect the processes stderr. Can + * be null, in which case the processes standard + * error will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. Can be * null. - * @return the exit value of this hook. - * @throws IOException - * if an I/O error occurs while executing this hook. - * @throws InterruptedException + * @return the exit value of this process. + * @throws java.io.IOException + * if an I/O error occurs while executing this process. + * @throws java.lang.InterruptedException * if the current thread is interrupted while waiting for the * process to end. - * @since 3.7 + * @since 4.2 */ - protected int runProcess(ProcessBuilder hookProcessBuilder, + public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws IOException, InterruptedException { + InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream( + stdinArgs.getBytes(Constants.CHARACTER_ENCODING)); + return runProcess(processBuilder, outRedirect, errRedirect, in); + } + + /** + * Runs the given process until termination, clearing its stdout and stderr + * streams on-the-fly. + * + * @param processBuilder + * The process builder configured for this process. + * @param outRedirect + * An OutputStream on which to redirect the processes stdout. Can + * be null, in which case the processes standard + * output will be lost. + * @param errRedirect + * An OutputStream on which to redirect the processes stderr. Can + * be null, in which case the processes standard + * error will be lost. + * @param inRedirect + * An InputStream from which to redirect the processes stdin. Can + * be null, in which case the process doesn't get + * any data over stdin. It is assumed that the whole InputStream + * will be consumed by the process. The method will close the + * inputstream after all bytes are read. + * @return the return code of this process. + * @throws java.io.IOException + * if an I/O error occurs while executing this process. + * @throws java.lang.InterruptedException + * if the current thread is interrupted while waiting for the + * process to end. + * @since 4.2 + */ + public int runProcess(ProcessBuilder processBuilder, + OutputStream outRedirect, OutputStream errRedirect, + InputStream inRedirect) throws IOException, + InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(2); Process process = null; // We'll record the first I/O exception that occurs, but keep on trying // to dispose of our open streams and file handles IOException ioException = null; try { - process = hookProcessBuilder.start(); - final Callable errorGobbler = new StreamGobbler( - process.getErrorStream(), errRedirect); - final Callable outputGobbler = new StreamGobbler( - process.getInputStream(), outRedirect); - executor.submit(errorGobbler); - executor.submit(outputGobbler); - if (stdinArgs != null) { - final PrintWriter stdinWriter = new PrintWriter( - process.getOutputStream()); - stdinWriter.print(stdinArgs); - stdinWriter.flush(); - // We are done with this hook's input. Explicitly close its - // stdin now to kick off any blocking read the hook might have. - stdinWriter.close(); + process = processBuilder.start(); + executor.execute( + new StreamGobbler(process.getErrorStream(), errRedirect)); + executor.execute( + new StreamGobbler(process.getInputStream(), outRedirect)); + OutputStream outputStream = process.getOutputStream(); + if (inRedirect != null) { + new StreamGobbler(inRedirect, outputStream).copy(); + } + try { + outputStream.close(); + } catch (IOException e) { + // When the process exits before consuming the input, the OutputStream + // is replaced with the null output stream. This null output stream + // throws IOException for all write calls. When StreamGobbler fails to + // flush the buffer because of this, this close call tries to flush it + // again. This causes another IOException. Since we ignore the + // IOException in StreamGobbler, we also ignore the exception here. } return process.waitFor(); } catch (IOException e) { @@ -935,6 +1228,9 @@ // A process doesn't clean its own resources even when destroyed // Explicitly try and close all three streams, preserving the // outer I/O exception if any. + if (inRedirect != null) { + inRedirect.close(); + } try { process.getErrorStream().close(); } catch (IOException e) { @@ -975,10 +1271,10 @@ pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being canceled - if (!pool.awaitTermination(5, TimeUnit.SECONDS)) + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) hasShutdown = false; } } catch (InterruptedException ie) { @@ -1005,6 +1301,28 @@ */ public abstract ProcessBuilder runInShell(String cmd, String[] args); + /** + * Execute a command defined by a {@link java.lang.ProcessBuilder}. + * + * @param pb + * The command to be executed + * @param in + * The standard input stream passed to the process + * @return The result of the executed command + * @throws java.lang.InterruptedException + * @throws java.io.IOException + * @since 4.2 + */ + public ExecutionResult execute(ProcessBuilder pb, InputStream in) + throws IOException, InterruptedException { + try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null); + TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, + 1024 * 1024)) { + int rc = runProcess(pb, stdout, stderr, in); + return new ExecutionResult(stdout, stderr, rc); + } + } + private static class Holder { final V value; @@ -1142,8 +1460,11 @@ } /** + * Get the file attributes we care for. + * * @param path - * @return the file attributes we care for + * a {@link java.io.File} object. + * @return the file attributes we care for. * @since 3.3 */ public Attributes getAttributes(File path) { @@ -1163,6 +1484,7 @@ * Normalize the unicode path to composed form. * * @param file + * a {@link java.io.File} object. * @return NFC-format File * @since 3.3 */ @@ -1174,6 +1496,7 @@ * Normalize the unicode path to composed form. * * @param name + * path name * @return NFC-format string * @since 3.3 */ @@ -1193,37 +1516,42 @@ * streams. *

    */ - private static class StreamGobbler implements Callable { - private final BufferedReader reader; + private static class StreamGobbler implements Runnable { + private InputStream in; - private final BufferedWriter writer; + private OutputStream out; public StreamGobbler(InputStream stream, OutputStream output) { - this.reader = new BufferedReader(new InputStreamReader(stream)); - if (output == null) - this.writer = null; - else - this.writer = new BufferedWriter(new OutputStreamWriter(output)); + this.in = stream; + this.out = output; } - public Void call() throws IOException { - boolean writeFailure = false; + @Override + public void run() { + try { + copy(); + } catch (IOException e) { + // Do nothing on read failure; leave streams open. + } + } - String line = null; - while ((line = reader.readLine()) != null) { - // Do not try to write again after a failure, but keep reading - // as long as possible to prevent the input stream from choking. - if (!writeFailure && writer != null) { + void copy() throws IOException { + boolean writeFailure = false; + byte buffer[] = new byte[4096]; + int readBytes; + while ((readBytes = in.read(buffer)) != -1) { + // Do not try to write again after a failure, but keep + // reading as long as possible to prevent the input stream + // from choking. + if (!writeFailure && out != null) { try { - writer.write(line); - writer.newLine(); - writer.flush(); + out.write(buffer, 0, readBytes); + out.flush(); } catch (IOException e) { writeFailure = true; } } } - return null; } } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,15 +50,27 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.UUID; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.CommandFailedException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Base FS for POSIX based systems @@ -66,10 +78,22 @@ * @since 3.0 */ public class FS_POSIX extends FS { + private final static Logger LOG = LoggerFactory.getLogger(FS_POSIX.class); + private static final int DEFAULT_UMASK = 0022; private volatile int umask = -1; - /** Default constructor. */ + private volatile boolean supportsUnixNLink = true; + + private volatile AtomicFileCreation supportsAtomicCreateNewFile = AtomicFileCreation.UNDEFINED; + + private enum AtomicFileCreation { + SUPPORTED, NOT_SUPPORTED, UNDEFINED + } + + /** + * Default constructor. + */ protected FS_POSIX() { } @@ -86,6 +110,38 @@ } } + private void determineAtomicFileCreationSupport() { + // @TODO: enhance SystemReader to support this without copying code + AtomicFileCreation ret = getAtomicFileCreationSupportOption( + SystemReader.getInstance().openUserConfig(null, this)); + if (ret == AtomicFileCreation.UNDEFINED + && StringUtils.isEmptyOrNull(SystemReader.getInstance() + .getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) { + ret = getAtomicFileCreationSupportOption( + SystemReader.getInstance().openSystemConfig(null, this)); + } + supportsAtomicCreateNewFile = ret; + } + + private AtomicFileCreation getAtomicFileCreationSupportOption( + FileBasedConfig config) { + try { + config.load(); + String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION, + null, + ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION); + if (value == null) { + return AtomicFileCreation.UNDEFINED; + } + return StringUtils.toBoolean(value) + ? AtomicFileCreation.SUPPORTED + : AtomicFileCreation.NOT_SUPPORTED; + } catch (IOException | ConfigInvalidException e) { + return AtomicFileCreation.SUPPORTED; + } + } + + /** {@inheritDoc} */ @Override public FS newInstance() { return new FS_POSIX(this); @@ -122,7 +178,7 @@ .defaultCharset().name()))) { if (p.waitFor() == 0) { String s = lineRead.readLine(); - if (s.matches("0?\\d{3}")) { //$NON-NLS-1$ + if (s != null && s.matches("0?\\d{3}")) { //$NON-NLS-1$ return Integer.parseInt(s, 8); } } @@ -133,6 +189,7 @@ } } + /** {@inheritDoc} */ @Override protected File discoverGitExe() { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ @@ -144,11 +201,18 @@ // On MacOSX, PATH is shorter when Eclipse is launched from the // Finder than from a terminal. Therefore try to launch bash as a // login shell and search using that. - String w = readPipe(userHome(), + String w; + try { + w = readPipe(userHome(), new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name()); - if (!StringUtils.isEmptyOrNull(w)) + } catch (CommandFailedException e) { + LOG.warn(e.getMessage()); + return null; + } + if (!StringUtils.isEmptyOrNull(w)) { gitExe = new File(w); + } } } } @@ -156,30 +220,34 @@ return gitExe; } + /** {@inheritDoc} */ @Override public boolean isCaseSensitive() { return !SystemReader.getInstance().isMacOS(); } + /** {@inheritDoc} */ @Override public boolean supportsExecute() { return true; } + /** {@inheritDoc} */ @Override public boolean canExecute(File f) { return FileUtils.canExecute(f); } + /** {@inheritDoc} */ @Override public boolean setExecute(File f, boolean canExecute) { if (!isFile(f)) return false; if (!canExecute) - return f.setExecutable(false); + return f.setExecutable(false, false); try { - Path path = f.toPath(); + Path path = FileUtils.toPath(f); Set pset = Files.getPosixFilePermissions(path); // owner (user) is always allowed to execute. @@ -211,9 +279,10 @@ } } + /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { - List argv = new ArrayList(4 + args.length); + List argv = new ArrayList<>(4 + args.length); argv.add("sh"); //$NON-NLS-1$ argv.add("-c"); //$NON-NLS-1$ argv.add(cmd + " \"$@\""); //$NON-NLS-1$ @@ -224,9 +293,7 @@ return proc; } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, PrintStream outRedirect, PrintStream errRedirect, @@ -235,48 +302,43 @@ errRedirect, stdinArgs); } + /** {@inheritDoc} */ @Override public boolean retryFailedLockFileCommit() { return false; } + /** {@inheritDoc} */ @Override public boolean supportsSymlinks() { return true; } + /** {@inheritDoc} */ @Override public void setHidden(File path, boolean hidden) throws IOException { // no action on POSIX } - /** - * @since 3.3 - */ + /** {@inheritDoc} */ @Override public Attributes getAttributes(File path) { return FileUtils.getFileAttributesPosix(this, path); } - /** - * @since 3.3 - */ + /** {@inheritDoc} */ @Override public File normalize(File file) { return FileUtils.normalize(file); } - /** - * @since 3.3 - */ + /** {@inheritDoc} */ @Override public String normalize(String name) { return FileUtils.normalize(name); } - /** - * @since 3.7 - */ + /** {@inheritDoc} */ @Override public File findHook(Repository repository, String hookName) { final File gitdir = repository.getDirectory(); @@ -289,4 +351,134 @@ return hookPath.toFile(); return null; } + + /** {@inheritDoc} */ + @Override + public boolean supportsAtomicCreateNewFile() { + if (supportsAtomicCreateNewFile == AtomicFileCreation.UNDEFINED) { + determineAtomicFileCreationSupport(); + } + return supportsAtomicCreateNewFile == AtomicFileCreation.SUPPORTED; + } + + @Override + @SuppressWarnings("boxing") + /** + * {@inheritDoc} + *

    + * An implementation of the File#createNewFile() semantics which works also + * on NFS. If the config option + * {@code core.supportsAtomicCreateNewFile = true} (which is the default) + * then simply File#createNewFile() is called. + * + * But if {@code core.supportsAtomicCreateNewFile = false} then after + * successful creation of the lock file a hard link to that lock file is + * created and the attribute nlink of the lock file is checked to be 2. If + * multiple clients manage to create the same lock file nlink would be + * greater than 2 showing the error. + * + * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html" + * + * @deprecated use {@link FS_POSIX#createNewFileAtomic(File)} instead + * @since 4.5 + */ + @Deprecated + public boolean createNewFile(File lock) throws IOException { + if (!lock.createNewFile()) { + return false; + } + if (supportsAtomicCreateNewFile() || !supportsUnixNLink) { + return true; + } + Path lockPath = lock.toPath(); + Path link = null; + try { + link = Files.createLink( + Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$ + lockPath); + Integer nlink = (Integer) (Files.getAttribute(lockPath, + "unix:nlink")); //$NON-NLS-1$ + if (nlink > 2) { + LOG.warn("nlink of link to lock file {0} was not 2 but {1}", //$NON-NLS-1$ + lock.getPath(), nlink); + return false; + } else if (nlink < 2) { + supportsUnixNLink = false; + } + return true; + } catch (UnsupportedOperationException | IllegalArgumentException e) { + supportsUnixNLink = false; + return true; + } finally { + if (link != null) { + Files.delete(link); + } + } + } + + /** + * {@inheritDoc} + *

    + * An implementation of the File#createNewFile() semantics which can create + * a unique file atomically also on NFS. If the config option + * {@code core.supportsAtomicCreateNewFile = true} (which is the default) + * then simply File#createNewFile() is called. + * + * But if {@code core.supportsAtomicCreateNewFile = false} then after + * successful creation of the lock file a hard link to that lock file is + * created and the attribute nlink of the lock file is checked to be 2. If + * multiple clients manage to create the same lock file nlink would be + * greater than 2 showing the error. The hard link needs to be retained + * until the corresponding file is no longer needed in order to prevent that + * another process can create the same file concurrently using another NFS + * client which might not yet see the file due to caching. + * + * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html" + * @param file + * the unique file to be created atomically + * @return LockToken this lock token must be held until the file is no + * longer needed + * @throws IOException + * @since 5.0 + */ + @Override + public LockToken createNewFileAtomic(File file) throws IOException { + if (!file.createNewFile()) { + return token(false, null); + } + if (supportsAtomicCreateNewFile() || !supportsUnixNLink) { + return token(true, null); + } + Path link = null; + Path path = file.toPath(); + try { + link = Files.createLink(Paths.get(uniqueLinkPath(file)), path); + Integer nlink = (Integer) (Files.getAttribute(path, + "unix:nlink")); //$NON-NLS-1$ + if (nlink.intValue() > 2) { + LOG.warn(MessageFormat.format( + JGitText.get().failedAtomicFileCreation, path, nlink)); + return token(false, link); + } else if (nlink.intValue() < 2) { + supportsUnixNLink = false; + } + return token(true, link); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + supportsUnixNLink = false; + return token(true, link); + } + } + + private static LockToken token(boolean created, @Nullable Path p) { + return ((p != null) && Files.exists(p)) + ? new LockToken(created, Optional.of(p)) + : new LockToken(created, Optional.empty()); + } + + private static String uniqueLinkPath(File file) { + UUID id = UUID.randomUUID(); + return file.getAbsolutePath() + "." //$NON-NLS-1$ + + Long.toHexString(id.getMostSignificantBits()) + + Long.toHexString(id.getLeastSignificantBits()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,8 +54,11 @@ import java.util.List; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * FS implementation for Cygwin on Windows @@ -63,14 +66,20 @@ * @since 3.0 */ public class FS_Win32_Cygwin extends FS_Win32 { + private final static Logger LOG = LoggerFactory + .getLogger(FS_Win32_Cygwin.class); + private static String cygpath; /** + * Whether cygwin is found + * * @return true if cygwin is found */ public static boolean isCygwin() { final String path = AccessController .doPrivileged(new PrivilegedAction() { + @Override public String run() { return System.getProperty("java.library.path"); //$NON-NLS-1$ } @@ -100,26 +109,39 @@ super(src); } + /** {@inheritDoc} */ + @Override public FS newInstance() { return new FS_Win32_Cygwin(this); } + /** {@inheritDoc} */ + @Override public File resolve(final File dir, final String pn) { String useCygPath = System.getProperty("jgit.usecygpath"); //$NON-NLS-1$ if (useCygPath != null && useCygPath.equals("true")) { //$NON-NLS-1$ - String w = readPipe(dir, // + String w; + try { + w = readPipe(dir, // new String[] { cygpath, "--windows", "--absolute", pn }, // //$NON-NLS-1$ //$NON-NLS-2$ "UTF-8"); //$NON-NLS-1$ - if (w != null) + } catch (CommandFailedException e) { + LOG.warn(e.getMessage()); + return null; + } + if (!StringUtils.isEmptyOrNull(w)) { return new File(w); + } } return super.resolve(dir, pn); } + /** {@inheritDoc} */ @Override protected File userHomeImpl() { final String home = AccessController .doPrivileged(new PrivilegedAction() { + @Override public String run() { return System.getenv("HOME"); //$NON-NLS-1$ } @@ -129,9 +151,10 @@ return resolve(new File("."), home); //$NON-NLS-1$ } + /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { - List argv = new ArrayList(4 + args.length); + List argv = new ArrayList<>(4 + args.length); argv.add("sh.exe"); //$NON-NLS-1$ argv.add("-c"); //$NON-NLS-1$ argv.add(cmd + " \"$@\""); //$NON-NLS-1$ @@ -142,18 +165,14 @@ return proc; } - /** - * @since 3.7 - */ + /** {@inheritDoc} */ @Override public String relativize(String base, String other) { final String relativized = super.relativize(base, other); return relativized.replace(File.separatorChar, '/'); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, PrintStream outRedirect, PrintStream errRedirect, @@ -162,17 +181,13 @@ errRedirect, stdinArgs); } - @Override - public boolean supportsSymlinks() { - return true; - } - - /** - * @since 3.7 - */ + /** {@inheritDoc} */ @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 -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,10 @@ import java.util.Arrays; import java.util.List; +import org.eclipse.jgit.errors.CommandFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * FS implementation for Windows @@ -58,6 +62,7 @@ * @since 3.0 */ public class FS_Win32 extends FS { + private final static Logger LOG = LoggerFactory.getLogger(FS_Win32.class); private volatile Boolean supportSymlinks; @@ -78,32 +83,43 @@ super(src); } + /** {@inheritDoc} */ + @Override public FS newInstance() { return new FS_Win32(this); } + /** {@inheritDoc} */ + @Override public boolean supportsExecute() { return false; } + /** {@inheritDoc} */ + @Override public boolean canExecute(final File f) { return false; } + /** {@inheritDoc} */ + @Override public boolean setExecute(final File f, final boolean canExec) { return false; } + /** {@inheritDoc} */ @Override public boolean isCaseSensitive() { return false; } + /** {@inheritDoc} */ @Override public boolean retryFailedLockFileCommit() { return true; } + /** {@inheritDoc} */ @Override protected File discoverGitExe() { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ @@ -113,18 +129,26 @@ if (searchPath(path, "bash.exe") != null) { //$NON-NLS-1$ // This isn't likely to work, but its worth trying: // If bash is in $PATH, git should also be in $PATH. - String w = readPipe(userHome(), + String w; + try { + w = readPipe(userHome(), new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name()); - if (!StringUtils.isEmptyOrNull(w)) + } catch (CommandFailedException e) { + LOG.warn(e.getMessage()); + return null; + } + if (!StringUtils.isEmptyOrNull(w)) { // The path may be in cygwin/msys notation so resolve it right away gitExe = resolve(null, w); + } } } return gitExe; } + /** {@inheritDoc} */ @Override protected File userHomeImpl() { String home = SystemReader.getInstance().getenv("HOME"); //$NON-NLS-1$ @@ -144,9 +168,10 @@ return super.userHomeImpl(); } + /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { - List argv = new ArrayList(3 + args.length); + List argv = new ArrayList<>(3 + args.length); argv.add("cmd.exe"); //$NON-NLS-1$ argv.add("/c"); //$NON-NLS-1$ argv.add(cmd); @@ -156,6 +181,7 @@ return proc; } + /** {@inheritDoc} */ @Override public boolean supportsSymlinks() { if (supportSymlinks == null) @@ -171,7 +197,8 @@ createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); - } catch (IOException | UnsupportedOperationException e) { + } catch (IOException | UnsupportedOperationException + | InternalError e) { supportSymlinks = Boolean.FALSE; } finally { if (tempFile != null) @@ -183,9 +210,7 @@ } } - /** - * @since 3.3 - */ + /** {@inheritDoc} */ @Override public Attributes getAttributes(File path) { return FileUtils.getFileAttributesBasic(this, path); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,7 +54,8 @@ * A utility for formatting dates according to the Git log.date formats plus * extensions. *

    - * The enum {@link Format} defines the available types. + * The enum {@link org.eclipse.jgit.util.GitDateFormatter.Format} defines the + * available types. */ public class GitDateFormatter { @@ -119,6 +120,8 @@ * Create a new Git oriented date formatter * * @param format + * a {@link org.eclipse.jgit.util.GitDateFormatter.Format} + * object. */ public GitDateFormatter(Format format) { this.format = format; @@ -159,6 +162,7 @@ * specification. * * @param ident + * a {@link org.eclipse.jgit.lib.PersonIdent} object. * @return formatted version of date, time and time zone */ @SuppressWarnings("boxing") diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,7 +55,7 @@ import org.eclipse.jgit.internal.JGitText; /** - * Parses strings with time and date specifications into {@link Date}. + * Parses strings with time and date specifications into {@link java.util.Date}. * * When git needs to parse strings specified by the user this parser can be * used. One example is the parsing of the config parameter gc.pruneexpire. The @@ -75,8 +75,9 @@ private static ThreadLocal>> formatCache = new ThreadLocal>>() { + @Override protected Map> initialValue() { - return new HashMap>(); + return new HashMap<>(); } }; @@ -90,7 +91,7 @@ Map map = cache .get(locale); if (map == null) { - map = new HashMap(); + map = new HashMap<>(); cache.put(locale, map); return getNewSimpleDateFormat(f, locale, map); } @@ -133,16 +134,17 @@ } /** - * Parses a string into a {@link Date} using the default locale. Since this - * parser also supports relative formats (e.g. "yesterday") the caller can - * specify the reference date. These types of strings can be parsed: + * Parses a string into a {@link java.util.Date} using the default locale. + * Since this parser also supports relative formats (e.g. "yesterday") the + * caller can specify the reference date. These types of strings can be + * parsed: *

      *
    • "never"
    • *
    • "now"
    • *
    • "yesterday"
    • *
    • "(x) years|months|weeks|days|hours|minutes|seconds ago"
      - * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of - * ' ' one can use '.' to seperate the words
    • + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' + * ' one can use '.' to seperate the words *
    • "yyyy-MM-dd HH:mm:ss Z" (ISO)
    • *
    • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
    • *
    • "yyyy-MM-dd"
    • @@ -160,11 +162,12 @@ * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this - * parser often but wants a consistent starting point for calls.
      + * parser often but wants a consistent starting point for + * calls.
      * If set to null then the current time will be used * instead. - * @return the parsed {@link Date} - * @throws ParseException + * @return the parsed {@link java.util.Date} + * @throws java.text.ParseException * if the given dateStr was not recognized */ public static Date parse(String dateStr, Calendar now) @@ -173,16 +176,17 @@ } /** - * Parses a string into a {@link Date} using the given locale. Since this - * parser also supports relative formats (e.g. "yesterday") the caller can - * specify the reference date. These types of strings can be parsed: + * Parses a string into a {@link java.util.Date} using the given locale. + * Since this parser also supports relative formats (e.g. "yesterday") the + * caller can specify the reference date. These types of strings can be + * parsed: *
        *
      • "never"
      • *
      • "now"
      • *
      • "yesterday"
      • *
      • "(x) years|months|weeks|days|hours|minutes|seconds ago"
        - * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of - * ' ' one can use '.' to seperate the words
      • + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' + * ' one can use '.' to seperate the words *
      • "yyyy-MM-dd HH:mm:ss Z" (ISO)
      • *
      • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
      • *
      • "yyyy-MM-dd"
      • @@ -200,13 +204,14 @@ * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this - * parser often but wants a consistent starting point for calls.
        + * parser often but wants a consistent starting point for + * calls.
        * If set to null then the current time will be used * instead. * @param locale * locale to be used to parse the date string - * @return the parsed {@link Date} - * @throws ParseException + * @return the parsed {@link java.util.Date} + * @throws java.text.ParseException * if the given dateStr was not recognized * @since 3.2 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Holder.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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; + +/** + * Holder of an object. + * + * @param + * the type of value held by this {@link org.eclipse.jgit.util.Holder} + * @since 4.3 + */ +public class Holder { + private T value; + + /** + *

        Constructor for Holder.

        + * + * @param value + * is the initial value that is {@link #set(Object)} + */ + public Holder(T value) { + set(value); + } + + /** + * Get the value held by this {@link org.eclipse.jgit.util.Holder} + * + * @return the value held by this {@link org.eclipse.jgit.util.Holder} + */ + public T get() { + return value; + } + + /** + * Set a new value held by this {@link org.eclipse.jgit.util.Holder} + * + * @param value + * to be set as new value held by this + * {@link org.eclipse.jgit.util.Holder} + */ + public void set(T value) { + this.value = value; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,16 +52,34 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.text.MessageFormat; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.http.HttpConnection; -/** Extra utilities to support usage of HTTP. */ +/** + * Extra utilities to support usage of HTTP. + */ public class HttpSupport { /** The {@code GET} HTTP method. */ public static final String METHOD_GET = "GET"; //$NON-NLS-1$ + /** The {@code HEAD} HTTP method. + * @since 4.3 */ + public static final String METHOD_HEAD = "HEAD"; //$NON-NLS-1$ + + /** The {@code POST} HTTP method. + * @since 4.3 */ + public static final String METHOD_PUT = "PUT"; //$NON-NLS-1$ + /** The {@code POST} HTTP method. */ public static final String METHOD_POST = "POST"; //$NON-NLS-1$ @@ -125,9 +143,21 @@ /** The {@code Accept-Encoding} header. */ public static final String HDR_ACCEPT_ENCODING = "Accept-Encoding"; //$NON-NLS-1$ + /** + * The {@code Location} header. + * @since 4.7 + */ + public static final String HDR_LOCATION = "Location"; //$NON-NLS-1$ + /** The {@code gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. */ public static final String ENCODING_GZIP = "gzip"; //$NON-NLS-1$ + /** + * The {@code x-gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. + * @since 4.6 + */ + public static final String ENCODING_X_GZIP = "x-gzip"; //$NON-NLS-1$ + /** The standard {@code text/plain} MIME type. */ public static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$ @@ -164,8 +194,9 @@ * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See - * {@link HttpConnection} for other defined constants. - * @throws IOException + * {@link org.eclipse.jgit.transport.http.HttpConnection} for other + * defined constants. + * @throws java.io.IOException * communications error prevented obtaining the response code. * @since 3.3 */ @@ -173,7 +204,8 @@ try { return c.getResponseCode(); } catch (ConnectException ce) { - final String host = c.getURL().getHost(); + final URL url = c.getURL(); + final String host = (url == null) ? "" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ @@ -191,8 +223,9 @@ * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See - * {@link HttpConnection} for other defined constants. - * @throws IOException + * {@link org.eclipse.jgit.transport.http.HttpConnection} for other + * defined constants. + * @throws java.io.IOException * communications error prevented obtaining the response code. */ public static int response(final java.net.HttpURLConnection c) @@ -200,7 +233,8 @@ try { return c.getResponseCode(); } catch (ConnectException ce) { - final String host = c.getURL().getHost(); + final URL url = c.getURL(); + final String host = (url == null) ? "" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ @@ -211,6 +245,23 @@ } /** + * Extract a HTTP header from the response. + * + * @param c + * connection the header should be obtained from. + * @param headerName + * the header name + * @return the header value + * @throws java.io.IOException + * communications error prevented obtaining the header. + * @since 4.7 + */ + public static String responseHeader(final HttpConnection c, + final String headerName) throws IOException { + return c.getHeaderField(headerName); + } + + /** * Determine the proxy server (if any) needed to obtain a URL. * * @param proxySelector @@ -218,7 +269,7 @@ * @param u * location of the server caller wants to talk to. * @return proxy to communicate with the supplied URL. - * @throws ConnectException + * @throws java.net.ConnectException * the proxy could not be computed as the supplied URL could not * be read. This failure should never occur. */ @@ -234,6 +285,56 @@ } } + /** + * Disable SSL and hostname verification for given HTTP connection + * + * @param conn + * a {@link org.eclipse.jgit.transport.http.HttpConnection} + * object. + * @throws java.io.IOException + * @since 4.3 + */ + public static void disableSslVerify(HttpConnection conn) + throws IOException { + final TrustManager[] trustAllCerts = new TrustManager[] { + new DummyX509TrustManager() }; + try { + conn.configure(null, trustAllCerts, null); + conn.setHostnameVerifier(new DummyHostnameVerifier()); + } catch (KeyManagementException e) { + throw new IOException(e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e.getMessage()); + } + } + + private static class DummyX509TrustManager implements X509TrustManager { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, + String authType) { + // no check + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, + String authType) { + // no check + } + } + + private static class DummyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + // always accept + return true; + } + } + private HttpSupport() { // Utility class only. } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,13 +44,17 @@ package org.eclipse.jgit.util; -/** A more efficient List<Integer> using a primitive integer array. */ +/** + * A more efficient List<Integer> using a primitive integer array. + */ public class IntList { private int[] entries; private int count; - /** Create an empty list with a default capacity. */ + /** + * Create an empty list with a default capacity. + */ public IntList() { this(10); } @@ -65,16 +69,37 @@ entries = new int[capacity]; } - /** @return number of entries in this list */ + /** + * Get number of entries in this list. + * + * @return number of entries in this list. + */ public int size() { return count; } /** + * Check if an entry appears in this collection. + * + * @param value + * the value to search for. + * @return true of {@code value} appears in this list. + * @since 4.9 + */ + public boolean contains(int value) { + for (int i = 0; i < count; i++) + if (entries[i] == value) + return true; + return false; + } + + /** + * Get the value at the specified index + * * @param i * index to read, must be in the range [0, {@link #size()}). * @return the number at the specified index - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * the index outside the valid range */ public int get(final int i) { @@ -83,7 +108,9 @@ return entries[i]; } - /** Empty this list */ + /** + * Empty this list + */ public void clear() { count = 0; } @@ -138,6 +165,8 @@ entries = n; } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append('['); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ import org.eclipse.jgit.diff.RawText; /** - * An OutputStream that expands LF to CRLF. + * An InputStream that expands LF to CRLF. * * Existing CRLF are not expanded to CRCRLF, but retained as is. * @@ -91,12 +91,14 @@ this.detectBinary = detectBinary; } + /** {@inheritDoc} */ @Override public int read() throws IOException { final int read = read(single, 0, 1); return read == 1 ? single[0] & 0xff : -1; } + /** {@inheritDoc} */ @Override public int read(byte[] bs, final int off, final int len) throws IOException { if (len == 0) @@ -135,6 +137,7 @@ return n; } + /** {@inheritDoc} */ @Override public void close() throws IOException { in.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,8 +50,11 @@ /** * An OutputStream that expands LF to CRLF. - *

        + * * Existing CRLF are not expanded to CRCRLF, but retained as is. + * + * A binary check on the first 8000 bytes is performed and in case of binary + * files, canonicalization is turned off (for the complete file). */ public class AutoCRLFOutputStream extends OutputStream { @@ -67,21 +70,40 @@ private int binbufcnt = 0; + private boolean detectBinary; + private boolean isBinary; /** - * @param out + *

        Constructor for AutoCRLFOutputStream.

        + * + * @param out a {@link java.io.OutputStream} object. */ public AutoCRLFOutputStream(OutputStream out) { + this(out, true); + } + + /** + *

        Constructor for AutoCRLFOutputStream.

        + * + * @param out a {@link java.io.OutputStream} object. + * @param detectBinary + * whether binaries should be detected + * @since 4.3 + */ + public AutoCRLFOutputStream(OutputStream out, boolean detectBinary) { this.out = out; + this.detectBinary = detectBinary; } + /** {@inheritDoc} */ @Override public void write(int b) throws IOException { onebytebuf[0] = (byte) b; write(onebytebuf, 0, 1); } + /** {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { int overflow = buffer(b, 0, b.length); @@ -89,6 +111,7 @@ write(b, b.length - overflow, overflow); } + /** {@inheritDoc} */ @Override public void write(byte[] b, final int startOff, final int startLen) throws IOException { @@ -141,12 +164,16 @@ } private void decideMode() throws IOException { - isBinary = RawText.isBinary(binbuf, binbufcnt); + if (detectBinary) { + isBinary = RawText.isBinary(binbuf, binbufcnt); + detectBinary = false; + } int cachedLen = binbufcnt; binbufcnt = binbuf.length + 1; // full! write(binbuf, 0, cachedLen); } + /** {@inheritDoc} */ @Override public void flush() throws IOException { if (binbufcnt <= binbuf.length) @@ -155,6 +182,7 @@ out.flush(); } + /** {@inheritDoc} */ @Override public void close() throws IOException { flush(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2010, 2013 Marc Strapetz + * Copyright (C) 2015, Ivan Motsch + * 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.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.diff.RawText; + +/** + * An InputStream that normalizes CRLF to LF. + * + * Existing single CR are not changed to LF, but retained as is. + * + * Optionally, a binary check on the first 8000 bytes is performed and in case + * of binary files, canonicalization is turned off (for the complete file). + *

        + * This is the former EolCanonicalizingInputStream with a new name in order to + * have same naming for all LF / CRLF streams + * + * @since 4.3 + */ +public class AutoLFInputStream extends InputStream { + private final byte[] single = new byte[1]; + + private final byte[] buf = new byte[8096]; + + private final InputStream in; + + private int cnt; + + private int ptr; + + private boolean isBinary; + + private boolean detectBinary; + + private boolean abortIfBinary; + + /** + * A special exception thrown when {@link AutoLFInputStream} is told to + * throw an exception when attempting to read a binary file. The exception + * may be thrown at any stage during reading. + * + * @since 3.3 + */ + public static class IsBinaryException extends IOException { + private static final long serialVersionUID = 1L; + + IsBinaryException() { + super(); + } + } + + /** + * Creates a new InputStream, wrapping the specified stream + * + * @param in + * raw input stream + * @param detectBinary + * whether binaries should be detected + * @since 2.0 + */ + public AutoLFInputStream(InputStream in, boolean detectBinary) { + this(in, detectBinary, false); + } + + /** + * Creates a new InputStream, wrapping the specified stream + * + * @param in + * raw input stream + * @param detectBinary + * whether binaries should be detected + * @param abortIfBinary + * throw an IOException if the file is binary + * @since 3.3 + */ + public AutoLFInputStream(InputStream in, boolean detectBinary, + boolean abortIfBinary) { + this.in = in; + this.detectBinary = detectBinary; + this.abortIfBinary = abortIfBinary; + } + + /** {@inheritDoc} */ + @Override + public int read() throws IOException { + final int read = read(single, 0, 1); + return read == 1 ? single[0] & 0xff : -1; + } + + /** {@inheritDoc} */ + @Override + public int read(byte[] bs, final int off, final int len) + throws IOException { + if (len == 0) + return 0; + + if (cnt == -1) + return -1; + + int i = off; + final int end = off + len; + + while (i < end) { + if (ptr == cnt && !fillBuffer()) { + break; + } + + byte b = buf[ptr++]; + if (isBinary || b != '\r') { + // Logic for binary files ends here + bs[i++] = b; + continue; + } + + if (ptr == cnt && !fillBuffer()) { + bs[i++] = '\r'; + break; + } + + if (buf[ptr] == '\n') { + bs[i++] = '\n'; + ptr++; + } else + bs[i++] = '\r'; + } + + return i == off ? -1 : i - off; + } + + /** + * Whether the stream has detected as a binary so far. + * + * @return true if the stream has detected as a binary so far. + * @since 3.3 + */ + public boolean isBinary() { + return isBinary; + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + in.close(); + } + + private boolean fillBuffer() throws IOException { + cnt = in.read(buf, 0, buf.length); + if (cnt < 1) + return false; + if (detectBinary) { + isBinary = RawText.isBinary(buf, cnt); + detectBinary = false; + if (isBinary && abortIfBinary) + throw new IsBinaryException(); + } + ptr = 0; + return true; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.diff.RawText; + +/** + * An OutputStream that reduces CRLF to LF. + * + * Existing single CR are not changed to LF, but retained as is. + * + * A binary check on the first 8000 bytes is performed and in case of binary + * files, canonicalization is turned off (for the complete file). + * + * @since 4.3 + */ +public class AutoLFOutputStream extends OutputStream { + + static final int BUFFER_SIZE = 8000; + + private final OutputStream out; + + private int buf = -1; + + private byte[] binbuf = new byte[BUFFER_SIZE]; + + private byte[] onebytebuf = new byte[1]; + + private int binbufcnt = 0; + + private boolean detectBinary; + + private boolean isBinary; + + /** + *

        + * Constructor for AutoLFOutputStream. + *

        + * + * @param out + * an {@link java.io.OutputStream} object. + */ + public AutoLFOutputStream(OutputStream out) { + this(out, true); + } + + /** + *

        + * Constructor for AutoLFOutputStream. + *

        + * + * @param out + * an {@link java.io.OutputStream} object. + * @param detectBinary + * whether binaries should be detected + */ + public AutoLFOutputStream(OutputStream out, boolean detectBinary) { + this.out = out; + this.detectBinary = detectBinary; + } + + /** {@inheritDoc} */ + @Override + public void write(int b) throws IOException { + onebytebuf[0] = (byte) b; + write(onebytebuf, 0, 1); + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] b) throws IOException { + int overflow = buffer(b, 0, b.length); + if (overflow > 0) { + write(b, b.length - overflow, overflow); + } + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] b, final int startOff, final int startLen) + throws IOException { + final int overflow = buffer(b, startOff, startLen); + if (overflow < 0) { + return; + } + final int off = startOff + startLen - overflow; + final int len = overflow; + if (len == 0) { + return; + } + int lastw = off; + if (isBinary) { + out.write(b, off, len); + return; + } + for (int i = off; i < off + len; ++i) { + final byte c = b[i]; + if (c == '\r') { + // skip write r but backlog r + if (lastw < i) { + out.write(b, lastw, i - lastw); + } + lastw = i + 1; + buf = '\r'; + } else if (c == '\n') { + if (buf == '\r') { + out.write('\n'); + lastw = i + 1; + buf = -1; + } else { + if (lastw < i + 1) { + out.write(b, lastw, i + 1 - lastw); + } + lastw = i + 1; + } + } else { + if (buf == '\r') { + out.write('\r'); + lastw = i; + } + buf = -1; + } + } + if (lastw < off + len) { + out.write(b, lastw, off + len - lastw); + } + } + + private int buffer(byte[] b, int off, int len) throws IOException { + if (binbufcnt > binbuf.length) { + return len; + } + int copy = Math.min(binbuf.length - binbufcnt, len); + System.arraycopy(b, off, binbuf, binbufcnt, copy); + binbufcnt += copy; + int remaining = len - copy; + if (remaining > 0) { + decideMode(); + } + return remaining; + } + + private void decideMode() throws IOException { + if (detectBinary) { + isBinary = RawText.isBinary(binbuf, binbufcnt); + detectBinary = false; + } + int cachedLen = binbufcnt; + binbufcnt = binbuf.length + 1; // full! + write(binbuf, 0, cachedLen); + } + + /** {@inheritDoc} */ + @Override + public void flush() throws IOException { + if (binbufcnt <= binbuf.length) { + decideMode(); + } + out.flush(); + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + flush(); + if (buf == '\r') { + out.write(buf); + buf = -1; + } + out.close(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import java.io.IOException; import java.io.OutputStream; -/** Counts the number of bytes written. */ +/** + * Counts the number of bytes written. + */ public class CountingOutputStream extends OutputStream { private final OutputStream out; private long cnt; @@ -61,28 +63,36 @@ this.out = out; } - /** @return current number of bytes written. */ + /** + * Get current number of bytes written. + * + * @return current number of bytes written. + */ public long getCount() { return cnt; } + /** {@inheritDoc} */ @Override public void write(int val) throws IOException { out.write(val); cnt++; } + /** {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) throws IOException { out.write(buf, off, len); cnt += len; } + /** {@inheritDoc} */ @Override public void flush() throws IOException { out.flush(); } + /** {@inheritDoc} */ @Override public void close() throws IOException { out.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,9 @@ import org.eclipse.jgit.internal.JGitText; -/** An OutputStream which always throws IllegalStateExeption during write. */ +/** + * An OutputStream which always throws IllegalStateExeption during write. + */ public final class DisabledOutputStream extends OutputStream { /** The canonical instance which always throws IllegalStateException. */ public static final DisabledOutputStream INSTANCE = new DisabledOutputStream(); @@ -58,6 +60,7 @@ // more than one instance from being created. } + /** {@inheritDoc} */ @Override public void write(int b) throws IOException { // We shouldn't be writing output at this stage, there diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolCanonicalizingInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,46 +46,16 @@ import java.io.IOException; import java.io.InputStream; -import org.eclipse.jgit.diff.RawText; - /** * An input stream which canonicalizes EOLs bytes on the fly to '\n'. * - * Optionally, a binary check on the first 8000 bytes is performed - * and in case of binary files, canonicalization is turned off - * (for the complete file). + * Optionally, a binary check on the first 8000 bytes is performed and in case + * of binary files, canonicalization is turned off (for the complete file). + * + * @deprecated use {@link org.eclipse.jgit.util.io.AutoLFInputStream} instead */ -public class EolCanonicalizingInputStream extends InputStream { - private final byte[] single = new byte[1]; - - private final byte[] buf = new byte[8096]; - - private final InputStream in; - - private int cnt; - - private int ptr; - - private boolean isBinary; - - private boolean detectBinary; - - private boolean abortIfBinary; - - /** - * A special exception thrown when {@link EolCanonicalizingInputStream} is - * told to throw an exception when attempting to read a binary file. The - * exception may be thrown at any stage during reading. - * - * @since 3.3 - */ - public static class IsBinaryException extends IOException { - private static final long serialVersionUID = 1L; - - IsBinaryException() { - super(); - } - } +@Deprecated +public class EolCanonicalizingInputStream extends AutoLFInputStream { /** * Creates a new InputStream, wrapping the specified stream @@ -94,10 +64,9 @@ * raw input stream * @param detectBinary * whether binaries should be detected - * @since 2.0 */ public EolCanonicalizingInputStream(InputStream in, boolean detectBinary) { - this(in, detectBinary, false); + super(in, detectBinary); } /** @@ -109,83 +78,25 @@ * whether binaries should be detected * @param abortIfBinary * throw an IOException if the file is binary - * @since 3.3 */ public EolCanonicalizingInputStream(InputStream in, boolean detectBinary, boolean abortIfBinary) { - this.in = in; - this.detectBinary = detectBinary; - this.abortIfBinary = abortIfBinary; - } - - @Override - public int read() throws IOException { - final int read = read(single, 0, 1); - return read == 1 ? single[0] & 0xff : -1; - } - - @Override - public int read(byte[] bs, final int off, final int len) throws IOException { - if (len == 0) - return 0; - - if (cnt == -1) - return -1; - - int i = off; - final int end = off + len; - - while (i < end) { - if (ptr == cnt && !fillBuffer()) { - break; - } - - byte b = buf[ptr++]; - if (isBinary || b != '\r') { - // Logic for binary files ends here - bs[i++] = b; - continue; - } - - if (ptr == cnt && !fillBuffer()) { - bs[i++] = '\r'; - break; - } - - if (buf[ptr] == '\n') { - bs[i++] = '\n'; - ptr++; - } else - bs[i++] = '\r'; - } - - return i == off ? -1 : i - off; + super(in, detectBinary, abortIfBinary); } /** - * @return true if the stream has detected as a binary so far + * A special exception thrown when {@link AutoLFInputStream} is told to + * throw an exception when attempting to read a binary file. The exception + * may be thrown at any stage during reading. + * * @since 3.3 */ - public boolean isBinary() { - return isBinary; - } - - @Override - public void close() throws IOException { - in.close(); - } + public static class IsBinaryException extends IOException { + private static final long serialVersionUID = 1L; - private boolean fillBuffer() throws IOException { - cnt = in.read(buf, 0, buf.length); - if (cnt < 1) - return false; - if (detectBinary) { - isBinary = RawText.isBinary(buf, cnt); - detectBinary = false; - if (isBinary && abortIfBinary) - throw new IsBinaryException(); + IsBinaryException() { + super(); } - ptr = 0; - return true; } + } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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.io; + +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; + +/** + * Utility used to create input and output stream wrappers for + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * + * @since 4.3 + */ +public final class EolStreamTypeUtil { + private static final boolean FORCE_EOL_LF_ON_CHECKOUT = false; + + private EolStreamTypeUtil() { + } + + /** + * Convenience method used to detect if CRLF conversion has been configured + * using the + *
          + *
        • global repo options
        • + *
        • global attributes
        • + *
        • info attributes
        • + *
        • working tree .gitattributes
        • + *
        + * + * @param op + * is the + * {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} of + * the current traversal + * @param options + * are the {@link org.eclipse.jgit.lib.Config} options with key + * {@link org.eclipse.jgit.treewalk.WorkingTreeOptions#KEY} + * @param attrs + * are the {@link org.eclipse.jgit.attributes.Attributes} of the + * file for which the + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} is to be + * detected + * @return the stream conversion + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} to be + * performed for the selected + * {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} + */ + public static EolStreamType detectStreamType(OperationType op, + WorkingTreeOptions options, Attributes attrs) { + switch (op) { + case CHECKIN_OP: + return checkInStreamType(options, attrs); + case CHECKOUT_OP: + return checkOutStreamType(options, attrs); + default: + throw new IllegalArgumentException("unknown OperationType " + op); //$NON-NLS-1$ + } + } + + /** + * Wrap the input stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * + * @param in + * original stream + * @param conversion + * to be performed + * @return the converted stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + */ + public static InputStream wrapInputStream(InputStream in, + EolStreamType conversion) { + switch (conversion) { + case TEXT_CRLF: + return new AutoCRLFInputStream(in, false); + case TEXT_LF: + return new AutoLFInputStream(in, false); + case AUTO_CRLF: + return new AutoCRLFInputStream(in, true); + case AUTO_LF: + return new AutoLFInputStream(in, true); + default: + return in; + } + } + + /** + * Wrap the output stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * + * @param out + * original stream + * @param conversion + * to be performed + * @return the converted stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + */ + public static OutputStream wrapOutputStream(OutputStream out, + EolStreamType conversion) { + switch (conversion) { + case TEXT_CRLF: + return new AutoCRLFOutputStream(out, false); + case AUTO_CRLF: + return new AutoCRLFOutputStream(out, true); + case TEXT_LF: + return new AutoLFOutputStream(out, false); + case AUTO_LF: + return new AutoLFOutputStream(out, true); + default: + return out; + } + } + + private static EolStreamType checkInStreamType(WorkingTreeOptions options, + Attributes attrs) { + if (attrs.isUnset("text")) {//$NON-NLS-1$ + // "binary" or "-text" (which is included in the binary expansion) + return EolStreamType.DIRECT; + } + + // old git system + if (attrs.isSet("crlf")) {//$NON-NLS-1$ + return EolStreamType.TEXT_LF; + } else if (attrs.isUnset("crlf")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.TEXT_LF; + } + + // new git system + String eol = attrs.getValue("eol"); //$NON-NLS-1$ + if (eol != null) + // check-in is always normalized to LF + return EolStreamType.TEXT_LF; + + if (attrs.isSet("text")) { //$NON-NLS-1$ + return EolStreamType.TEXT_LF; + } + + if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.AUTO_LF; + } + + switch (options.getAutoCRLF()) { + case TRUE: + case INPUT: + return EolStreamType.AUTO_LF; + case FALSE: + return EolStreamType.DIRECT; + } + + return EolStreamType.DIRECT; + } + + private static EolStreamType checkOutStreamType(WorkingTreeOptions options, + Attributes attrs) { + if (attrs.isUnset("text")) {//$NON-NLS-1$ + // "binary" or "-text" (which is included in the binary expansion) + return EolStreamType.DIRECT; + } + + // old git system + if (attrs.isSet("crlf")) {//$NON-NLS-1$ + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + } else if (attrs.isUnset("crlf")) {//$NON-NLS-1$ + return EolStreamType.DIRECT; + } else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$ + return EolStreamType.DIRECT; + } + + // new git system + String eol = attrs.getValue("eol"); //$NON-NLS-1$ + if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$ + return EolStreamType.TEXT_CRLF; + if (eol != null && "lf".equals(eol)) //$NON-NLS-1$ + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + + if (attrs.isSet("text")) { //$NON-NLS-1$ + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.TEXT_CRLF; + default: + // no decision + } + switch (options.getEOL()) { + case CRLF: + return EolStreamType.TEXT_CRLF; + case LF: + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + case NATIVE: + default: + return EolStreamType.DIRECT; + } + } + + if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$ + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.AUTO_CRLF; + default: + // no decision + } + switch (options.getEOL()) { + case CRLF: + return EolStreamType.AUTO_CRLF; + case LF: + return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF + : EolStreamType.DIRECT; + case NATIVE: + default: + return EolStreamType.DIRECT; + } + } + + switch (options.getAutoCRLF()) { + case TRUE: + return EolStreamType.AUTO_CRLF; + default: + // no decision + } + + return EolStreamType.DIRECT; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java 2019-09-03 12:37:49.000000000 +0000 @@ -88,7 +88,9 @@ final AutoKiller autoKiller; - /** Create a new timer with a default thread name. */ + /** + * Create a new timer with a default thread name. + */ public InterruptTimer() { this("JGit-InterruptTimer"); //$NON-NLS-1$ } @@ -123,12 +125,16 @@ state.begin(timeout); } - /** Disable the interrupt timer, as the operation is complete. */ + /** + * Disable the interrupt timer, as the operation is complete. + */ public void end() { state.end(); } - /** Shutdown the timer thread, and wait for it to terminate. */ + /** + * Shutdown the timer thread, and wait for it to terminate. + */ public void terminate() { state.terminate(); try { @@ -176,6 +182,7 @@ callingThread = Thread.currentThread(); } + @Override public synchronized void run() { while (!terminated && callingThread.isAlive()) { try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016, 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.io; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jgit.internal.JGitText; + +/** + * OutputStream isolated from interrupts. + *

        + * Wraps an OutputStream to prevent interrupts during writes from being made + * visible to that stream instance. This works around buggy or difficult + * OutputStream implementations like JSch that cannot gracefully handle an + * interrupt during write. + *

        + * Every write (or flush) requires a context switch to another thread. Callers + * should wrap this stream with {@code BufferedOutputStream} using a suitable + * buffer size to amortize the cost of context switches. + * + * @since 4.6 + */ +public class IsolatedOutputStream extends OutputStream { + private final OutputStream dst; + private final ExecutorService copier; + private Future pending; + + /** + * Wraps an OutputStream. + * + * @param out + * stream to send all writes to. + */ + public IsolatedOutputStream(OutputStream out) { + dst = out; + copier = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue(1), new NamedThreadFactory()); + } + + /** {@inheritDoc} */ + @Override + public void write(int ch) throws IOException { + write(new byte[] { (byte) ch }, 0, 1); + } + + /** {@inheritDoc} */ + @Override + public void write(final byte[] buf, final int pos, final int cnt) + throws IOException { + checkClosed(); + execute(new Callable() { + @Override + public Void call() throws IOException { + dst.write(buf, pos, cnt); + return null; + } + }); + } + + /** {@inheritDoc} */ + @Override + public void flush() throws IOException { + checkClosed(); + execute(new Callable() { + @Override + public Void call() throws IOException { + dst.flush(); + return null; + } + }); + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + if (!copier.isShutdown()) { + try { + if (pending == null || tryCleanClose()) { + cleanClose(); + } else { + dirtyClose(); + } + } finally { + copier.shutdown(); + } + } + } + + private boolean tryCleanClose() { + /* + * If the caller stopped waiting for a prior write or flush, they could + * be trying to close a stream that is still in-use. Check if the prior + * operation ended in a predictable way. + */ + try { + pending.get(0, TimeUnit.MILLISECONDS); + pending = null; + return true; + } catch (TimeoutException | InterruptedException e) { + return false; + } catch (ExecutionException e) { + pending = null; + return true; + } + } + + private void cleanClose() throws IOException { + execute(new Callable() { + @Override + public Void call() throws IOException { + dst.close(); + return null; + } + }); + } + + private void dirtyClose() throws IOException { + /* + * Interrupt any still pending write or flush operation. This may cause + * massive failures inside of the stream, but its going to be closed as + * the next step. + */ + pending.cancel(true); + + Future close; + try { + close = copier.submit(new Callable() { + @Override + public Void call() throws IOException { + dst.close(); + return null; + } + }); + } catch (RejectedExecutionException e) { + throw new IOException(e); + } + try { + close.get(200, TimeUnit.MILLISECONDS); + } catch (InterruptedException | TimeoutException e) { + close.cancel(true); + throw new IOException(e); + } catch (ExecutionException e) { + throw new IOException(e.getCause()); + } + } + + private void checkClosed() throws IOException { + if (copier.isShutdown()) { + throw new IOException(JGitText.get().closed); + } + } + + private void execute(Callable task) throws IOException { + if (pending != null) { + // Check (and rethrow) any prior failed operation. + checkedGet(pending); + } + try { + pending = copier.submit(task); + } catch (RejectedExecutionException e) { + throw new IOException(e); + } + checkedGet(pending); + pending = null; + } + + private static void checkedGet(Future future) throws IOException { + try { + future.get(); + } catch (InterruptedException e) { + throw interrupted(e); + } catch (ExecutionException e) { + throw new IOException(e.getCause()); + } + } + + private static InterruptedIOException interrupted(InterruptedException c) { + InterruptedIOException e = new InterruptedIOException(); + e.initCause(c); + return e; + } + + private static class NamedThreadFactory implements ThreadFactory { + private static final AtomicInteger cnt = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + int n = cnt.incrementAndGet(); + String name = IsolatedOutputStream.class.getSimpleName() + '-' + n; + return new Thread(r, name); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,13 +51,13 @@ import org.eclipse.jgit.internal.JGitText; /** - * Wraps a {@link InputStream}, limiting the number of bytes which can be - * read. + * Wraps a {@link java.io.InputStream}, limiting the number of bytes which can + * be read. * - * This class was copied and modifed from the Google Guava 16.0. Differently from - * the original Guava code, when a caller tries to read from this stream past - * the given limit and the wrapped stream hasn't yet reached its EOF this class - * will call the limitExceeded method instead of returning EOF. + * This class was copied and modifed from the Google Guava 16.0. Differently + * from the original Guava code, when a caller tries to read from this stream + * past the given limit and the wrapped stream hasn't yet reached its EOF this + * class will call the limitExceeded method instead of returning EOF. * * @since 3.3 */ @@ -80,18 +80,21 @@ this.limit = limit; } + /** {@inheritDoc} */ @Override public int available() throws IOException { return (int) Math.min(in.available(), left); } // it's okay to mark even if mark isn't supported, as reset won't work + /** {@inheritDoc} */ @Override public synchronized void mark(int readLimit) { in.mark(readLimit); mark = left; } + /** {@inheritDoc} */ @Override public int read() throws IOException { if (left == 0) { @@ -107,6 +110,7 @@ return result; } + /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (left == 0) { @@ -123,6 +127,7 @@ return result; } + /** {@inheritDoc} */ @Override public synchronized void reset() throws IOException { if (!in.markSupported()) @@ -135,6 +140,7 @@ left = mark; } + /** {@inheritDoc} */ @Override public long skip(long n) throws IOException { n = Math.min(n, left); @@ -147,10 +153,11 @@ * Called when trying to read past the given {@link #limit} and the wrapped * InputStream {@link #in} hasn't yet reached its EOF * - * @throws IOException - * subclasses can throw an IOException when the limit is exceeded. - * The throws IOException will be forwarded back to the caller of - * the read method which read the stream past the limit. + * @throws java.io.IOException + * subclasses can throw an {@link java.io.IOException} when the + * limit is exceeded. The throws java.io.IOException will be + * forwarded back to the caller of the read method which read + * the stream past the limit. */ protected abstract void limitExceeded() throws IOException; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,20 +66,24 @@ * {@link #toString()} returns all written data, after converting it to a String * under the assumption of UTF-8 encoding. *

        - * Internally {@link RawParseUtils#decode(byte[])} is used by {@code toString()} - * tries to work out a reasonably correct character set for the raw data. + * Internally {@link org.eclipse.jgit.util.RawParseUtils#decode(byte[])} is used + * by {@code toString()} tries to work out a reasonably correct character set + * for the raw data. */ public class MessageWriter extends Writer { private final ByteArrayOutputStream buf; private final OutputStreamWriter enc; - /** Create an empty writer. */ + /** + * Create an empty writer. + */ public MessageWriter() { buf = new ByteArrayOutputStream(); enc = new OutputStreamWriter(getRawStream(), Constants.CHARSET); } + /** {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException { synchronized (buf) { @@ -89,6 +93,9 @@ } /** + * Get the underlying byte stream that character writes to this writer drop + * into. + * * @return the underlying byte stream that character writes to this writer * drop into. Writes to this stream should should be in UTF-8. */ @@ -96,17 +103,20 @@ return buf; } + /** {@inheritDoc} */ @Override public void close() throws IOException { // Do nothing, we are buffered with no resources. } + /** {@inheritDoc} */ @Override public void flush() throws IOException { // Do nothing, we are buffered with no resources. } /** @return string version of all buffered data. */ + /** {@inheritDoc} */ @Override public String toString() { return RawParseUtils.decode(buf.toByteArray()); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/NullOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/NullOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/NullOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/NullOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,16 +57,19 @@ // more than one instance from being created. } + /** {@inheritDoc} */ @Override public void write(int b) { // Discard. } + /** {@inheritDoc} */ @Override public void write(byte[] buf) { // Discard. } + /** {@inheritDoc} */ @Override public void write(byte[] buf, int pos, int cnt) { // Discard. diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SafeBufferedOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SafeBufferedOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SafeBufferedOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SafeBufferedOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,21 +43,18 @@ package org.eclipse.jgit.util.io; import java.io.BufferedOutputStream; -import java.io.IOException; import java.io.OutputStream; /** - * A BufferedOutputStream that throws an error if the final flush fails on - * close. - *

        - * Java's BufferedOutputStream swallows errors that occur when the output stream - * tries to write the final bytes to the output during close. This may result in - * corrupted files without notice. - *

        + *

        SafeBufferedOutputStream class.

        + * + * @deprecated use BufferedOutputStream in Java 8 and later. */ +@Deprecated public class SafeBufferedOutputStream extends BufferedOutputStream { - /** + *

        Constructor for SafeBufferedOutputStream.

        + * * @see BufferedOutputStream#BufferedOutputStream(OutputStream) * @param out * underlying output stream @@ -67,6 +64,8 @@ } /** + *

        Constructor for SafeBufferedOutputStream.

        + * * @see BufferedOutputStream#BufferedOutputStream(OutputStream, int) * @param out * underlying output stream @@ -76,13 +75,4 @@ public SafeBufferedOutputStream(OutputStream out, int size) { super(out, size); } - - @Override - public void close() throws IOException { - try { - flush(); - } finally { - super.close(); - } - } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,9 @@ import java.io.InterruptedIOException; import java.io.OutputStream; -/** Thread to copy from an input stream to an output stream. */ +/** + * Thread to copy from an input stream to an output stream. + */ public class StreamCopyThread extends Thread { private static final int BUFFER_SIZE = 1024; @@ -58,6 +60,9 @@ private volatile boolean done; + /** Lock held by flush to avoid interrupting a write. */ + private final Object writeLock; + /** * Create a thread to copy data from an input stream to an output stream. * @@ -72,6 +77,7 @@ setName(Thread.currentThread().getName() + "-StreamCopy"); //$NON-NLS-1$ src = i; dst = o; + writeLock = new Object(); } /** @@ -81,8 +87,11 @@ * happen at some future point in time, when the thread wakes up to process * the request. */ + @Deprecated public void flush() { - interrupt(); + synchronized (writeLock) { + interrupt(); + } } /** @@ -91,7 +100,7 @@ * This method signals to the copy thread that it should stop as soon as * there is no more IO occurring. * - * @throws InterruptedException + * @throws java.lang.InterruptedException * the calling thread was interrupted. */ public void halt() throws InterruptedException { @@ -105,16 +114,23 @@ } } + /** {@inheritDoc} */ @Override public void run() { try { final byte[] buf = new byte[BUFFER_SIZE]; - int interruptCounter = 0; + boolean readInterrupted = false; for (;;) { try { - if (interruptCounter > 0) { - dst.flush(); - interruptCounter--; + if (readInterrupted) { + synchronized (writeLock) { + boolean interruptedAgain = Thread.interrupted(); + dst.flush(); + if (interruptedAgain) { + interrupt(); + } + } + readInterrupted = false; } if (done) @@ -124,26 +140,18 @@ try { n = src.read(buf); } catch (InterruptedIOException wakey) { - interruptCounter++; + readInterrupted = true; continue; } if (n < 0) break; - boolean writeInterrupted = false; - for (;;) { - try { - dst.write(buf, 0, n); - } catch (InterruptedIOException wakey) { - writeInterrupted = true; - continue; - } - - // set interrupt status, which will be checked - // when we block in src.read - if (writeInterrupted) + synchronized (writeLock) { + boolean writeInterrupted = Thread.interrupted(); + dst.write(buf, 0, n); + if (writeInterrupted) { interrupt(); - break; + } } } catch (IOException e) { break; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,15 +47,13 @@ import java.io.InputStream; import java.io.OutputStream; -import org.eclipse.jgit.util.TemporaryBuffer; - /** * Input stream that copies data read to another output stream. * - * This stream is primarily useful with a {@link TemporaryBuffer}, where any - * data read or skipped by the caller is also duplicated into the temporary - * buffer. Later the temporary buffer can then be used instead of the original - * source stream. + * This stream is primarily useful with a + * {@link org.eclipse.jgit.util.TemporaryBuffer}, where any data read or skipped + * by the caller is also duplicated into the temporary buffer. Later the + * temporary buffer can then be used instead of the original source stream. * * During close this stream copies any remaining data from the source stream * into the destination stream. @@ -74,13 +72,14 @@ * source stream to consume. * @param dst * destination to copy the source to as it is consumed. Typically - * this is a {@link TemporaryBuffer}. + * this is a {@link org.eclipse.jgit.util.TemporaryBuffer}. */ public TeeInputStream(InputStream src, OutputStream dst) { this.src = src; this.dst = dst; } + /** {@inheritDoc} */ @Override public int read() throws IOException { byte[] b = skipBuffer(); @@ -88,6 +87,7 @@ return n == 1 ? b[0] & 0xff : -1; } + /** {@inheritDoc} */ @Override public long skip(final long count) throws IOException { long skipped = 0; @@ -104,6 +104,7 @@ return skipped; } + /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) @@ -115,6 +116,8 @@ return n; } + /** {@inheritDoc} */ + @Override public void close() throws IOException { byte[] b = skipBuffer(); for (;;) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,27 +64,31 @@ * Construct a JGitPrintWriter * * @param out - * the underlying {@link Writer} + * the underlying {@link java.io.Writer} */ public ThrowingPrintWriter(Writer out) { this.out = out; LF = AccessController.doPrivileged(new PrivilegedAction() { + @Override public String run() { return SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$ } }); } + /** {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException { out.write(cbuf, off, len); } + /** {@inheritDoc} */ @Override public void flush() throws IOException { out.flush(); } + /** {@inheritDoc} */ @Override public void close() throws IOException { out.close(); @@ -93,8 +97,8 @@ /** * Print a string and terminate with a line feed. * - * @param s - * @throws IOException + * @param s a {@link java.lang.String} object. + * @throws java.io.IOException */ public void println(String s) throws IOException { print(s + LF); @@ -103,7 +107,7 @@ /** * Print a platform dependent new line * - * @throws IOException + * @throws java.io.IOException */ public void println() throws IOException { print(LF); @@ -112,8 +116,8 @@ /** * Print a char * - * @param value - * @throws IOException + * @param value a char. + * @throws java.io.IOException */ public void print(char value) throws IOException { print(String.valueOf(value)); @@ -123,7 +127,8 @@ * Print an int as string * * @param value - * @throws IOException + * an int. + * @throws java.io.IOException */ public void print(int value) throws IOException { print(String.valueOf(value)); @@ -132,8 +137,8 @@ /** * Print a long as string * - * @param value - * @throws IOException + * @param value a long. + * @throws java.io.IOException */ public void print(long value) throws IOException { print(String.valueOf(value)); @@ -142,8 +147,8 @@ /** * Print a short as string * - * @param value - * @throws IOException + * @param value a short. + * @throws java.io.IOException */ public void print(short value) throws IOException { print(String.valueOf(value)); @@ -151,11 +156,13 @@ /** * Print a formatted message according to - * {@link String#format(String, Object...)}. + * {@link java.lang.String#format(String, Object...)}. * * @param fmt + * a {@link java.lang.String} object. * @param args - * @throws IOException + * objects. + * @throws java.io.IOException */ public void format(String fmt, Object... args) throws IOException { print(String.format(fmt, args)); @@ -165,7 +172,8 @@ * Print an object's toString representations * * @param any - * @throws IOException + * an object. + * @throws java.io.IOException */ public void print(Object any) throws IOException { out.write(String.valueOf(any)); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,9 @@ import org.eclipse.jgit.internal.JGitText; -/** InputStream with a configurable timeout. */ +/** + * InputStream with a configurable timeout. + */ public class TimeoutInputStream extends FilterInputStream { private final InterruptTimer myTimer; @@ -72,12 +74,18 @@ myTimer = timer; } - /** @return number of milliseconds before aborting a read. */ + /** + * Get number of milliseconds before aborting a read. + * + * @return number of milliseconds before aborting a read. + */ public int getTimeout() { return timeout; } /** + * Set number of milliseconds before aborting a read. + * * @param millis * number of milliseconds before aborting a read. Must be > 0. */ @@ -88,6 +96,7 @@ timeout = millis; } + /** {@inheritDoc} */ @Override public int read() throws IOException { try { @@ -100,11 +109,13 @@ } } + /** {@inheritDoc} */ @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } + /** {@inheritDoc} */ @Override public int read(byte[] buf, int off, int cnt) throws IOException { try { @@ -117,6 +128,7 @@ } } + /** {@inheritDoc} */ @Override public long skip(long cnt) throws IOException { try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import org.eclipse.jgit.internal.JGitText; -/** OutputStream with a configurable timeout. */ +/** + * OutputStream with a configurable timeout. + */ public class TimeoutOutputStream extends OutputStream { private final OutputStream dst; @@ -73,14 +75,21 @@ myTimer = timer; } - /** @return number of milliseconds before aborting a write. */ + /** + * Get number of milliseconds before aborting a write. + * + * @return number of milliseconds before aborting a write. + */ public int getTimeout() { return timeout; } /** + * Set number of milliseconds before aborting a write. + * * @param millis - * number of milliseconds before aborting a write. Must be > 0. + * number of milliseconds before aborting a write. Must be > + * 0. */ public void setTimeout(final int millis) { if (millis < 0) @@ -89,6 +98,7 @@ timeout = millis; } + /** {@inheritDoc} */ @Override public void write(int b) throws IOException { try { @@ -101,11 +111,13 @@ } } + /** {@inheritDoc} */ @Override public void write(byte[] buf) throws IOException { write(buf, 0, buf.length); } + /** {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) throws IOException { try { @@ -118,6 +130,7 @@ } } + /** {@inheritDoc} */ @Override public void flush() throws IOException { try { @@ -130,6 +143,7 @@ } } + /** {@inheritDoc} */ @Override public void close() throws IOException { try { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,7 +58,7 @@ * Currently this stream does not support the mark/reset APIs. If mark and later * reset functionality is needed the caller should wrap this stream with a * {@link java.io.BufferedInputStream}. - * */ + */ public class UnionInputStream extends InputStream { private static final InputStream EOF = new InputStream() { @Override @@ -67,9 +67,11 @@ } }; - private final LinkedList streams = new LinkedList(); + private final LinkedList streams = new LinkedList<>(); - /** Create an empty InputStream that is currently at EOF state. */ + /** + * Create an empty InputStream that is currently at EOF state. + */ public UnionInputStream() { // Do nothing. } @@ -122,6 +124,7 @@ return streams.isEmpty(); } + /** {@inheritDoc} */ @Override public int read() throws IOException { for (;;) { @@ -136,6 +139,7 @@ } } + /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) @@ -152,11 +156,13 @@ } } + /** {@inheritDoc} */ @Override public int available() throws IOException { return head().available(); } + /** {@inheritDoc} */ @Override public long skip(final long count) throws IOException { long skipped = 0; @@ -190,6 +196,7 @@ return skipped; } + /** {@inheritDoc} */ @Override public void close() throws IOException { IOException err = null; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,9 +71,9 @@ * @param path * location of the file to read. * @return complete contents of the requested local file. - * @throws FileNotFoundException + * @throws java.io.FileNotFoundException * the file does not exist. - * @throws IOException + * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readFully(final File path) @@ -91,9 +91,9 @@ * only the first limit number of bytes are returned * @return complete contents of the requested local file. If the contents * exceeds the limit, then only the limit is returned. - * @throws FileNotFoundException + * @throws java.io.FileNotFoundException * the file does not exist. - * @throws IOException + * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readSome(final File path, final int limit) @@ -131,9 +131,9 @@ * maximum number of bytes to read, if the file is larger than * this limit an IOException is thrown. * @return complete contents of the requested local file. - * @throws FileNotFoundException + * @throws java.io.FileNotFoundException * the file does not exist. - * @throws IOException + * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readFully(final File path, final int max) @@ -199,7 +199,7 @@ * on obtaining the underlying array for efficient data access. If * {@code sizeHint} was too large, the array may be over-allocated, * resulting in {@code limit() < array().length}. - * @throws IOException + * @throws java.io.IOException * there was an error reading from the stream. */ public static ByteBuffer readWholeStream(InputStream in, int sizeHint) @@ -238,7 +238,7 @@ * number of bytes that must be read. * @throws EOFException * the stream ended before dst was fully populated. - * @throws IOException + * @throws java.io.IOException * there was an error reading from the stream. */ public static void readFully(final InputStream fd, final byte[] dst, @@ -264,7 +264,7 @@ * @param len * number of bytes that should be read. * @return number of bytes actually read. - * @throws IOException + * @throws java.io.IOException * there was an error reading from the channel. */ public static int read(ReadableByteChannel channel, byte[] dst, int off, @@ -293,7 +293,7 @@ * @param off * position within the buffer to start writing to. * @return number of bytes in buffer or stream, whichever is shortest - * @throws IOException + * @throws java.io.IOException * there was an error reading from the stream. */ public static int readFully(InputStream fd, byte[] dst, int off) @@ -322,7 +322,7 @@ * @throws EOFException * the stream ended before the requested number of bytes were * skipped. - * @throws IOException + * @throws java.io.IOException * there was an error reading from the stream. */ public static void skipFully(final InputStream fd, long toSkip) @@ -344,7 +344,7 @@ * @since 2.0 */ public static List readLines(final String s) { - List l = new ArrayList(); + List l = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); @@ -384,7 +384,7 @@ * 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 + * @throws java.io.IOException * there was an error reading from the stream. * @since 4.1 */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2018, Markus Duft + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.concurrent.Callable; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; + +/** + * Represents an optionally present LFS support implementation + * + * @since 4.11 + */ +public class LfsFactory { + + private static LfsFactory instance = new LfsFactory(); + + /** + * Constructor + */ + protected LfsFactory() { + } + + /** + * @return the current LFS implementation + */ + public static LfsFactory getInstance() { + return instance; + } + + /** + * @param instance + * register a {@link LfsFactory} instance as the + * {@link LfsFactory} implementation to use. + */ + public static void setInstance(LfsFactory instance) { + LfsFactory.instance = instance; + } + + /** + * @return whether LFS support is available + */ + public boolean isAvailable() { + return false; + } + + /** + * Apply clean filtering to the given stream, writing the file content to + * the LFS storage if required and returning a stream to the LFS pointer + * instead. + * + * @param db + * the repository + * @param input + * the original input + * @param length + * the expected input stream length + * @param attribute + * the attribute used to check for LFS enablement (i.e. "merge", + * "diff", "filter" from .gitattributes). + * @return a stream to the content that should be written to the object + * store along with the expected length of the stream. the original + * stream is not applicable. + * @throws IOException + * in case of an error + */ + public LfsInputStream applyCleanFilter(Repository db, + InputStream input, long length, Attribute attribute) + throws IOException { + return new LfsInputStream(input, length); + } + + /** + * Apply smudge filtering to a given loader, potentially redirecting it to a + * LFS blob which is downloaded on demand. + * + * @param db + * the repository + * @param loader + * the loader for the blob + * @param attribute + * the attribute used to check for LFS enablement (i.e. "merge", + * "diff", "filter" from .gitattributes). + * @return a loader for the actual data of a blob, or the original loader in + * case LFS is not applicable. + * @throws IOException + */ + public ObjectLoader applySmudgeFilter(Repository db, + ObjectLoader loader, Attribute attribute) throws IOException { + return loader; + } + + /** + * Retrieve a pre-push hook to be applied. + * + * @param repo + * the {@link Repository} the hook is applied to. + * @param outputStream + * @return a {@link PrePushHook} implementation or null + */ + public @Nullable PrePushHook getPrePushHook(Repository repo, + PrintStream outputStream) { + return null; + } + + /** + * Retrieve an {@link LfsInstallCommand} which can be used to enable LFS + * support (if available) either per repository or for the user. + * + * @return a command to install LFS support. + */ + public @Nullable LfsInstallCommand getInstallCommand() { + return null; + } + + /** + * @param db + * the repository to check + * @return whether LFS is enabled for the given repository locally or + * globally. + */ + public boolean isEnabled(Repository db) { + return false; + } + + /** + * @param db + * the repository + * @param path + * the path to find attributes for + * @return the {@link Attributes} for the given path. + * @throws IOException + * in case of an error + */ + public static Attributes getAttributesForPath(Repository db, String path) + throws IOException { + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new FileTreeIterator(db)); + PathFilter f = PathFilter.create(path); + walk.setFilter(f); + walk.setRecursive(false); + Attributes attr = null; + while (walk.next()) { + if (f.isDone(walk)) { + attr = walk.getAttributes(); + break; + } else if (walk.isSubtree()) { + walk.enterSubtree(); + } + } + if (attr == null) { + throw new IOException(MessageFormat + .format(JGitText.get().noPathAttributesFound, path)); + } + + return attr; + } + } + + /** + * Get attributes for given path and commit + * + * @param db + * the repository + * @param path + * the path to find attributes for + * @param commit + * the commit to inspect. + * @return the {@link Attributes} for the given path. + * @throws IOException + * in case of an error + */ + public static Attributes getAttributesForPath(Repository db, String path, + RevCommit commit) throws IOException { + if (commit == null) { + return getAttributesForPath(db, path); + } + + try (TreeWalk walk = TreeWalk.forPath(db, path, commit.getTree())) { + Attributes attr = walk == null ? null : walk.getAttributes(); + if (attr == null) { + throw new IOException(MessageFormat + .format(JGitText.get().noPathAttributesFound, path)); + } + + return attr; + } + } + + /** + * Encapsulate a potentially exchanged {@link InputStream} along with the + * expected stream content length. + */ + public static final class LfsInputStream extends InputStream { + /** + * The actual stream. + */ + private InputStream stream; + + /** + * The expected stream content length. + */ + private long length; + + /** + * Create a new wrapper around a certain stream + * + * @param stream + * the stream to wrap. the stream will be closed on + * {@link #close()}. + * @param length + * the expected length of the stream + */ + public LfsInputStream(InputStream stream, long length) { + this.stream = stream; + this.length = length; + } + + /** + * Create a new wrapper around a temporary buffer. + * + * @param buffer + * the buffer to initialize stream and length from. The + * buffer will be destroyed on {@link #close()} + * @throws IOException + * in case of an error opening the stream to the buffer. + */ + public LfsInputStream(TemporaryBuffer buffer) throws IOException { + this.stream = buffer.openInputStreamWithAutoDestroy(); + this.length = buffer.length(); + } + + @Override + public void close() throws IOException { + stream.close(); + } + + @Override + public int read() throws IOException { + return stream.read(); + } + + /** + * @return the length of the stream + */ + public long getLength() { + return length; + } + } + + /** + * A command to enable LFS. Optionally set a {@link Repository} to enable + * locally on the repository only. + */ + public interface LfsInstallCommand extends Callable { + /** + * @param repo + * the repository to enable support for. + * @return The {@link LfsInstallCommand} for chaining. + */ + public LfsInstallCommand setRepository(Repository repo); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,13 +46,17 @@ import java.util.Arrays; -/** A more efficient List<Long> using a primitive long array. */ +/** + * A more efficient List<Long> using a primitive long array. + */ public class LongList { private long[] entries; private int count; - /** Create an empty list with a default capacity. */ + /** + * Create an empty list with a default capacity. + */ public LongList() { this(10); } @@ -67,16 +71,22 @@ entries = new long[capacity]; } - /** @return number of entries in this list */ + /** + * Get number of entries in this list + * + * @return number of entries in this list + */ public int size() { return count; } /** + * Get the value at the specified index + * * @param i * index to read, must be in the range [0, {@link #size()}). * @return the number at the specified index - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * the index outside the valid range */ public long get(final int i) { @@ -99,7 +109,9 @@ return false; } - /** Empty this list */ + /** + * Clear this list + */ public void clear() { count = 0; } @@ -148,7 +160,9 @@ add(val); } - /** Sort the list of longs according to their natural ordering. */ + /** + * Sort the list of longs according to their natural ordering. + */ public void sort() { Arrays.sort(entries, 0, count); } @@ -159,6 +173,8 @@ entries = n; } + /** {@inheritDoc} */ + @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append('['); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2009, 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; + +/** + * Simple Map<long, Object>. + * + * @param + * type of the value instance. + * @since 4.9 + */ +public class LongMap { + private static final float LOAD_FACTOR = 0.75f; + + private Node[] table; + + /** Number of entries currently in the map. */ + private int size; + + /** Next {@link #size} to trigger a {@link #grow()}. */ + private int growAt; + + /** + * Initialize an empty LongMap. + */ + public LongMap() { + table = createArray(64); + growAt = (int) (table.length * LOAD_FACTOR); + } + + /** + * Whether {@code key} is present in the map. + * + * @param key + * the key to find. + * @return {@code true} if {@code key} is present in the map. + */ + public boolean containsKey(long key) { + return get(key) != null; + } + + /** + * Get value for this {@code key} + * + * @param key + * the key to find. + * @return stored value for this key, or {@code null}. + */ + public V get(long key) { + for (Node n = table[index(key)]; n != null; n = n.next) { + if (n.key == key) + return n.value; + } + return null; + } + + /** + * Remove an entry from the map + * + * @param key + * key to remove from the map. + * @return old value of the key, or {@code null}. + */ + public V remove(long key) { + Node n = table[index(key)]; + Node prior = null; + while (n != null) { + if (n.key == key) { + if (prior == null) + table[index(key)] = n.next; + else + prior.next = n.next; + size--; + return n.value; + } + prior = n; + n = n.next; + } + return null; + } + + /** + * Put a new entry into the map + * + * @param key + * key to store {@code value} under. + * @param value + * new value. + * @return prior value, or null. + */ + public V put(long key, V value) { + for (Node n = table[index(key)]; n != null; n = n.next) { + if (n.key == key) { + final V o = n.value; + n.value = value; + return o; + } + } + + if (++size == growAt) + grow(); + insert(new Node<>(key, value)); + return null; + } + + private void insert(final Node n) { + final int idx = index(n.key); + n.next = table[idx]; + table[idx] = n; + } + + private void grow() { + final Node[] oldTable = table; + final int oldSize = table.length; + + table = createArray(oldSize << 1); + growAt = (int) (table.length * LOAD_FACTOR); + for (int i = 0; i < oldSize; i++) { + Node e = oldTable[i]; + while (e != null) { + final Node n = e.next; + insert(e); + e = n; + } + } + } + + private final int index(final long key) { + int h = ((int) key) >>> 1; + h ^= (h >>> 20) ^ (h >>> 12); + return h & (table.length - 1); + } + + @SuppressWarnings("unchecked") + private static final Node[] createArray(final int sz) { + return new Node[sz]; + } + + private static class Node { + final long key; + V value; + Node next; + + Node(final long k, final V v) { + key = k; + value = v; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.util; -/** A boxed integer that can be modified. */ +/** + * A boxed integer that can be modified. + */ public final class MutableInteger { /** Current value of this boxed value. */ public int value; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2008, 2015 Shawn O. Pearce * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -43,7 +43,9 @@ package org.eclipse.jgit.util; -/** Conversion utilities for network byte order handling. */ +/** + * Conversion utilities for network byte order handling. + */ public final class NB { /** * Compare a 32 bit unsigned integer stored in a 32 bit signed integer. @@ -66,6 +68,37 @@ } /** + * Compare a 64 bit unsigned integer stored in a 64 bit signed integer. + *

        + * This function performs an unsigned compare operation, even though Java + * does not natively support unsigned integer values. Negative numbers are + * treated as larger than positive ones. + * + * @param a + * the first value to compare. + * @param b + * the second value to compare. + * @return < 0 if a < b; 0 if a == b; > 0 if a > b. + * @since 4.3 + */ + public static int compareUInt64(final long a, final long b) { + long cmp = (a >>> 1) - (b >>> 1); + if (cmp > 0) { + return 1; + } else if (cmp < 0) { + return -1; + } + cmp = ((a & 1) - (b & 1)); + if (cmp > 0) { + return 1; + } else if (cmp < 0) { + return -1; + } else { + return 0; + } + } + + /** * Convert sequence of 2 bytes (network byte order) into unsigned value. * * @param intbuf @@ -82,6 +115,24 @@ } /** + * Convert sequence of 3 bytes (network byte order) into unsigned value. + * + * @param intbuf + * buffer to acquire the 3 bytes of data from. + * @param offset + * position within the buffer to begin reading from. This + * position and the next 2 bytes after it (for a total of 3 + * bytes) will be read. + * @return signed integer value that matches the 24 bits read. + * @since 4.9 + */ + public static int decodeUInt24(byte[] intbuf, int offset) { + int r = (intbuf[offset] & 0xff) << 8; + r |= intbuf[offset + 1] & 0xff; + return (r << 8) | (intbuf[offset + 2] & 0xff); + } + + /** * Convert sequence of 4 bytes (network byte order) into signed value. * * @param intbuf @@ -188,6 +239,29 @@ intbuf[offset + 1] = (byte) v; v >>>= 8; + intbuf[offset] = (byte) v; + } + + /** + * Write a 24 bit integer as a sequence of 3 bytes (network byte order). + * + * @param intbuf + * buffer to write the 3 bytes of data into. + * @param offset + * position within the buffer to begin writing to. This position + * and the next 2 bytes after it (for a total of 3 bytes) will be + * replaced. + * @param v + * the value to write. + * @since 4.9 + */ + public static void encodeInt24(byte[] intbuf, int offset, int v) { + intbuf[offset + 2] = (byte) v; + v >>>= 8; + + intbuf[offset + 1] = (byte) v; + v >>>= 8; + intbuf[offset] = (byte) v; } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2016, 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.eclipse.jgit.lib.FileMode.TYPE_MASK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + +/** + * Utility functions for paths inside of a Git repository. + * + * @since 4.2 + */ +public class Paths { + /** + * Remove trailing {@code '/'} if present. + * + * @param path + * input path to potentially remove trailing {@code '/'} from. + * @return null if {@code path == null}; {@code path} after removing a + * trailing {@code '/'}. + */ + public static String stripTrailingSeparator(String path) { + if (path == null || path.isEmpty()) { + return path; + } + + int i = path.length(); + if (path.charAt(path.length() - 1) != '/') { + return path; + } + do { + i--; + } while (path.charAt(i - 1) == '/'); + return path.substring(0, i); + } + + /** + * Compare two paths according to Git path sort ordering rules. + * + * @param aPath + * first path buffer. The range {@code [aPos, aEnd)} is used. + * @param aPos + * index into {@code aPath} where the first path starts. + * @param aEnd + * 1 past last index of {@code aPath}. + * @param aMode + * mode of the first file. Trees are sorted as though + * {@code aPath[aEnd] == '/'}, even if aEnd does not exist. + * @param bPath + * second path buffer. The range {@code [bPos, bEnd)} is used. + * @param bPos + * index into {@code bPath} where the second path starts. + * @param bEnd + * 1 past last index of {@code bPath}. + * @param bMode + * mode of the second file. Trees are sorted as though + * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. + * @return <0 if {@code aPath} sorts before {@code bPath}; + * 0 if the paths are the same; + * >0 if {@code aPath} sorts after {@code bPath}. + */ + public static int compare(byte[] aPath, int aPos, int aEnd, int aMode, + byte[] bPath, int bPos, int bEnd, int bMode) { + int cmp = coreCompare( + aPath, aPos, aEnd, aMode, + bPath, bPos, bEnd, bMode); + if (cmp == 0) { + cmp = lastPathChar(aMode) - lastPathChar(bMode); + } + return cmp; + } + + /** + * Compare two paths, checking for identical name. + *

        + * Unlike {@code compare} this method returns {@code 0} when the paths have + * the same characters in their names, even if the mode differs. It is + * intended for use in validation routines detecting duplicate entries. + *

        + * Returns {@code 0} if the names are identical and a conflict exists + * between {@code aPath} and {@code bPath}, as they share the same name. + *

        + * Returns {@code <0} if all possibles occurrences of {@code aPath} sort + * before {@code bPath} and no conflict can happen. In a properly sorted + * tree there are no other occurrences of {@code aPath} and therefore there + * are no duplicate names. + *

        + * Returns {@code >0} when it is possible for a duplicate occurrence of + * {@code aPath} to appear later, after {@code bPath}. Callers should + * continue to examine candidates for {@code bPath} until the method returns + * one of the other return values. + * + * @param aPath + * first path buffer. The range {@code [aPos, aEnd)} is used. + * @param aPos + * index into {@code aPath} where the first path starts. + * @param aEnd + * 1 past last index of {@code aPath}. + * @param bPath + * second path buffer. The range {@code [bPos, bEnd)} is used. + * @param bPos + * index into {@code bPath} where the second path starts. + * @param bEnd + * 1 past last index of {@code bPath}. + * @param bMode + * mode of the second file. Trees are sorted as though + * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. + * @return <0 if no duplicate name could exist; + * 0 if the paths have the same name; + * >0 other {@code bPath} should still be checked by caller. + */ + public static int compareSameName( + byte[] aPath, int aPos, int aEnd, + byte[] bPath, int bPos, int bEnd, int bMode) { + return coreCompare( + aPath, aPos, aEnd, TYPE_TREE, + bPath, bPos, bEnd, bMode); + } + + private static int coreCompare( + byte[] aPath, int aPos, int aEnd, int aMode, + byte[] bPath, int bPos, int bEnd, int bMode) { + while (aPos < aEnd && bPos < bEnd) { + int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); + if (cmp != 0) { + return cmp; + } + } + if (aPos < aEnd) { + return (aPath[aPos] & 0xff) - lastPathChar(bMode); + } + if (bPos < bEnd) { + return lastPathChar(aMode) - (bPath[bPos] & 0xff); + } + return 0; + } + + private static int lastPathChar(int mode) { + if ((mode & TYPE_MASK) == TYPE_TREE) { + return '/'; + } + return 0; + } + + private Paths() { + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java 2019-09-03 12:37:49.000000000 +0000 @@ -86,6 +86,8 @@ } /** + *

        Constructor for ProcessResult.

        + * * @param exitCode * Exit code of the process. * @param status @@ -97,6 +99,8 @@ } /** + * Get exit code of the process. + * * @return The exit code of the process. */ public int getExitCode() { @@ -104,6 +108,8 @@ } /** + * Get the status of the process' execution. + * * @return The status of the process' execution. */ public Status getStatus() { @@ -111,6 +117,8 @@ } /** + * Whether the execution occurred and resulted in an error + * * @return true if the execution occurred and resulted in a * return code different from 0, false otherwise. * @since 4.0 diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,9 @@ import org.eclipse.jgit.lib.Constants; -/** Utility functions related to quoted string handling. */ +/** + * Utility functions related to quoted string handling. + */ public abstract class QuotedString { /** Quoting style that obeys the rules Git applies to file names */ public static final GitPathStyle GIT_PATH = new GitPathStyle(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,18 +74,25 @@ endPtr = end; } + /** {@inheritDoc} */ + @Override public char charAt(final int index) { return (char) (buffer[startPtr + index] & 0xff); } + /** {@inheritDoc} */ + @Override public int length() { return endPtr - startPtr; } + /** {@inheritDoc} */ + @Override public CharSequence subSequence(final int start, final int end) { return new RawCharSequence(buffer, startPtr + start, startPtr + end); } + /** {@inheritDoc} */ @Override public String toString() { final int n = length(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,8 @@ package org.eclipse.jgit.util; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.ObjectChecker.author; import static org.eclipse.jgit.lib.ObjectChecker.committer; import static org.eclipse.jgit.lib.ObjectChecker.encoding; @@ -60,17 +62,20 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.PersonIdent; -/** Handy utility functions to parse raw object contents. */ +/** + * Handy utility functions to parse raw object contents. + */ public final class RawParseUtils { /** * UTF-8 charset constant. * * @since 2.2 */ - public static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$ + public static final Charset UTF8_CHARSET = UTF_8; private static final byte[] digits10; @@ -81,8 +86,9 @@ private static final Map encodingAliases; static { - encodingAliases = new HashMap(); - encodingAliases.put("latin-1", Charset.forName("ISO-8859-1")); //$NON-NLS-1$ //$NON-NLS-2$ + encodingAliases = new HashMap<>(); + encodingAliases.put("latin-1", ISO_8859_1); //$NON-NLS-1$ + encodingAliases.put("iso-latin-1", ISO_8859_1); //$NON-NLS-1$ digits10 = new byte['9' + 1]; Arrays.fill(digits10, (byte) -1); @@ -302,7 +308,7 @@ * @param p * first position within the buffer to parse. * @return the integer value. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * if the string is not hex formatted. */ public static final int parseHexInt16(final byte[] bs, final int p) { @@ -332,7 +338,7 @@ * @param p * first position within the buffer to parse. * @return the integer value. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * if the string is not hex formatted. */ public static final int parseHexInt32(final byte[] bs, final int p) { @@ -362,12 +368,78 @@ } /** + * Parse 16 character base 16 (hex) formatted string to unsigned long. + *

        + * The number is read in network byte order, that is, most significant + * nibble first. + * + * @param bs + * buffer to parse digits from; positions {@code [p, p+16)} will + * be parsed. + * @param p + * first position within the buffer to parse. + * @return the integer value. + * @throws java.lang.ArrayIndexOutOfBoundsException + * if the string is not hex formatted. + * @since 4.3 + */ + public static final long parseHexInt64(final byte[] bs, final int p) { + long r = digits16[bs[p]] << 4; + + r |= digits16[bs[p + 1]]; + r <<= 4; + + r |= digits16[bs[p + 2]]; + r <<= 4; + + r |= digits16[bs[p + 3]]; + r <<= 4; + + r |= digits16[bs[p + 4]]; + r <<= 4; + + r |= digits16[bs[p + 5]]; + r <<= 4; + + r |= digits16[bs[p + 6]]; + r <<= 4; + + r |= digits16[bs[p + 7]]; + r <<= 4; + + r |= digits16[bs[p + 8]]; + r <<= 4; + + r |= digits16[bs[p + 9]]; + r <<= 4; + + r |= digits16[bs[p + 10]]; + r <<= 4; + + r |= digits16[bs[p + 11]]; + r <<= 4; + + r |= digits16[bs[p + 12]]; + r <<= 4; + + r |= digits16[bs[p + 13]]; + r <<= 4; + + r |= digits16[bs[p + 14]]; + + final int last = digits16[bs[p + 15]]; + if (r < 0 || last < 0) + throw new ArrayIndexOutOfBoundsException(); + return (r << 4) | last; + } + + /** * Parse a single hex digit to its numeric value (0-15). * * @param digit * hex character to parse. * @return numeric value, in the range 0-15. - * @throws ArrayIndexOutOfBoundsException + * @throws java.lang.ArrayIndexOutOfBoundsException * if the input digit is not a valid hex digit. */ public static final int parseHexInt4(final byte digit) { @@ -540,7 +612,7 @@ * Index the region between [ptr, end) to find line starts. *

        * The returned list is 1 indexed. Index 0 contains - * {@link Integer#MIN_VALUE} to pad the list out. + * {@link java.lang.Integer#MIN_VALUE} to pad the list out. *

        * Using a 1 indexed list means that line numbers can be directly accessed * from the list, so list.get(1) (aka get line 1) returns @@ -548,6 +620,10 @@ *

        * The last element (index map.size()-1) always contains * end. + *

        + * If the data contains a '\0' anywhere, the whole region is considered + * binary and a LineMap corresponding to a single line is returned. + *

        * * @param buf * buffer to scan. @@ -559,14 +635,29 @@ * @return a line map indexing the start position of each line. */ public static final IntList lineMap(final byte[] buf, int ptr, int end) { + int start = ptr; + // Experimentally derived from multiple source repositories // the average number of bytes/line is 36. Its a rough guess // to initially size our map close to the target. - // - final IntList map = new IntList((end - ptr) / 36); - map.fillTo(1, Integer.MIN_VALUE); - for (; ptr < end; ptr = nextLF(buf, ptr)) - map.add(ptr); + IntList map = new IntList((end - ptr) / 36); + map.add(Integer.MIN_VALUE); + boolean foundLF = true; + for (; ptr < end; ptr++) { + if (foundLF) { + map.add(ptr); + } + + if (buf[ptr] == '\0') { + // binary data. + map = new IntList(3); + map.add(Integer.MIN_VALUE); + map.add(start); + break; + } + + foundLF = (buf[ptr] == '\n'); + } map.add(end); return map; } @@ -671,35 +762,60 @@ } /** + * Parse the "encoding " header as a string. + *

        + * Locates the "encoding " header (if present) and returns its value. + * + * @param b + * buffer to scan. + * @return the encoding header as specified in the commit; null if the + * header was not present and should be assumed. + * @since 4.2 + */ + @Nullable + public static String parseEncodingName(final byte[] b) { + int enc = encoding(b, 0); + if (enc < 0) { + return null; + } + int lf = nextLF(b, enc); + return decode(UTF_8, b, enc, lf - 1); + } + + /** * Parse the "encoding " header into a character set reference. *

        * Locates the "encoding " header (if present) by first calling * {@link #encoding(byte[], int)} and then returns the proper character set * to apply to this buffer to evaluate its contents as character data. *

        - * If no encoding header is present, {@link Constants#CHARSET} is assumed. + * If no encoding header is present {@code UTF-8} is assumed. * * @param b * buffer to scan. * @return the Java character set representation. Never null. + * @throws IllegalCharsetNameException + * if the character set requested by the encoding header is + * malformed and unsupportable. + * @throws UnsupportedCharsetException + * if the JRE does not support the character set requested by + * the encoding header. */ public static Charset parseEncoding(final byte[] b) { - final int enc = encoding(b, 0); - if (enc < 0) - return Constants.CHARSET; - final int lf = nextLF(b, enc); - String decoded = decode(Constants.CHARSET, b, enc, lf - 1); + String enc = parseEncodingName(b); + if (enc == null) { + return UTF_8; + } + + String name = enc.trim(); try { - return Charset.forName(decoded); - } catch (IllegalCharsetNameException badName) { - Charset aliased = charsetForAlias(decoded); - if (aliased != null) - return aliased; - throw badName; - } catch (UnsupportedCharsetException badName) { - Charset aliased = charsetForAlias(decoded); - if (aliased != null) + return Charset.forName(name); + } catch (IllegalCharsetNameException + | UnsupportedCharsetException badName) { + Charset aliased = charsetForAlias(name); + if (aliased != null) { return aliased; + } throw badName; } } @@ -738,7 +854,15 @@ * parsed. */ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) { - final Charset cs = parseEncoding(raw); + Charset cs; + try { + cs = parseEncoding(raw); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + // Assume UTF-8 for person identities, usually this is correct. + // If not decode() will fall back to the ISO-8859-1 encoding. + cs = UTF_8; + } + final int emailB = nextLF(raw, nameB, '<'); final int emailE = nextLF(raw, emailB, '>'); if (emailB >= raw.length || raw[emailB] == '\n' || @@ -886,7 +1010,7 @@ */ public static String decode(final byte[] buffer, final int start, final int end) { - return decode(Constants.CHARSET, buffer, start, end); + return decode(UTF_8, buffer, start, end); } /** @@ -954,29 +1078,27 @@ * data from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. - * @throws CharacterCodingException + * @throws java.nio.charset.CharacterCodingException * the input is not in any of the tested character sets. */ public static String decodeNoFallback(final Charset cs, final byte[] buffer, final int start, final int end) throws CharacterCodingException { - final ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start); + ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start); b.mark(); // Try our built-in favorite. The assumption here is that // decoding will fail if the data is not actually encoded // using that encoder. - // try { - return decode(b, Constants.CHARSET); + return decode(b, UTF_8); } catch (CharacterCodingException e) { b.reset(); } - if (!cs.equals(Constants.CHARSET)) { + if (!cs.equals(UTF_8)) { // Try the suggested encoding, it might be right since it was // provided by the caller. - // try { return decode(b, cs); } catch (CharacterCodingException e) { @@ -986,9 +1108,8 @@ // Try the default character set. A small group of people // might actually use the same (or very similar) locale. - // - final Charset defcs = Charset.defaultCharset(); - if (!defcs.equals(cs) && !defcs.equals(Constants.CHARSET)) { + Charset defcs = Charset.defaultCharset(); + if (!defcs.equals(cs) && !defcs.equals(UTF_8)) { try { return decode(b, defcs); } catch (CharacterCodingException e) { @@ -1103,13 +1224,15 @@ } /** + * Get last index of {@code ch} in raw, trimming spaces. + * * @param raw * buffer to scan. * @param ch * character to find. * @param pos * starting position. - * @return last index of ch in raw, trimming spaces. + * @return last index of {@code ch} in raw, trimming spaces. * @since 4.1 */ public static int lastIndexOfTrim(byte[] raw, char ch, int pos) { diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java 2019-09-03 12:37:49.000000000 +0000 @@ -131,6 +131,7 @@ return needleString; } + /** {@inheritDoc} */ @Override public String toString() { return pattern(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,21 +68,21 @@ * the type of reference being stored in the collection. */ public class RefList implements Iterable { - private static final RefList EMPTY = new RefList(new Ref[0], 0); + private static final RefList EMPTY = new RefList<>(new Ref[0], 0); /** + * Create an empty unmodifiable reference list. + * * @return an empty unmodifiable reference list. - * @param - * the type of reference being stored in the collection. */ @SuppressWarnings("unchecked") public static RefList emptyList() { return (RefList) EMPTY; } - private final Ref[] list; + final Ref[] list; - private final int cnt; + final int cnt; RefList(Ref[] list, int cnt) { this.list = list; @@ -100,38 +100,55 @@ this.cnt = src.cnt; } + /** {@inheritDoc} */ + @Override public Iterator iterator() { return new Iterator() { private int idx; + @Override public boolean hasNext() { return idx < cnt; } + @Override public Ref next() { if (idx < cnt) return list[idx++]; throw new NoSuchElementException(); } + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - /** @return this cast as an immutable, standard {@link java.util.List}. */ + /** + * Cast {@code this} as an immutable, standard {@link java.util.List}. + * + * @return {@code this} as an immutable, standard {@link java.util.List}. + */ public final List asList() { final List r = Arrays.asList(list).subList(0, cnt); return Collections.unmodifiableList(r); } - /** @return number of items in this list. */ + /** + * Get number of items in this list. + * + * @return number of items in this list. + */ public final int size() { return cnt; } - /** @return true if the size of this list is 0. */ + /** + * Get if this list is empty. + * + * @return true if the size of this list is 0. + */ public final boolean isEmpty() { return cnt == 0; } @@ -209,7 +226,7 @@ * @return a new builder with the first {@code n} elements already added. */ public final Builder copy(int n) { - Builder r = new Builder(Math.max(16, n)); + Builder r = new Builder<>(Math.max(16, n)); r.addAll(list, 0, n); return r; } @@ -230,7 +247,7 @@ Ref[] newList = new Ref[cnt]; System.arraycopy(list, 0, newList, 0, cnt); newList[idx] = ref; - return new RefList(newList, cnt); + return new RefList<>(newList, cnt); } /** @@ -257,7 +274,7 @@ newList[idx] = ref; if (idx < cnt) System.arraycopy(list, idx, newList, idx + 1, cnt - idx); - return new RefList(newList, cnt + 1); + return new RefList<>(newList, cnt + 1); } /** @@ -278,7 +295,7 @@ System.arraycopy(list, 0, newList, 0, idx); if (idx + 1 < cnt) System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1)); - return new RefList(newList, cnt - 1); + return new RefList<>(newList, cnt - 1); } /** @@ -299,6 +316,7 @@ return add(idx, ref); } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); @@ -334,10 +352,11 @@ * Create an empty list with at least the specified capacity. * * @param capacity - * the new capacity. + * the new capacity; if zero or negative, behavior is the same as + * {@link #Builder()}. */ public Builder(int capacity) { - list = new Ref[capacity]; + list = new Ref[Math.max(capacity, 16)]; } /** @return number of items in this builder's internal collection. */ @@ -427,7 +446,7 @@ /** @return an unmodifiable list using this collection's backing array. */ public RefList toRefList() { - return new RefList(list, size); + return new RefList<>(list, size); } @Override diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,17 +59,19 @@ * Specialized Map to present a {@code RefDatabase} namespace. *

        * Although not declared as a {@link java.util.SortedMap}, iterators from this - * map's projections always return references in {@link RefComparator} ordering. - * The map's internal representation is a sorted array of {@link Ref} objects, + * map's projections always return references in + * {@link org.eclipse.jgit.lib.RefComparator} ordering. The map's internal + * representation is a sorted array of {@link org.eclipse.jgit.lib.Ref} objects, * which means lookup and replacement is O(log N), while insertion and removal * can be as expensive as O(N + log N) while the list expands or contracts. * Since this is not a general map implementation, all entries must be keyed by * the reference name. *

        * This class is really intended as a helper for {@code RefDatabase}, which - * needs to perform a merge-join of three sorted {@link RefList}s in order to - * present the unified namespace of the packed-refs file, the loose refs/ - * directory tree, and the resolved form of any symbolic references. + * needs to perform a merge-join of three sorted + * {@link org.eclipse.jgit.util.RefList}s in order to present the unified + * namespace of the packed-refs file, the loose refs/ directory tree, and the + * resolved form of any symbolic references. */ public class RefMap extends AbstractMap { /** @@ -78,10 +80,10 @@ * All reference names in this map must start with this prefix. If the * prefix is not the empty string, it must end with a '/'. */ - private final String prefix; + final String prefix; /** Immutable collection of the packed references at construction time. */ - private RefList packed; + RefList packed; /** * Immutable collection of the loose references at construction time. @@ -91,7 +93,7 @@ * are typically unresolved, so they only tell us who their target is, but * not the current value of the target. */ - private RefList loose; + RefList loose; /** * Immutable collection of resolved symbolic references. @@ -101,15 +103,17 @@ * from {@link #loose}. Every entry in this list must be matched by an entry * in {@code loose}, otherwise it might be omitted by the map. */ - private RefList resolved; + RefList resolved; - private int size; + int size; - private boolean sizeIsValid; + boolean sizeIsValid; private Set> entrySet; - /** Construct an empty map with a small initial capacity. */ + /** + * Construct an empty map with a small initial capacity. + */ public RefMap() { prefix = ""; //$NON-NLS-1$ packed = RefList.emptyList(); @@ -145,11 +149,13 @@ this.resolved = (RefList) resolved; } + /** {@inheritDoc} */ @Override public boolean containsKey(Object name) { return get(name) != null; } + /** {@inheritDoc} */ @Override public Ref get(Object key) { String name = toRefName((String) key); @@ -161,6 +167,7 @@ return ref; } + /** {@inheritDoc} */ @Override public Ref put(final String keyName, Ref value) { String name = toRefName(keyName); @@ -189,6 +196,7 @@ } } + /** {@inheritDoc} */ @Override public Ref remove(Object key) { String name = toRefName((String) key); @@ -212,11 +220,13 @@ return res; } + /** {@inheritDoc} */ @Override public boolean isEmpty() { return entrySet().isEmpty(); } + /** {@inheritDoc} */ @Override public Set> entrySet() { if (entrySet == null) { @@ -258,6 +268,7 @@ return entrySet; } + /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); @@ -280,7 +291,7 @@ return name; } - private String toMapKey(Ref ref) { + String toMapKey(Ref ref) { String name = ref.getName(); if (0 < prefix.length()) name = name.substring(prefix.length()); @@ -304,12 +315,14 @@ } } + @Override public boolean hasNext() { if (next == null) next = peek(); return next != null; } + @Override public Entry next() { if (hasNext()) { Entry r = next; @@ -367,6 +380,7 @@ return null; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -379,14 +393,17 @@ this.ref = ref; } + @Override public String getKey() { return toMapKey(ref); } + @Override public Ref getValue() { return ref; } + @Override public Ref setValue(Ref value) { Ref prior = put(getKey(), value); ref = value; diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,10 +67,14 @@ final static long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS; /** + * Get age of given {@link java.util.Date} compared to now formatted in the + * same relative format as returned by {@code git log --relative-date} + * * @param when - * {@link Date} to format - * @return age of given {@link Date} compared to now formatted in the same - * relative format as returned by {@code git log --relative-date} + * {@link java.util.Date} to format + * @return age of given {@link java.util.Date} compared to now formatted in + * the same relative format as returned by + * {@code git log --relative-date} */ @SuppressWarnings("boxing") public static String format(Date when) { @@ -114,10 +118,11 @@ // up to 5 years use "year, months" rounded to months if (ageMillis < 5 * YEAR_IN_MILLIS) { - long years = ageMillis / YEAR_IN_MILLIS; + long years = round(ageMillis, MONTH_IN_MILLIS) / 12; String yearLabel = (years > 1) ? JGitText.get().years : // JGitText.get().year; - long months = round(ageMillis % YEAR_IN_MILLIS, MONTH_IN_MILLIS); + long months = round(ageMillis - years * YEAR_IN_MILLIS, + MONTH_IN_MILLIS); String monthLabel = (months > 1) ? JGitText.get().months : // (months == 1 ? JGitText.get().month : ""); //$NON-NLS-1$ return MessageFormat.format( @@ -140,4 +145,4 @@ long rounded = (n + unit / 2) / unit; return rounded; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017, 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.sha1; + +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Thrown by {@link org.eclipse.jgit.util.sha1.SHA1} if it detects a likely hash + * collision. + * + * @since 4.7 + */ +public class Sha1CollisionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Initialize with default message. + * + * @param id + * object whose contents are a hash collision. + */ + public Sha1CollisionException(ObjectId id) { + super(MessageFormat.format( + JGitText.get().sha1CollisionDetected1, + id.name())); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2017, 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.sha1; + +import static java.lang.Integer.lowestOneBit; +import static java.lang.Integer.numberOfTrailingZeros; +import static java.lang.Integer.rotateLeft; +import static java.lang.Integer.rotateRight; + +import java.util.Arrays; + +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pure Java implementation of SHA-1 from FIPS 180-1 / RFC 3174. + * + *

        + * See RFC 3174. + *

        + * Unlike MessageDigest, this implementation includes the algorithm used by + * {@code sha1dc} to detect cryptanalytic collision attacks against SHA-1, such + * as the one used by SHAttered. See + * + * sha1collisiondetection for more information. + *

        + * When detectCollision is true (default), this implementation throws + * {@link org.eclipse.jgit.util.sha1.Sha1CollisionException} from any digest + * method if a potential collision was detected. + * + * @since 4.7 + */ +public class SHA1 { + private static Logger LOG = LoggerFactory.getLogger(SHA1.class); + private static final boolean DETECT_COLLISIONS; + + static { + SystemReader sr = SystemReader.getInstance(); + String v = sr.getProperty("org.eclipse.jgit.util.sha1.detectCollision"); //$NON-NLS-1$ + DETECT_COLLISIONS = v != null ? Boolean.parseBoolean(v) : true; + } + + /** + * Create a new context to compute a SHA-1 hash of data. + * + * @return a new context to compute a SHA-1 hash of data. + */ + public static SHA1 newInstance() { + return new SHA1(); + } + + private final State h = new State(); + private final int[] w = new int[80]; + + /** Buffer to accumulate partial blocks to 64 byte alignment. */ + private final byte[] buffer = new byte[64]; + + /** Total number of bytes in the message. */ + private long length; + + private boolean detectCollision = DETECT_COLLISIONS; + private boolean foundCollision; + + private final int[] w2 = new int[80]; + private final State state58 = new State(); + private final State state65 = new State(); + private final State hIn = new State(); + private final State hTmp = new State(); + + private SHA1() { + h.init(); + } + + /** + * Enable likely collision detection. + *

        + * Default is {@code true}. + *

        + * May also be set by system property: + * {@code -Dorg.eclipse.jgit.util.sha1.detectCollision=true}. + * + * @param detect + * a boolean. + * @return {@code this} + */ + public SHA1 setDetectCollision(boolean detect) { + detectCollision = detect; + return this; + } + + /** + * Update the digest computation by adding a byte. + * + * @param b a byte. + */ + public void update(byte b) { + int bufferLen = (int) (length & 63); + length++; + buffer[bufferLen] = b; + if (bufferLen == 63) { + compress(buffer, 0); + } + } + + /** + * Update the digest computation by adding bytes to the message. + * + * @param in + * input array of bytes. + */ + public void update(byte[] in) { + update(in, 0, in.length); + } + + /** + * Update the digest computation by adding bytes to the message. + * + * @param in + * input array of bytes. + * @param p + * offset to start at from {@code in}. + * @param len + * number of bytes to hash. + */ + public void update(byte[] in, int p, int len) { + // SHA-1 compress can only process whole 64 byte blocks. + // Hold partial updates in buffer, whose length is the low bits. + int bufferLen = (int) (length & 63); + length += len; + + if (bufferLen > 0) { + int n = Math.min(64 - bufferLen, len); + System.arraycopy(in, p, buffer, bufferLen, n); + p += n; + len -= n; + if (bufferLen + n < 64) { + return; + } + compress(buffer, 0); + } + while (len >= 64) { + compress(in, p); + p += 64; + len -= 64; + } + if (len > 0) { + System.arraycopy(in, p, buffer, 0, len); + } + } + + private void compress(byte[] block, int p) { + initBlock(block, p); + int ubcDvMask = detectCollision ? UbcCheck.check(w) : 0; + compress(); + + while (ubcDvMask != 0) { + int b = numberOfTrailingZeros(lowestOneBit(ubcDvMask)); + UbcCheck.DvInfo dv = UbcCheck.DV[b]; + for (int i = 0; i < 80; i++) { + w2[i] = w[i] ^ dv.dm[i]; + } + recompress(dv.testt); + if (eq(hTmp, h)) { + foundCollision = true; + break; + } + ubcDvMask &= ~(1 << b); + } + } + + private void initBlock(byte[] block, int p) { + for (int t = 0; t < 16; t++) { + w[t] = NB.decodeInt32(block, p + (t << 2)); + } + + // RFC 3174 6.1.b, extend state vector to 80 words. + for (int t = 16; t < 80; t++) { + int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = rotateLeft(x, 1); // S^1(...) + } + } + + private void compress() { + // Method 1 from RFC 3174 section 6.1. + // Method 2 (circular queue of 16 words) is slower. + int a = h.a, b = h.b, c = h.c, d = h.d, e = h.e; + + // @formatter:off + e += s1(a, b, c, d,w[ 0]); b = rotateLeft( b, 30); + d += s1(e, a, b, c,w[ 1]); a = rotateLeft( a, 30); + c += s1(d, e, a, b,w[ 2]); e = rotateLeft( e, 30); + b += s1(c, d, e, a,w[ 3]); d = rotateLeft( d, 30); + a += s1(b, c, d, e,w[ 4]); c = rotateLeft( c, 30); + e += s1(a, b, c, d,w[ 5]); b = rotateLeft( b, 30); + d += s1(e, a, b, c,w[ 6]); a = rotateLeft( a, 30); + c += s1(d, e, a, b,w[ 7]); e = rotateLeft( e, 30); + b += s1(c, d, e, a,w[ 8]); d = rotateLeft( d, 30); + a += s1(b, c, d, e,w[ 9]); c = rotateLeft( c, 30); + e += s1(a, b, c, d,w[ 10]); b = rotateLeft( b, 30); + d += s1(e, a, b, c,w[ 11]); a = rotateLeft( a, 30); + c += s1(d, e, a, b,w[ 12]); e = rotateLeft( e, 30); + b += s1(c, d, e, a,w[ 13]); d = rotateLeft( d, 30); + a += s1(b, c, d, e,w[ 14]); c = rotateLeft( c, 30); + e += s1(a, b, c, d,w[ 15]); b = rotateLeft( b, 30); + d += s1(e, a, b, c,w[ 16]); a = rotateLeft( a, 30); + c += s1(d, e, a, b,w[ 17]); e = rotateLeft( e, 30); + b += s1(c, d, e, a,w[ 18]); d = rotateLeft( d, 30); + a += s1(b, c, d, e,w[ 19]); c = rotateLeft( c, 30); + + e += s2(a, b, c, d,w[ 20]); b = rotateLeft( b, 30); + d += s2(e, a, b, c,w[ 21]); a = rotateLeft( a, 30); + c += s2(d, e, a, b,w[ 22]); e = rotateLeft( e, 30); + b += s2(c, d, e, a,w[ 23]); d = rotateLeft( d, 30); + a += s2(b, c, d, e,w[ 24]); c = rotateLeft( c, 30); + e += s2(a, b, c, d,w[ 25]); b = rotateLeft( b, 30); + d += s2(e, a, b, c,w[ 26]); a = rotateLeft( a, 30); + c += s2(d, e, a, b,w[ 27]); e = rotateLeft( e, 30); + b += s2(c, d, e, a,w[ 28]); d = rotateLeft( d, 30); + a += s2(b, c, d, e,w[ 29]); c = rotateLeft( c, 30); + e += s2(a, b, c, d,w[ 30]); b = rotateLeft( b, 30); + d += s2(e, a, b, c,w[ 31]); a = rotateLeft( a, 30); + c += s2(d, e, a, b,w[ 32]); e = rotateLeft( e, 30); + b += s2(c, d, e, a,w[ 33]); d = rotateLeft( d, 30); + a += s2(b, c, d, e,w[ 34]); c = rotateLeft( c, 30); + e += s2(a, b, c, d,w[ 35]); b = rotateLeft( b, 30); + d += s2(e, a, b, c,w[ 36]); a = rotateLeft( a, 30); + c += s2(d, e, a, b,w[ 37]); e = rotateLeft( e, 30); + b += s2(c, d, e, a,w[ 38]); d = rotateLeft( d, 30); + a += s2(b, c, d, e,w[ 39]); c = rotateLeft( c, 30); + + e += s3(a, b, c, d,w[ 40]); b = rotateLeft( b, 30); + d += s3(e, a, b, c,w[ 41]); a = rotateLeft( a, 30); + c += s3(d, e, a, b,w[ 42]); e = rotateLeft( e, 30); + b += s3(c, d, e, a,w[ 43]); d = rotateLeft( d, 30); + a += s3(b, c, d, e,w[ 44]); c = rotateLeft( c, 30); + e += s3(a, b, c, d,w[ 45]); b = rotateLeft( b, 30); + d += s3(e, a, b, c,w[ 46]); a = rotateLeft( a, 30); + c += s3(d, e, a, b,w[ 47]); e = rotateLeft( e, 30); + b += s3(c, d, e, a,w[ 48]); d = rotateLeft( d, 30); + a += s3(b, c, d, e,w[ 49]); c = rotateLeft( c, 30); + e += s3(a, b, c, d,w[ 50]); b = rotateLeft( b, 30); + d += s3(e, a, b, c,w[ 51]); a = rotateLeft( a, 30); + c += s3(d, e, a, b,w[ 52]); e = rotateLeft( e, 30); + b += s3(c, d, e, a,w[ 53]); d = rotateLeft( d, 30); + a += s3(b, c, d, e,w[ 54]); c = rotateLeft( c, 30); + e += s3(a, b, c, d,w[ 55]); b = rotateLeft( b, 30); + d += s3(e, a, b, c,w[ 56]); a = rotateLeft( a, 30); + c += s3(d, e, a, b,w[ 57]); e = rotateLeft( e, 30); + state58.save(a, b, c, d, e); + b += s3(c, d, e, a,w[ 58]); d = rotateLeft( d, 30); + a += s3(b, c, d, e,w[ 59]); c = rotateLeft( c, 30); + + e += s4(a, b, c, d,w[ 60]); b = rotateLeft( b, 30); + d += s4(e, a, b, c,w[ 61]); a = rotateLeft( a, 30); + c += s4(d, e, a, b,w[ 62]); e = rotateLeft( e, 30); + b += s4(c, d, e, a,w[ 63]); d = rotateLeft( d, 30); + a += s4(b, c, d, e,w[ 64]); c = rotateLeft( c, 30); + state65.save(a, b, c, d, e); + e += s4(a, b, c, d,w[ 65]); b = rotateLeft( b, 30); + d += s4(e, a, b, c,w[ 66]); a = rotateLeft( a, 30); + c += s4(d, e, a, b,w[ 67]); e = rotateLeft( e, 30); + b += s4(c, d, e, a,w[ 68]); d = rotateLeft( d, 30); + a += s4(b, c, d, e,w[ 69]); c = rotateLeft( c, 30); + e += s4(a, b, c, d,w[ 70]); b = rotateLeft( b, 30); + d += s4(e, a, b, c,w[ 71]); a = rotateLeft( a, 30); + c += s4(d, e, a, b,w[ 72]); e = rotateLeft( e, 30); + b += s4(c, d, e, a,w[ 73]); d = rotateLeft( d, 30); + a += s4(b, c, d, e,w[ 74]); c = rotateLeft( c, 30); + e += s4(a, b, c, d,w[ 75]); b = rotateLeft( b, 30); + d += s4(e, a, b, c,w[ 76]); a = rotateLeft( a, 30); + c += s4(d, e, a, b,w[ 77]); e = rotateLeft( e, 30); + b += s4(c, d, e, a,w[ 78]); d = rotateLeft( d, 30); + a += s4(b, c, d, e,w[ 79]); c = rotateLeft( c, 30); + + // @formatter:on + h.save(h.a + a, h.b + b, h.c + c, h.d + d, h.e + e); + } + + private void recompress(int t) { + State s; + if (t == 58) { + s = state58; + } else if (t == 65) { + s = state65; + } else { + throw new IllegalStateException(); + } + int a = s.a, b = s.b, c = s.c, d = s.d, e = s.e; + + // @formatter:off + if (t == 65) { + { c = rotateRight( c, 30); a -= s4(b, c, d, e,w2[ 64]);} + { d = rotateRight( d, 30); b -= s4(c, d, e, a,w2[ 63]);} + { e = rotateRight( e, 30); c -= s4(d, e, a, b,w2[ 62]);} + { a = rotateRight( a, 30); d -= s4(e, a, b, c,w2[ 61]);} + { b = rotateRight( b, 30); e -= s4(a, b, c, d,w2[ 60]);} + + { c = rotateRight( c, 30); a -= s3(b, c, d, e,w2[ 59]);} + { d = rotateRight( d, 30); b -= s3(c, d, e, a,w2[ 58]);} + } + { e = rotateRight( e, 30); c -= s3(d, e, a, b,w2[ 57]);} + { a = rotateRight( a, 30); d -= s3(e, a, b, c,w2[ 56]);} + { b = rotateRight( b, 30); e -= s3(a, b, c, d,w2[ 55]);} + { c = rotateRight( c, 30); a -= s3(b, c, d, e,w2[ 54]);} + { d = rotateRight( d, 30); b -= s3(c, d, e, a,w2[ 53]);} + { e = rotateRight( e, 30); c -= s3(d, e, a, b,w2[ 52]);} + { a = rotateRight( a, 30); d -= s3(e, a, b, c,w2[ 51]);} + { b = rotateRight( b, 30); e -= s3(a, b, c, d,w2[ 50]);} + { c = rotateRight( c, 30); a -= s3(b, c, d, e,w2[ 49]);} + { d = rotateRight( d, 30); b -= s3(c, d, e, a,w2[ 48]);} + { e = rotateRight( e, 30); c -= s3(d, e, a, b,w2[ 47]);} + { a = rotateRight( a, 30); d -= s3(e, a, b, c,w2[ 46]);} + { b = rotateRight( b, 30); e -= s3(a, b, c, d,w2[ 45]);} + { c = rotateRight( c, 30); a -= s3(b, c, d, e,w2[ 44]);} + { d = rotateRight( d, 30); b -= s3(c, d, e, a,w2[ 43]);} + { e = rotateRight( e, 30); c -= s3(d, e, a, b,w2[ 42]);} + { a = rotateRight( a, 30); d -= s3(e, a, b, c,w2[ 41]);} + { b = rotateRight( b, 30); e -= s3(a, b, c, d,w2[ 40]);} + + { c = rotateRight( c, 30); a -= s2(b, c, d, e,w2[ 39]);} + { d = rotateRight( d, 30); b -= s2(c, d, e, a,w2[ 38]);} + { e = rotateRight( e, 30); c -= s2(d, e, a, b,w2[ 37]);} + { a = rotateRight( a, 30); d -= s2(e, a, b, c,w2[ 36]);} + { b = rotateRight( b, 30); e -= s2(a, b, c, d,w2[ 35]);} + { c = rotateRight( c, 30); a -= s2(b, c, d, e,w2[ 34]);} + { d = rotateRight( d, 30); b -= s2(c, d, e, a,w2[ 33]);} + { e = rotateRight( e, 30); c -= s2(d, e, a, b,w2[ 32]);} + { a = rotateRight( a, 30); d -= s2(e, a, b, c,w2[ 31]);} + { b = rotateRight( b, 30); e -= s2(a, b, c, d,w2[ 30]);} + { c = rotateRight( c, 30); a -= s2(b, c, d, e,w2[ 29]);} + { d = rotateRight( d, 30); b -= s2(c, d, e, a,w2[ 28]);} + { e = rotateRight( e, 30); c -= s2(d, e, a, b,w2[ 27]);} + { a = rotateRight( a, 30); d -= s2(e, a, b, c,w2[ 26]);} + { b = rotateRight( b, 30); e -= s2(a, b, c, d,w2[ 25]);} + { c = rotateRight( c, 30); a -= s2(b, c, d, e,w2[ 24]);} + { d = rotateRight( d, 30); b -= s2(c, d, e, a,w2[ 23]);} + { e = rotateRight( e, 30); c -= s2(d, e, a, b,w2[ 22]);} + { a = rotateRight( a, 30); d -= s2(e, a, b, c,w2[ 21]);} + { b = rotateRight( b, 30); e -= s2(a, b, c, d,w2[ 20]);} + + { c = rotateRight( c, 30); a -= s1(b, c, d, e,w2[ 19]);} + { d = rotateRight( d, 30); b -= s1(c, d, e, a,w2[ 18]);} + { e = rotateRight( e, 30); c -= s1(d, e, a, b,w2[ 17]);} + { a = rotateRight( a, 30); d -= s1(e, a, b, c,w2[ 16]);} + { b = rotateRight( b, 30); e -= s1(a, b, c, d,w2[ 15]);} + { c = rotateRight( c, 30); a -= s1(b, c, d, e,w2[ 14]);} + { d = rotateRight( d, 30); b -= s1(c, d, e, a,w2[ 13]);} + { e = rotateRight( e, 30); c -= s1(d, e, a, b,w2[ 12]);} + { a = rotateRight( a, 30); d -= s1(e, a, b, c,w2[ 11]);} + { b = rotateRight( b, 30); e -= s1(a, b, c, d,w2[ 10]);} + { c = rotateRight( c, 30); a -= s1(b, c, d, e,w2[ 9]);} + { d = rotateRight( d, 30); b -= s1(c, d, e, a,w2[ 8]);} + { e = rotateRight( e, 30); c -= s1(d, e, a, b,w2[ 7]);} + { a = rotateRight( a, 30); d -= s1(e, a, b, c,w2[ 6]);} + { b = rotateRight( b, 30); e -= s1(a, b, c, d,w2[ 5]);} + { c = rotateRight( c, 30); a -= s1(b, c, d, e,w2[ 4]);} + { d = rotateRight( d, 30); b -= s1(c, d, e, a,w2[ 3]);} + { e = rotateRight( e, 30); c -= s1(d, e, a, b,w2[ 2]);} + { a = rotateRight( a, 30); d -= s1(e, a, b, c,w2[ 1]);} + { b = rotateRight( b, 30); e -= s1(a, b, c, d,w2[ 0]);} + + hIn.save(a, b, c, d, e); + a = s.a; b = s.b; c = s.c; d = s.d; e = s.e; + + if (t == 58) { + { b += s3(c, d, e, a,w2[ 58]); d = rotateLeft( d, 30);} + { a += s3(b, c, d, e,w2[ 59]); c = rotateLeft( c, 30);} + + { e += s4(a, b, c, d,w2[ 60]); b = rotateLeft( b, 30);} + { d += s4(e, a, b, c,w2[ 61]); a = rotateLeft( a, 30);} + { c += s4(d, e, a, b,w2[ 62]); e = rotateLeft( e, 30);} + { b += s4(c, d, e, a,w2[ 63]); d = rotateLeft( d, 30);} + { a += s4(b, c, d, e,w2[ 64]); c = rotateLeft( c, 30);} + } + { e += s4(a, b, c, d,w2[ 65]); b = rotateLeft( b, 30);} + { d += s4(e, a, b, c,w2[ 66]); a = rotateLeft( a, 30);} + { c += s4(d, e, a, b,w2[ 67]); e = rotateLeft( e, 30);} + { b += s4(c, d, e, a,w2[ 68]); d = rotateLeft( d, 30);} + { a += s4(b, c, d, e,w2[ 69]); c = rotateLeft( c, 30);} + { e += s4(a, b, c, d,w2[ 70]); b = rotateLeft( b, 30);} + { d += s4(e, a, b, c,w2[ 71]); a = rotateLeft( a, 30);} + { c += s4(d, e, a, b,w2[ 72]); e = rotateLeft( e, 30);} + { b += s4(c, d, e, a,w2[ 73]); d = rotateLeft( d, 30);} + { a += s4(b, c, d, e,w2[ 74]); c = rotateLeft( c, 30);} + { e += s4(a, b, c, d,w2[ 75]); b = rotateLeft( b, 30);} + { d += s4(e, a, b, c,w2[ 76]); a = rotateLeft( a, 30);} + { c += s4(d, e, a, b,w2[ 77]); e = rotateLeft( e, 30);} + { b += s4(c, d, e, a,w2[ 78]); d = rotateLeft( d, 30);} + { a += s4(b, c, d, e,w2[ 79]); c = rotateLeft( c, 30);} + + // @formatter:on + hTmp.save(hIn.a + a, hIn.b + b, hIn.c + c, hIn.d + d, hIn.e + e); + } + + private static int s1(int a, int b, int c, int d, int w_t) { + return rotateLeft(a, 5) + // f: 0 <= t <= 19 + + ((b & c) | ((~b) & d)) + + 0x5A827999 + w_t; + } + + private static int s2(int a, int b, int c, int d, int w_t) { + return rotateLeft(a, 5) + // f: 20 <= t <= 39 + + (b ^ c ^ d) + + 0x6ED9EBA1 + w_t; + } + + private static int s3(int a, int b, int c, int d, int w_t) { + return rotateLeft(a, 5) + // f: 40 <= t <= 59 + + ((b & c) | (b & d) | (c & d)) + + 0x8F1BBCDC + w_t; + } + + private static int s4(int a, int b, int c, int d, int w_t) { + return rotateLeft(a, 5) + // f: 60 <= t <= 79 + + (b ^ c ^ d) + + 0xCA62C1D6 + w_t; + } + + private static boolean eq(State q, State r) { + return q.a == r.a + && q.b == r.b + && q.c == r.c + && q.d == r.d + && q.e == r.e; + } + + private void finish() { + int bufferLen = (int) (length & 63); + if (bufferLen > 55) { + // Last block is too small; pad, compress, pad another block. + buffer[bufferLen++] = (byte) 0x80; + Arrays.fill(buffer, bufferLen, 64, (byte) 0); + compress(buffer, 0); + Arrays.fill(buffer, 0, 56, (byte) 0); + } else { + // Last block can hold padding and length. + buffer[bufferLen++] = (byte) 0x80; + Arrays.fill(buffer, bufferLen, 56, (byte) 0); + } + + // SHA-1 appends the length of the message in bits after the + // padding block (above). Here length is in bytes. Multiply by + // 8 by shifting by 3 as part of storing the 64 bit byte length + // into the two words expected in the trailer. + NB.encodeInt32(buffer, 56, (int) (length >>> (32 - 3))); + NB.encodeInt32(buffer, 60, (int) (length << 3)); + compress(buffer, 0); + + if (foundCollision) { + ObjectId id = h.toObjectId(); + LOG.warn("possible SHA-1 collision " + id.name()); //$NON-NLS-1$ + throw new Sha1CollisionException(id); + } + } + + /** + * Finish the digest and return the resulting hash. + *

        + * Once {@code digest()} is called, this instance should be discarded. + * + * @return the bytes for the resulting hash. + * @throws org.eclipse.jgit.util.sha1.Sha1CollisionException + * if a collision was detected and safeHash is false. + */ + public byte[] digest() throws Sha1CollisionException { + finish(); + + byte[] b = new byte[20]; + NB.encodeInt32(b, 0, h.a); + NB.encodeInt32(b, 4, h.b); + NB.encodeInt32(b, 8, h.c); + NB.encodeInt32(b, 12, h.d); + NB.encodeInt32(b, 16, h.e); + return b; + } + + /** + * Finish the digest and return the resulting hash. + *

        + * Once {@code digest()} is called, this instance should be discarded. + * + * @return the ObjectId for the resulting hash. + * @throws org.eclipse.jgit.util.sha1.Sha1CollisionException + * if a collision was detected and safeHash is false. + */ + public ObjectId toObjectId() throws Sha1CollisionException { + finish(); + return h.toObjectId(); + } + + /** + * Finish the digest and return the resulting hash. + *

        + * Once {@code digest()} is called, this instance should be discarded. + * + * @param id + * destination to copy the digest to. + * @throws org.eclipse.jgit.util.sha1.Sha1CollisionException + * if a collision was detected and safeHash is false. + */ + public void digest(MutableObjectId id) throws Sha1CollisionException { + finish(); + id.set(h.a, h.b, h.c, h.d, h.e); + } + + /** + * Check if a collision was detected. + * + *

        + * This method only returns an accurate result after the digest was obtained + * through {@link #digest()}, {@link #digest(MutableObjectId)} or + * {@link #toObjectId()}, as the hashing function must finish processing to + * know the final state. + * + * @return {@code true} if a likely collision was detected. + */ + public boolean hasCollision() { + return foundCollision; + } + + /** + * Reset this instance to compute another hash. + * + * @return {@code this}. + */ + public SHA1 reset() { + h.init(); + length = 0; + foundCollision = false; + return this; + } + + private static final class State { + int a; + int b; + int c; + int d; + int e; + + final void init() { + // Magic initialization constants defined by FIPS180. + save(0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0); + } + + final void save(int a1, int b1, int c1, int d1, int e1) { + a = a1; + b = b1; + c = c1; + d = d1; + e = e1; + } + + ObjectId toObjectId() { + return new ObjectId(a, b, c, d, e); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,1040 @@ +/* +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* MIT License +* +* Copyright (c) 2017: +* Marc Stevens +* Cryptology Group +* Centrum Wiskunde & Informatica +* P.O. Box 94079, 1090 GB Amsterdam, Netherlands +* marc@marc-stevens.nl +* +* Dan Shumow +* Microsoft Research +* danshu@microsoft.com +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +package org.eclipse.jgit.util.sha1; + +// Converted by hand by Shawn Pearce (Google), using lib/ubc_check.c from +// https://github.com/cr-marcstevens/sha1collisiondetection/ +// +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// Array DV contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section + +final class UbcCheck { + private static final int DV_I_43_0_bit = 1 << 0; + private static final int DV_I_44_0_bit = 1 << 1; + private static final int DV_I_45_0_bit = 1 << 2; + private static final int DV_I_46_0_bit = 1 << 3; + private static final int DV_I_46_2_bit = 1 << 4; + private static final int DV_I_47_0_bit = 1 << 5; + private static final int DV_I_47_2_bit = 1 << 6; + private static final int DV_I_48_0_bit = 1 << 7; + private static final int DV_I_48_2_bit = 1 << 8; + private static final int DV_I_49_0_bit = 1 << 9; + private static final int DV_I_49_2_bit = 1 << 10; + private static final int DV_I_50_0_bit = 1 << 11; + private static final int DV_I_50_2_bit = 1 << 12; + private static final int DV_I_51_0_bit = 1 << 13; + private static final int DV_I_51_2_bit = 1 << 14; + private static final int DV_I_52_0_bit = 1 << 15; + private static final int DV_II_45_0_bit = 1 << 16; + private static final int DV_II_46_0_bit = 1 << 17; + private static final int DV_II_46_2_bit = 1 << 18; + private static final int DV_II_47_0_bit = 1 << 19; + private static final int DV_II_48_0_bit = 1 << 20; + private static final int DV_II_49_0_bit = 1 << 21; + private static final int DV_II_49_2_bit = 1 << 22; + private static final int DV_II_50_0_bit = 1 << 23; + private static final int DV_II_50_2_bit = 1 << 24; + private static final int DV_II_51_0_bit = 1 << 25; + private static final int DV_II_51_2_bit = 1 << 26; + private static final int DV_II_52_0_bit = 1 << 27; + private static final int DV_II_53_0_bit = 1 << 28; + private static final int DV_II_54_0_bit = 1 << 29; + private static final int DV_II_55_0_bit = 1 << 30; + private static final int DV_II_56_0_bit = 1 << 31; + + static int check(int[] w) { + int mask = ~0; + mask &= (((((w[44] ^ w[45]) >>> 29) & 1) - 1) | ~(DV_I_48_0_bit + | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit + | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit)); + mask &= (((((w[49] ^ w[50]) >>> 29) & 1) - 1) + | ~(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit + | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= (((((w[48] ^ w[49]) >>> 29) & 1) - 1) + | ~(DV_I_45_0_bit | DV_I_52_0_bit | DV_II_49_0_bit + | DV_II_50_0_bit | DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= ((((w[47] ^ (w[50] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit + | DV_II_45_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)); + mask &= (((((w[47] ^ w[48]) >>> 29) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_51_0_bit | DV_II_48_0_bit + | DV_II_49_0_bit | DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= (((((w[46] >>> 4) ^ (w[49] >>> 29)) & 1) - 1) + | ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit + | DV_I_52_0_bit | DV_II_50_0_bit | DV_II_55_0_bit)); + mask &= (((((w[46] ^ w[47]) >>> 29) & 1) - 1) + | ~(DV_I_43_0_bit | DV_I_50_0_bit | DV_II_47_0_bit + | DV_II_48_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)); + mask &= (((((w[45] >>> 4) ^ (w[48] >>> 29)) & 1) - 1) + | ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit + | DV_I_51_0_bit | DV_II_49_0_bit | DV_II_54_0_bit)); + mask &= (((((w[45] ^ w[46]) >>> 29) & 1) - 1) + | ~(DV_I_49_0_bit | DV_I_52_0_bit | DV_II_46_0_bit + | DV_II_47_0_bit | DV_II_51_0_bit | DV_II_52_0_bit)); + mask &= (((((w[44] >>> 4) ^ (w[47] >>> 29)) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit + | DV_I_50_0_bit | DV_II_48_0_bit | DV_II_53_0_bit)); + mask &= (((((w[43] >>> 4) ^ (w[46] >>> 29)) & 1) - 1) + | ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit + | DV_I_49_0_bit | DV_II_47_0_bit | DV_II_52_0_bit)); + mask &= (((((w[43] ^ w[44]) >>> 29) & 1) - 1) + | ~(DV_I_47_0_bit | DV_I_50_0_bit | DV_I_51_0_bit + | DV_II_45_0_bit | DV_II_49_0_bit | DV_II_50_0_bit)); + mask &= (((((w[42] >>> 4) ^ (w[45] >>> 29)) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit + | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_51_0_bit)); + mask &= (((((w[41] >>> 4) ^ (w[44] >>> 29)) & 1) - 1) + | ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit + | DV_I_51_0_bit | DV_II_45_0_bit | DV_II_50_0_bit)); + mask &= (((((w[40] ^ w[41]) >>> 29) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_47_0_bit | DV_I_48_0_bit + | DV_II_46_0_bit | DV_II_47_0_bit | DV_II_56_0_bit)); + mask &= (((((w[54] ^ w[55]) >>> 29) & 1) - 1) + | ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_50_0_bit + | DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= (((((w[53] ^ w[54]) >>> 29) & 1) - 1) + | ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_49_0_bit + | DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= (((((w[52] ^ w[53]) >>> 29) & 1) - 1) + | ~(DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit + | DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= ((((w[50] ^ (w[53] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit + | DV_II_48_0_bit | DV_II_54_0_bit)); + mask &= (((((w[50] ^ w[51]) >>> 29) & 1) - 1) + | ~(DV_I_47_0_bit | DV_II_46_0_bit | DV_II_51_0_bit + | DV_II_52_0_bit | DV_II_56_0_bit)); + mask &= ((((w[49] ^ (w[52] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit + | DV_II_47_0_bit | DV_II_53_0_bit)); + mask &= ((((w[48] ^ (w[51] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit + | DV_II_46_0_bit | DV_II_52_0_bit)); + mask &= (((((w[42] ^ w[43]) >>> 29) & 1) - 1) + | ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_I_50_0_bit + | DV_II_48_0_bit | DV_II_49_0_bit)); + mask &= (((((w[41] ^ w[42]) >>> 29) & 1) - 1) + | ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_I_49_0_bit + | DV_II_47_0_bit | DV_II_48_0_bit)); + mask &= (((((w[40] >>> 4) ^ (w[43] >>> 29)) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_50_0_bit + | DV_II_49_0_bit | DV_II_56_0_bit)); + mask &= (((((w[39] >>> 4) ^ (w[42] >>> 29)) & 1) - 1) + | ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_49_0_bit + | DV_II_48_0_bit | DV_II_55_0_bit)); + if ((mask & (DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit + | DV_II_54_0_bit | DV_II_56_0_bit)) != 0) + mask &= (((((w[38] >>> 4) ^ (w[41] >>> 29)) & 1) - 1) + | ~(DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit + | DV_II_54_0_bit | DV_II_56_0_bit)); + mask &= (((((w[37] >>> 4) ^ (w[40] >>> 29)) & 1) - 1) + | ~(DV_I_43_0_bit | DV_I_47_0_bit | DV_II_46_0_bit + | DV_II_53_0_bit | DV_II_55_0_bit)); + if ((mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit + | DV_II_56_0_bit)) != 0) + mask &= (((((w[55] ^ w[56]) >>> 29) & 1) - 1) | ~(DV_I_52_0_bit + | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)); + if ((mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit + | DV_II_56_0_bit)) != 0) + mask &= ((((w[52] ^ (w[55] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit + | DV_II_56_0_bit)); + if ((mask & (DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit + | DV_II_55_0_bit)) != 0) + mask &= ((((w[51] ^ (w[54] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit + | DV_II_55_0_bit)); + if ((mask & (DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit + | DV_II_53_0_bit)) != 0) + mask &= (((((w[51] ^ w[52]) >>> 29) & 1) - 1) | ~(DV_I_48_0_bit + | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)); + if ((mask & (DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit + | DV_II_48_0_bit)) != 0) + mask &= (((((w[36] >>> 4) ^ (w[40] >>> 29)) & 1) - 1) + | ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit + | DV_II_48_0_bit)); + if ((mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)) != 0) + mask &= ((0 - (((w[53] ^ w[56]) >>> 29) & 1)) + | ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)); + if ((mask & (DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)) != 0) + mask &= ((0 - (((w[51] ^ w[54]) >>> 29) & 1)) + | ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)); + if ((mask & (DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)) != 0) + mask &= ((0 - (((w[50] ^ w[52]) >>> 29) & 1)) + | ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)); + if ((mask & (DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)) != 0) + mask &= ((0 - (((w[49] ^ w[51]) >>> 29) & 1)) + | ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)); + if ((mask & (DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)) != 0) + mask &= ((0 - (((w[48] ^ w[50]) >>> 29) & 1)) + | ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)); + if ((mask & (DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)) != 0) + mask &= ((0 - (((w[47] ^ w[49]) >>> 29) & 1)) + | ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)); + if ((mask & (DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)) != 0) + mask &= ((0 - (((w[46] ^ w[48]) >>> 29) & 1)) + | ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)); + mask &= ((((w[45] ^ w[47]) & (1 << 6)) - (1 << 6)) + | ~(DV_I_47_2_bit | DV_I_49_2_bit | DV_I_51_2_bit)); + if ((mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)) != 0) + mask &= ((0 - (((w[45] ^ w[47]) >>> 29) & 1)) + | ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)); + mask &= (((((w[44] ^ w[46]) >>> 6) & 1) - 1) + | ~(DV_I_46_2_bit | DV_I_48_2_bit | DV_I_50_2_bit)); + if ((mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)) != 0) + mask &= ((0 - (((w[44] ^ w[46]) >>> 29) & 1)) + | ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)); + mask &= ((0 - ((w[41] ^ (w[42] >>> 5)) & (1 << 1))) + | ~(DV_I_48_2_bit | DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= ((0 - ((w[40] ^ (w[41] >>> 5)) & (1 << 1))) + | ~(DV_I_47_2_bit | DV_I_51_2_bit | DV_II_50_2_bit)); + if ((mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)) != 0) + mask &= ((0 - (((w[40] ^ w[42]) >>> 4) & 1)) + | ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)); + mask &= ((0 - ((w[39] ^ (w[40] >>> 5)) & (1 << 1))) + | ~(DV_I_46_2_bit | DV_I_50_2_bit | DV_II_49_2_bit)); + if ((mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)) != 0) + mask &= ((0 - (((w[39] ^ w[41]) >>> 4) & 1)) + | ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)); + if ((mask & (DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)) != 0) + mask &= ((0 - (((w[38] ^ w[40]) >>> 4) & 1)) + | ~(DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)); + if ((mask & (DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)) != 0) + mask &= ((0 - (((w[37] ^ w[39]) >>> 4) & 1)) + | ~(DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)); + mask &= ((0 - ((w[36] ^ (w[37] >>> 5)) & (1 << 1))) + | ~(DV_I_47_2_bit | DV_I_50_2_bit | DV_II_46_2_bit)); + if ((mask & (DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)) != 0) + mask &= (((((w[35] >>> 4) ^ (w[39] >>> 29)) & 1) - 1) + | ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)); + if ((mask & (DV_I_48_0_bit | DV_II_48_0_bit)) != 0) + mask &= ((0 - ((w[63] ^ (w[64] >>> 5)) & (1 << 0))) + | ~(DV_I_48_0_bit | DV_II_48_0_bit)); + if ((mask & (DV_I_45_0_bit | DV_II_45_0_bit)) != 0) + mask &= ((0 - ((w[63] ^ (w[64] >>> 5)) & (1 << 1))) + | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if ((mask & (DV_I_47_0_bit | DV_II_47_0_bit)) != 0) + mask &= ((0 - ((w[62] ^ (w[63] >>> 5)) & (1 << 0))) + | ~(DV_I_47_0_bit | DV_II_47_0_bit)); + if ((mask & (DV_I_46_0_bit | DV_II_46_0_bit)) != 0) + mask &= ((0 - ((w[61] ^ (w[62] >>> 5)) & (1 << 0))) + | ~(DV_I_46_0_bit | DV_II_46_0_bit)); + mask &= ((0 - ((w[61] ^ (w[62] >>> 5)) & (1 << 2))) + | ~(DV_I_46_2_bit | DV_II_46_2_bit)); + if ((mask & (DV_I_45_0_bit | DV_II_45_0_bit)) != 0) + mask &= ((0 - ((w[60] ^ (w[61] >>> 5)) & (1 << 0))) + | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if ((mask & (DV_II_51_0_bit | DV_II_54_0_bit)) != 0) + mask &= (((((w[58] ^ w[59]) >>> 29) & 1) - 1) + | ~(DV_II_51_0_bit | DV_II_54_0_bit)); + if ((mask & (DV_II_50_0_bit | DV_II_53_0_bit)) != 0) + mask &= (((((w[57] ^ w[58]) >>> 29) & 1) - 1) + | ~(DV_II_50_0_bit | DV_II_53_0_bit)); + if ((mask & (DV_II_52_0_bit | DV_II_54_0_bit)) != 0) + mask &= ((((w[56] ^ (w[59] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_II_52_0_bit | DV_II_54_0_bit)); + if ((mask & (DV_II_51_0_bit | DV_II_52_0_bit)) != 0) + mask &= ((0 - (((w[56] ^ w[59]) >>> 29) & 1)) + | ~(DV_II_51_0_bit | DV_II_52_0_bit)); + if ((mask & (DV_II_49_0_bit | DV_II_52_0_bit)) != 0) + mask &= (((((w[56] ^ w[57]) >>> 29) & 1) - 1) + | ~(DV_II_49_0_bit | DV_II_52_0_bit)); + if ((mask & (DV_II_51_0_bit | DV_II_53_0_bit)) != 0) + mask &= ((((w[55] ^ (w[58] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_II_51_0_bit | DV_II_53_0_bit)); + if ((mask & (DV_II_50_0_bit | DV_II_52_0_bit)) != 0) + mask &= ((((w[54] ^ (w[57] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_II_50_0_bit | DV_II_52_0_bit)); + if ((mask & (DV_II_49_0_bit | DV_II_51_0_bit)) != 0) + mask &= ((((w[53] ^ (w[56] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_II_49_0_bit | DV_II_51_0_bit)); + mask &= ((((w[51] ^ (w[50] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + mask &= ((((w[48] ^ w[50]) & (1 << 6)) - (1 << 6)) + | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + if ((mask & (DV_I_51_0_bit | DV_I_52_0_bit)) != 0) + mask &= ((0 - (((w[48] ^ w[55]) >>> 29) & 1)) + | ~(DV_I_51_0_bit | DV_I_52_0_bit)); + mask &= ((((w[47] ^ w[49]) & (1 << 6)) - (1 << 6)) + | ~(DV_I_49_2_bit | DV_I_51_2_bit)); + mask &= ((((w[48] ^ (w[47] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_I_47_2_bit | DV_II_51_2_bit)); + mask &= ((((w[46] ^ w[48]) & (1 << 6)) - (1 << 6)) + | ~(DV_I_48_2_bit | DV_I_50_2_bit)); + mask &= ((((w[47] ^ (w[46] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_I_46_2_bit | DV_II_50_2_bit)); + mask &= ((0 - ((w[44] ^ (w[45] >>> 5)) & (1 << 1))) + | ~(DV_I_51_2_bit | DV_II_49_2_bit)); + mask &= ((((w[43] ^ w[45]) & (1 << 6)) - (1 << 6)) + | ~(DV_I_47_2_bit | DV_I_49_2_bit)); + mask &= (((((w[42] ^ w[44]) >>> 6) & 1) - 1) + | ~(DV_I_46_2_bit | DV_I_48_2_bit)); + mask &= ((((w[43] ^ (w[42] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= ((((w[42] ^ (w[41] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_I_51_2_bit | DV_II_50_2_bit)); + mask &= ((((w[41] ^ (w[40] >>> 5)) & (1 << 1)) - (1 << 1)) + | ~(DV_I_50_2_bit | DV_II_49_2_bit)); + if ((mask & (DV_I_52_0_bit | DV_II_51_0_bit)) != 0) + mask &= ((((w[39] ^ (w[43] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_52_0_bit | DV_II_51_0_bit)); + if ((mask & (DV_I_51_0_bit | DV_II_50_0_bit)) != 0) + mask &= ((((w[38] ^ (w[42] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_51_0_bit | DV_II_50_0_bit)); + if ((mask & (DV_I_48_2_bit | DV_I_51_2_bit)) != 0) + mask &= ((0 - ((w[37] ^ (w[38] >>> 5)) & (1 << 1))) + | ~(DV_I_48_2_bit | DV_I_51_2_bit)); + if ((mask & (DV_I_50_0_bit | DV_II_49_0_bit)) != 0) + mask &= ((((w[37] ^ (w[41] >>> 25)) & (1 << 4)) - (1 << 4)) + | ~(DV_I_50_0_bit | DV_II_49_0_bit)); + if ((mask & (DV_II_52_0_bit | DV_II_54_0_bit)) != 0) + mask &= ((0 - ((w[36] ^ w[38]) & (1 << 4))) + | ~(DV_II_52_0_bit | DV_II_54_0_bit)); + mask &= ((0 - ((w[35] ^ (w[36] >>> 5)) & (1 << 1))) + | ~(DV_I_46_2_bit | DV_I_49_2_bit)); + if ((mask & (DV_I_51_0_bit | DV_II_47_0_bit)) != 0) + mask &= ((((w[35] ^ (w[39] >>> 25)) & (1 << 3)) - (1 << 3)) + | ~(DV_I_51_0_bit | DV_II_47_0_bit)); + + if (mask == 0) { + return mask; + } + + if ((mask & DV_I_43_0_bit) != 0) + if (0 == ((w[61] ^ (w[62] >>> 5)) & (1 << 1)) + || 0 != ((w[59] ^ (w[63] >>> 25)) & (1 << 5)) + || 0 == ((w[58] ^ (w[63] >>> 30)) & (1 << 0))) + mask &= ~DV_I_43_0_bit; + if ((mask & DV_I_44_0_bit) != 0) + if (0 == ((w[62] ^ (w[63] >>> 5)) & (1 << 1)) + || 0 != ((w[60] ^ (w[64] >>> 25)) & (1 << 5)) + || 0 == ((w[59] ^ (w[64] >>> 30)) & (1 << 0))) + mask &= ~DV_I_44_0_bit; + if ((mask & DV_I_46_2_bit) != 0) + mask &= ((~((w[40] ^ w[42]) >>> 2)) | ~DV_I_46_2_bit); + if ((mask & DV_I_47_2_bit) != 0) + if (0 == ((w[62] ^ (w[63] >>> 5)) & (1 << 2)) + || 0 != ((w[41] ^ w[43]) & (1 << 6))) + mask &= ~DV_I_47_2_bit; + if ((mask & DV_I_48_2_bit) != 0) + if (0 == ((w[63] ^ (w[64] >>> 5)) & (1 << 2)) + || 0 != ((w[48] ^ (w[49] << 5)) & (1 << 6))) + mask &= ~DV_I_48_2_bit; + if ((mask & DV_I_49_2_bit) != 0) + if (0 != ((w[49] ^ (w[50] << 5)) & (1 << 6)) + || 0 == ((w[42] ^ w[50]) & (1 << 1)) + || 0 != ((w[39] ^ (w[40] << 5)) & (1 << 6)) + || 0 == ((w[38] ^ w[40]) & (1 << 1))) + mask &= ~DV_I_49_2_bit; + if ((mask & DV_I_50_0_bit) != 0) + mask &= ((((w[36] ^ w[37]) << 7)) | ~DV_I_50_0_bit); + if ((mask & DV_I_50_2_bit) != 0) + mask &= ((((w[43] ^ w[51]) << 11)) | ~DV_I_50_2_bit); + if ((mask & DV_I_51_0_bit) != 0) + mask &= ((((w[37] ^ w[38]) << 9)) | ~DV_I_51_0_bit); + if ((mask & DV_I_51_2_bit) != 0) + if (0 != ((w[51] ^ (w[52] << 5)) & (1 << 6)) + || 0 != ((w[49] ^ w[51]) & (1 << 6)) + || 0 != ((w[37] ^ (w[37] >>> 5)) & (1 << 1)) + || 0 != ((w[35] ^ (w[39] >>> 25)) & (1 << 5))) + mask &= ~DV_I_51_2_bit; + if ((mask & DV_I_52_0_bit) != 0) + mask &= ((((w[38] ^ w[39]) << 11)) | ~DV_I_52_0_bit); + if ((mask & DV_II_46_2_bit) != 0) + mask &= ((((w[47] ^ w[51]) << 17)) | ~DV_II_46_2_bit); + if ((mask & DV_II_48_0_bit) != 0) + if (0 != ((w[36] ^ (w[40] >>> 25)) & (1 << 3)) + || 0 == ((w[35] ^ (w[40] << 2)) & (1 << 30))) + mask &= ~DV_II_48_0_bit; + if ((mask & DV_II_49_0_bit) != 0) + if (0 != ((w[37] ^ (w[41] >>> 25)) & (1 << 3)) + || 0 == ((w[36] ^ (w[41] << 2)) & (1 << 30))) + mask &= ~DV_II_49_0_bit; + if ((mask & DV_II_49_2_bit) != 0) + if (0 != ((w[53] ^ (w[54] << 5)) & (1 << 6)) + || 0 != ((w[51] ^ w[53]) & (1 << 6)) + || 0 == ((w[50] ^ w[54]) & (1 << 1)) + || 0 != ((w[45] ^ (w[46] << 5)) & (1 << 6)) + || 0 != ((w[37] ^ (w[41] >>> 25)) & (1 << 5)) + || 0 == ((w[36] ^ (w[41] >>> 30)) & (1 << 0))) + mask &= ~DV_II_49_2_bit; + if ((mask & DV_II_50_0_bit) != 0) + if (0 == ((w[55] ^ w[58]) & (1 << 29)) + || 0 != ((w[38] ^ (w[42] >>> 25)) & (1 << 3)) + || 0 == ((w[37] ^ (w[42] << 2)) & (1 << 30))) + mask &= ~DV_II_50_0_bit; + if ((mask & DV_II_50_2_bit) != 0) + if (0 != ((w[54] ^ (w[55] << 5)) & (1 << 6)) + || 0!=((w[52] ^ w[54]) & (1 << 6)) + || 0==((w[51] ^ w[55]) & (1 << 1)) + || 0==((w[45] ^ w[47]) & (1 << 1)) + || 0!=((w[38] ^ (w[42] >>> 25)) & (1 << 5)) + || 0==((w[37] ^ (w[42] >>> 30)) & (1 << 0))) + mask &= ~DV_II_50_2_bit; + if ((mask & DV_II_51_0_bit) != 0) + if (0 != ((w[39] ^ (w[43] >>> 25)) & (1 << 3)) + || 0 == ((w[38] ^ (w[43] << 2)) & (1 << 30))) + mask &= ~DV_II_51_0_bit; + if ((mask & DV_II_51_2_bit) != 0) + if (0 != ((w[55] ^ (w[56] << 5)) & (1 << 6)) + || 0 != ((w[53] ^ w[55]) & (1 << 6)) + || 0 == ((w[52] ^ w[56]) & (1 << 1)) + || 0 == ((w[46] ^ w[48]) & (1 << 1)) + || 0 != ((w[39] ^ (w[43] >>> 25)) & (1 << 5)) + || 0 == ((w[38] ^ (w[43] >>> 30)) & (1 << 0))) + mask &= ~DV_II_51_2_bit; + if ((mask & DV_II_52_0_bit) != 0) + if (0 != ((w[59] ^ w[60]) & (1 << 29)) + || 0 != ((w[40] ^ (w[44] >>> 25)) & (1 << 3)) + || 0 != ((w[40] ^ (w[44] >>> 25)) & (1 << 4)) + || 0==((w[39] ^ (w[44] << 2)) & (1 << 30))) + mask &= ~DV_II_52_0_bit; + if ((mask & DV_II_53_0_bit) != 0) + if (0==((w[58] ^ w[61]) & (1 << 29)) + || 0!=((w[57] ^ (w[61] >>> 25)) & (1 << 4)) + || 0!=((w[41] ^ (w[45] >>> 25)) & (1 << 3)) + || 0!=((w[41] ^ (w[45] >>> 25)) & (1 << 4))) + mask &= ~DV_II_53_0_bit; + if ((mask & DV_II_54_0_bit) != 0) + if (0 != ((w[58] ^ (w[62] >>> 25)) & (1 << 4)) + || 0 != ((w[42] ^ (w[46] >>> 25)) & (1 << 3)) + || 0 != ((w[42] ^ (w[46] >>> 25)) & (1 << 4))) + mask &= ~DV_II_54_0_bit; + if ((mask & DV_II_55_0_bit) != 0) + if (0 != ((w[59] ^ (w[63] >>> 25)) & (1 << 4)) + || 0 != ((w[57] ^ (w[59] >>> 25)) & (1 << 4)) + || 0 != ((w[43] ^ (w[47] >>> 25)) & (1 << 3)) + || 0 != ((w[43] ^ (w[47] >>> 25)) & (1 << 4))) + mask &= ~DV_II_55_0_bit; + if ((mask & DV_II_56_0_bit) != 0) + if (0 != ((w[60] ^ (w[64] >>> 25)) & (1 << 4)) + || 0 != ((w[44] ^ (w[48] >>> 25)) & (1 << 3)) + || 0 != ((w[44] ^ (w[48] >>> 25)) & (1 << 4))) + mask &= ~DV_II_56_0_bit; + return mask; + } + + private UbcCheck() { + } + + static final class DvInfo { + final int testt; + final int maskb; + final int[] dm; + + @SuppressWarnings("unused") + DvInfo(int dvType, int dvK, int dvB, int testt, int maskb, int[] dm) { + this.testt = testt; + this.maskb = maskb; + this.dm = dm; + + // Only states 58 and 65 are saved. + if (testt != 58 && testt != 65) { + throw new IllegalArgumentException(); + } + } + } + + static final DvInfo[] DV = new DvInfo[] { + new DvInfo(1, 43, 0, 58, 0, new int[] { 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, + 0x00000803, 0x80000161, 0x80000599 }), + new DvInfo(1, 44, 0, 58, 1, new int[] { 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, + 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, + 0x8000004c, 0x00000803, 0x80000161 }), + new DvInfo(1, 45, 0, 58, 2, new int[] { 0xf4000014, 0xb4000008, + 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, + 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, + 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, + 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, + 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, + 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, + 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, + 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, + 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, + 0x800000e6, 0x8000004c, 0x00000803 }), + new DvInfo(1, 46, 0, 58, 3, new int[] { 0x2c000010, 0xf4000014, + 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, + 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, + 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, + 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, + 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, + 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, + 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, + 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, + 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, + 0x00000408, 0x800000e6, 0x8000004c }), + new DvInfo(1, 46, 2, 58, 4, new int[] { 0xb0000040, 0xd0000053, + 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040, + 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, + 0x00000001, 0x40000002, 0xc0000043, 0x40000062, 0x80000001, + 0x40000042, 0x40000042, 0x40000002, 0x00000002, 0x00000040, + 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000101, 0x00000009, + 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, + 0x00001020, 0x0000039a, 0x00000132 }), + new DvInfo(1, 47, 0, 58, 5, new int[] { 0xc8000010, 0x2c000010, + 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, + 0x00000164, 0x00000408, 0x800000e6 }), + new DvInfo(1, 47, 2, 58, 6, new int[] { 0x20000043, 0xb0000040, + 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, + 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, + 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, + 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, + 0xc0000043, 0x40000022, 0x00000003, 0x40000042, 0xc0000043, + 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, 0x40000062, + 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002, + 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, + 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101, + 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, + 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, + 0x00000590, 0x00001020, 0x0000039a }), + new DvInfo(1, 48, 0, 58, 7, new int[] { 0xb800000a, 0xc8000010, + 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164, 0x00000408 }), + new DvInfo(1, 48, 2, 58, 8, new int[] { 0xe000002a, 0x20000043, + 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, + 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, + 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, + 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001, + 0x40000042, 0xc0000043, 0x40000022, 0x00000003, 0x40000042, + 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, + 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, + 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, + 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, + 0x80000000, 0x00000040, 0x80000002, 0x00000000, 0x80000000, + 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, + 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, + 0x00000060, 0x00000590, 0x00001020 }), + new DvInfo(1, 49, 0, 58, 9, new int[] { 0x18000000, 0xb800000a, + 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, + 0x80000202, 0x00000018, 0x00000164 }), + new DvInfo(1, 49, 2, 58, 10, new int[] { 0x60000000, 0xe000002a, + 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, + 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, + 0x80000001, 0x00000020, 0x00000003, 0x40000052, 0x40000040, + 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, 0x20000060, + 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x00000003, + 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, + 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, + 0x40000002, 0x00000002, 0x00000040, 0x80000002, 0x80000000, + 0x80000002, 0x80000040, 0x00000000, 0x80000040, 0x80000000, + 0x00000040, 0x80000000, 0x00000040, 0x80000002, 0x00000000, + 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, + 0x00000009, 0x00000101, 0x00000009, 0x00000012, 0x00000202, + 0x0000001a, 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, + 0x0000080a, 0x00000060, 0x00000590 }), + new DvInfo(1, 50, 0, 65, 11, new int[] { 0x0800000c, 0x18000000, + 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, + 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, + 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, + 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, + 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, + 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, + 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, + 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, + 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, + 0x80000012, 0x80000202, 0x00000018 }), + new DvInfo(1, 50, 2, 65, 12, new int[] { 0x20000030, 0x60000000, + 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, + 0x20000000, 0x60000032, 0x60000043, 0x20000040, 0xe0000042, + 0x60000002, 0x80000001, 0x00000020, 0x00000003, 0x40000052, + 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, + 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, + 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, + 0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042, + 0x40000042, 0x40000002, 0x00000002, 0x00000040, 0x80000002, + 0x80000000, 0x80000002, 0x80000040, 0x00000000, 0x80000040, + 0x80000000, 0x00000040, 0x80000000, 0x00000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000004, 0x00000080, + 0x00000004, 0x00000009, 0x00000101, 0x00000009, 0x00000012, + 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, 0x00000026, + 0x0000004a, 0x0000080a, 0x00000060 }), + new DvInfo(1, 51, 0, 65, 13, new int[] { 0xe8000000, 0x0800000c, + 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, + 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, + 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, + 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, + 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, + 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, + 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, + 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, + 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, + 0x80000009, 0x80000012, 0x80000202 }), + new DvInfo(1, 51, 2, 65, 14, new int[] { 0xa0000003, 0x20000030, + 0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, + 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040, + 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, + 0x00000001, 0x40000002, 0xc0000043, 0x40000062, 0x80000001, + 0x40000042, 0x40000042, 0x40000002, 0x00000002, 0x00000040, + 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000101, 0x00000009, + 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a }), + new DvInfo(1, 52, 0, 65, 15, new int[] { 0x04000010, 0xe8000000, + 0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, + 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012 }), + new DvInfo(2, 45, 0, 58, 16, new int[] { 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, + 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a, + 0x000002e4, 0x80000054, 0x00000967 }), + new DvInfo(2, 46, 0, 58, 17, new int[] { 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, + 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d, + 0x8000041a, 0x000002e4, 0x80000054 }), + new DvInfo(2, 46, 2, 58, 18, new int[] { 0x90000070, 0xb0000053, + 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062, + 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, 0xe0000041, + 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, + 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, + 0x60000042, 0x80000002, 0x00000000, 0x00000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000000, + 0x00000040, 0x80000001, 0x00000060, 0x80000003, 0x40000002, + 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, + 0x00000040, 0x00000002, 0x80000000, 0x80000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000105, 0x00000089, + 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e, + 0x00000224, 0x00000050, 0x0000092e, 0x0000046c, 0x000005b6, + 0x0000106a, 0x00000b90, 0x00000152 }), + new DvInfo(2, 47, 0, 58, 19, new int[] { 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, + 0x8000016d, 0x8000041a, 0x000002e4 }), + new DvInfo(2, 48, 0, 58, 20, new int[] { 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b, + 0x0000011b, 0x8000016d, 0x8000041a }), + new DvInfo(2, 49, 0, 58, 21, new int[] { 0x3c000004, 0xbc00001a, + 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014, + 0x8000024b, 0x0000011b, 0x8000016d }), + new DvInfo(2, 49, 2, 58, 22, new int[] { 0xf0000010, 0xf000006a, + 0x80000040, 0x90000070, 0xb0000053, 0x30000008, 0x00000043, + 0xd0000072, 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, + 0xe0000042, 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, + 0xe0000072, 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, + 0x20000001, 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, + 0x00000000, 0x00000000, 0x80000000, 0x00000002, 0x00000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000001, + 0x00000060, 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, + 0x80000000, 0x80000000, 0x80000002, 0x00000040, 0x00000002, + 0x80000000, 0x80000000, 0x80000000, 0x00000002, 0x00000040, + 0x00000000, 0x80000040, 0x80000002, 0x00000000, 0x80000000, + 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, + 0x00000009, 0x00000105, 0x00000089, 0x00000016, 0x0000020b, + 0x0000011b, 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, + 0x0000092e, 0x0000046c, 0x000005b6 }), + new DvInfo(2, 50, 0, 65, 23, new int[] { 0xb400001c, 0x3c000004, + 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, + 0x00000014, 0x8000024b, 0x0000011b }), + new DvInfo(2, 50, 2, 65, 24, new int[] { 0xd0000072, 0xf0000010, + 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, 0x30000008, + 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062, 0xc0000042, + 0x00000030, 0xe0000042, 0x20000060, 0xe0000041, 0x20000050, + 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, 0x60000041, + 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, 0x60000042, + 0x80000002, 0x00000000, 0x00000000, 0x80000000, 0x00000002, + 0x00000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, + 0x80000001, 0x00000060, 0x80000003, 0x40000002, 0xc0000040, + 0xc0000002, 0x80000000, 0x80000000, 0x80000002, 0x00000040, + 0x00000002, 0x80000000, 0x80000000, 0x80000000, 0x00000002, + 0x00000040, 0x00000000, 0x80000040, 0x80000002, 0x00000000, + 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000004, 0x00000080, + 0x00000004, 0x00000009, 0x00000105, 0x00000089, 0x00000016, + 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e, 0x00000224, + 0x00000050, 0x0000092e, 0x0000046c }), + new DvInfo(2, 51, 0, 65, 25, new int[] { 0xc0000010, 0xb400001c, + 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, + 0x00000089, 0x00000014, 0x8000024b }), + new DvInfo(2, 51, 2, 65, 26, new int[] { 0x00000043, 0xd0000072, + 0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, + 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062, + 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, 0xe0000041, + 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, + 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, + 0x60000042, 0x80000002, 0x00000000, 0x00000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000000, + 0x00000040, 0x80000001, 0x00000060, 0x80000003, 0x40000002, + 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, + 0x00000040, 0x00000002, 0x80000000, 0x80000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000105, 0x00000089, + 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e, + 0x00000224, 0x00000050, 0x0000092e }), + new DvInfo(2, 52, 0, 65, 27, new int[] { 0x0c000002, 0xc0000010, + 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014 }), + new DvInfo(2, 53, 0, 65, 28, new int[] { 0xcc000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089 }), + new DvInfo(2, 54, 0, 65, 29, new int[] { 0x0400001c, 0xcc000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, + 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107 }), + new DvInfo(2, 55, 0, 65, 30, new int[] { 0x00000010, 0x0400001c, + 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, + 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b }), + new DvInfo(2, 56, 0, 65, 31, new int[] { 0x2600001a, 0x00000010, + 0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046 }), }; + + static { + // Assert the DV array is indexed by maskb; that is DV block using + // maskb = N must be at array index N. + for (int i = 0; i < DV.length; i++) { + if (i != DV[i].maskb) { + throw new IllegalStateException("must be indexed by maskb"); //$NON-NLS-1$ + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,9 @@ import org.eclipse.jgit.internal.JGitText; -/** Miscellaneous string comparison utility methods. */ +/** + * Miscellaneous string comparison utility methods. + */ public final class StringUtils { private static final char[] LC; @@ -102,10 +104,12 @@ * *

        * Capitalizes a String changing the first letter to title case as per - * {@link Character#toTitleCase(char)}. No other letters are changed. + * {@link java.lang.Character#toTitleCase(char)}. No other letters are + * changed. + *

        + *

        + * A null input String returns null. *

        - * - * A null input String returns null.

        * * @param str * the String to capitalize, may be null @@ -156,9 +160,8 @@ * first string to compare. * @param b * second string to compare. - * @return negative, zero or positive if a sorts before, is equal to, or - * sorts after b. * @since 2.0 + * @return an int. */ public static int compareIgnoreCase(String a, String b) { for (int i = 0; i < a.length() && i < b.length(); i++) { @@ -179,9 +182,8 @@ * first string to compare. * @param b * second string to compare. - * @return negative, zero or positive if a sorts before, is equal to, or - * sorts after b. * @since 2.0 + * @return an int. */ public static int compareWithCase(String a, String b) { for (int i = 0; i < a.length() && i < b.length(); i++) { @@ -199,7 +201,7 @@ * @param stringValue * the string to parse. * @return the boolean interpretation of {@code value}. - * @throws IllegalArgumentException + * @throws java.lang.IllegalArgumentException * if {@code value} is not recognized as one of the standard * boolean names. */ diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,10 +56,12 @@ import java.util.Locale; import java.util.TimeZone; -import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.time.MonotonicClock; +import org.eclipse.jgit.util.time.MonotonicSystemClock; /** * Interface to read values from the system. @@ -85,22 +87,27 @@ private static class Default extends SystemReader { private volatile String hostname; + @Override public String getenv(String variable) { return System.getenv(variable); } + @Override public String getProperty(String key) { return System.getProperty(key); } + @Override public FileBasedConfig openSystemConfig(Config parent, FS fs) { File configFile = fs.getGitSystemConfig(); if (configFile == null) { return new FileBasedConfig(null, fs) { + @Override public void load() { // empty, do not load } + @Override public boolean isOutdated() { // regular class would bomb here return false; @@ -110,11 +117,13 @@ return new FileBasedConfig(parent, configFile, fs); } + @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { final File home = fs.userHome(); return new FileBasedConfig(parent, new File(home, ".gitconfig"), fs); //$NON-NLS-1$ } + @Override public String getHostname() { if (hostname == null) { try { @@ -142,12 +151,18 @@ private static SystemReader INSTANCE = DEFAULT; - /** @return the live instance to read system properties. */ + /** + * Get time since epoch, with up to millisecond resolution. + * + * @return time since epoch, with up to millisecond resolution. + */ public static SystemReader getInstance() { return INSTANCE; } /** + * Set the new instance to use when accessing properties. + * * @param newReader * the new instance to use when accessing properties, or null for * the default instance. @@ -192,18 +207,26 @@ public abstract String getHostname(); /** - * @param variable system variable to read + * Get value of the system variable + * + * @param variable + * system variable to read * @return value of the system variable */ public abstract String getenv(String variable); /** - * @param key of the system property to read + * Get value of the system property + * + * @param key + * of the system property to read * @return value of the system property */ public abstract String getProperty(String key); /** + * Open the git configuration found in the user home + * * @param parent * a config with values not found directly in the returned config * @param fs @@ -214,6 +237,8 @@ public abstract FileBasedConfig openUserConfig(Config parent, FS fs); /** + * Open the gitonfig configuration found in the system-wide "etc" directory + * * @param parent * a config with values not found directly in the returned * config. Null is a reasonable value here. @@ -226,17 +251,34 @@ public abstract FileBasedConfig openSystemConfig(Config parent, FS fs); /** + * Get the current system time + * * @return the current system time */ public abstract long getCurrentTime(); /** - * @param when TODO + * Get clock instance preferred by this system. + * + * @return clock instance preferred by this system. + * @since 4.6 + */ + public MonotonicClock getClock() { + return new MonotonicSystemClock(); + } + + /** + * Get the local time zone + * + * @param when + * a system timestamp * @return the local time zone */ public abstract int getTimezone(long when); /** + * Get system time zone, possibly mocked for testing + * * @return system time zone, possibly mocked for testing * @since 1.2 */ @@ -245,6 +287,8 @@ } /** + * Get the locale to use + * * @return the locale to use * @since 1.2 */ @@ -257,7 +301,7 @@ * * @param pattern * the pattern as defined in - * {@link SimpleDateFormat#SimpleDateFormat(String)} + * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} * @return the simple date format * @since 2.0 */ @@ -270,7 +314,7 @@ * * @param pattern * the pattern as defined in - * {@link SimpleDateFormat#SimpleDateFormat(String)} + * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} * @param locale * locale to be used for the {@code SimpleDateFormat} * @return the simple date format @@ -285,10 +329,10 @@ * * @param dateStyle * the date style as specified in - * {@link DateFormat#getDateTimeInstance(int, int)} + * {@link java.text.DateFormat#getDateTimeInstance(int, int)} * @param timeStyle * the time style as specified in - * {@link DateFormat#getDateTimeInstance(int, int)} + * {@link java.text.DateFormat#getDateTimeInstance(int, int)} * @return the date format * @since 2.0 */ @@ -297,7 +341,9 @@ } /** - * @return true if we are running on a Windows. + * Whether we are running on Windows. + * + * @return true if we are running on Windows. */ public boolean isWindows() { if (isWindows == null) { @@ -308,6 +354,8 @@ } /** + * Whether we are running on Mac OS X + * * @return true if we are running on Mac OS X */ public boolean isMacOS() { @@ -321,6 +369,7 @@ private String getOsName() { return AccessController.doPrivileged(new PrivilegedAction() { + @Override public String run() { return getProperty("os.name"); //$NON-NLS-1$ } @@ -333,10 +382,25 @@ * Scans a multi-directory path string such as {@code "src/main.c"}. * * @param path path string to scan. - * @throws CorruptObjectException path is invalid. + * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid. * @since 3.6 */ public void checkPath(String path) throws CorruptObjectException { platformChecker.checkPath(path); } + + /** + * Check tree path entry for validity. + *

        + * Scans a multi-directory path string such as {@code "src/main.c"}. + * + * @param path + * path string to scan. + * @throws org.eclipse.jgit.errors.CorruptObjectException + * path is invalid. + * @since 4.2 + */ + public void checkPath(byte[] path) throws CorruptObjectException { + platformChecker.checkPath(path, 0, path.length); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,7 +56,6 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * A fully buffered output stream. @@ -69,7 +68,7 @@ protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024; /** Chain of data, if we are still completely in-core; otherwise null. */ - private ArrayList blocks; + ArrayList blocks; /** * Maximum number of bytes we will permit storing in memory. @@ -115,6 +114,7 @@ reset(); } + /** {@inheritDoc} */ @Override public void write(final int b) throws IOException { if (overflow != null) { @@ -135,6 +135,7 @@ s.buffer[s.count++] = (byte) b; } + /** {@inheritDoc} */ @Override public void write(final byte[] b, int off, int len) throws IOException { if (overflow == null) { @@ -163,7 +164,7 @@ /** * Dumps the entire buffer into the overflow stream, and flushes it. * - * @throws IOException + * @throws java.io.IOException * the overflow stream cannot be started, or the buffer contents * cannot be written to it, or it failed to flush. */ @@ -178,7 +179,7 @@ * * @param in * the stream to read from, until EOF is reached. - * @throws IOException + * @throws java.io.IOException * an error occurred reading from the input stream, or while * writing to a local temporary file. */ @@ -228,10 +229,8 @@ * The buffer is only complete after {@link #close()} has been invoked. * * @return the complete byte array; length matches {@link #length()}. - * @throws IOException + * @throws java.io.IOException * an error occurred reading from a local temporary file - * @throws OutOfMemoryError - * the buffer cannot fit in memory */ public byte[] toByteArray() throws IOException { final long len = length(); @@ -247,6 +246,33 @@ } /** + * Convert this buffer's contents into a contiguous byte array. If this size + * of the buffer exceeds the limit only return the first {@code limit} bytes + *

        + * The buffer is only complete after {@link #close()} has been invoked. + * + * @param limit + * the maximum number of bytes to be returned + * @return the byte array limited to {@code limit} bytes. + * @throws java.io.IOException + * an error occurred reading from a local temporary file + * @since 4.2 + */ + public byte[] toByteArray(int limit) throws IOException { + final long len = Math.min(length(), limit); + if (Integer.MAX_VALUE < len) + throw new OutOfMemoryError( + JGitText.get().lengthExceedsMaximumArraySize); + final byte[] out = new byte[(int) len]; + int outPtr = 0; + for (final Block b : blocks) { + System.arraycopy(b.buffer, 0, out, outPtr, b.count); + outPtr += b.count; + } + return out; + } + + /** * Send this buffer to an output stream. *

        * This method may only be invoked after {@link #close()} has completed @@ -258,7 +284,7 @@ * if not null progress updates are sent here. Caller should * initialize the task and the number of work units to * {@link #length()}/1024. - * @throws IOException + * @throws java.io.IOException * an error occurred reading from a temporary file on the local * system, or writing to the output stream. */ @@ -280,14 +306,36 @@ * * @return a stream to read from the buffer. The caller must close the * stream when it is no longer useful. - * @throws IOException + * @throws java.io.IOException * an error occurred opening the temporary file. */ public InputStream openInputStream() throws IOException { return new BlockInputStream(); } - /** Reset this buffer for reuse, purging all buffered content. */ + /** + * Same as {@link #openInputStream()} but handling destruction of any + * associated resources automatically when closing the returned stream. + * + * @return an InputStream which will automatically destroy any associated + * temporary file on {@link #close()} + * @throws IOException + * in case of an error. + * @since 4.11 + */ + public InputStream openInputStreamWithAutoDestroy() throws IOException { + return new BlockInputStream() { + @Override + public void close() throws IOException { + super.close(); + destroy(); + } + }; + } + + /** + * Reset this buffer for reuse, purging all buffered content. + */ public void reset() { if (overflow != null) { destroy(); @@ -295,7 +343,7 @@ if (blocks != null) blocks.clear(); else - blocks = new ArrayList(initialBlocks); + blocks = new ArrayList<>(initialBlocks); blocks.add(new Block(Math.min(inCoreLimit, Block.SZ))); } @@ -304,7 +352,7 @@ * * @return the output stream to receive the buffered content, followed by * the remaining output. - * @throws IOException + * @throws java.io.IOException * the buffer cannot create the overflow stream. */ protected abstract OutputStream overflow() throws IOException; @@ -329,10 +377,12 @@ overflow.write(b.buffer, 0, b.count); blocks = null; - overflow = new SafeBufferedOutputStream(overflow, Block.SZ); + overflow = new BufferedOutputStream(overflow, Block.SZ); overflow.write(last.buffer, 0, last.count); } + /** {@inheritDoc} */ + @Override public void close() throws IOException { if (overflow != null) { try { @@ -343,7 +393,9 @@ } } - /** Clear this buffer so it has no data, and cannot be used again. */ + /** + * Clear this buffer so it has no data, and cannot be used again. + */ public void destroy() { blocks = null; @@ -411,11 +463,13 @@ this.directory = directory; } + @Override protected OutputStream overflow() throws IOException { onDiskFile = File.createTempFile("jgit_", ".buf", directory); //$NON-NLS-1$ //$NON-NLS-2$ return new BufferedOutputStream(new FileOutputStream(onDiskFile)); } + @Override public long length() { if (onDiskFile == null) { return super.length(); @@ -423,6 +477,7 @@ return onDiskFile.length(); } + @Override public byte[] toByteArray() throws IOException { if (onDiskFile == null) { return super.toByteArray(); @@ -441,6 +496,7 @@ return out; } + @Override public void writeTo(final OutputStream os, ProgressMonitor pm) throws IOException { if (onDiskFile == null) { @@ -470,6 +526,20 @@ } @Override + public InputStream openInputStreamWithAutoDestroy() throws IOException { + if (onDiskFile == null) { + return super.openInputStreamWithAutoDestroy(); + } + return new FileInputStream(onDiskFile) { + @Override + public void close() throws IOException { + super.close(); + destroy(); + } + }; + } + + @Override public void destroy() { super.destroy(); diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicClock.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicClock.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicClock.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicClock.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016, 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.time; + +import java.time.Duration; + +/** + * A provider of time. + *

        + * Clocks should provide wall clock time, obtained from a reasonable clock + * source, such as the local system clock. + *

        + * MonotonicClocks provide the following behavior, with the assertion always + * being true if + * {@link org.eclipse.jgit.util.time.ProposedTimestamp#blockUntil(Duration)} is + * used: + * + *

        + *   MonotonicClock clk = ...;
        + *   long r1;
        + *   try (ProposedTimestamp t1 = clk.propose()) {
        + *   	r1 = t1.millis();
        + *   	t1.blockUntil(...);
        + *   }
        + *
        + *   try (ProposedTimestamp t2 = clk.propose()) {
        + *   	assert t2.millis() > r1;
        + *   }
        + * 
        + * + * @since 4.6 + */ +public interface MonotonicClock { + /** + * Obtain a timestamp close to "now". + *

        + * Proposed times are close to "now", but may not yet be certainly in the + * past. This allows the calling thread to interleave other useful work + * while waiting for the clock instance to create an assurance it will never + * in the future propose a time earlier than the returned time. + *

        + * A hypothetical implementation could read the local system clock (managed + * by NTP) and return that proposal, concurrently sending network messages + * to closely collaborating peers in the same cluster to also ensure their + * system clocks are ahead of this time. In such an implementation the + * {@link org.eclipse.jgit.util.time.ProposedTimestamp#blockUntil(Duration)} + * method would wait for replies from the peers indicating their own system + * clocks have moved past the proposed time. + * + * @return a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. + */ + ProposedTimestamp propose(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016, 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.time; + +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A {@link org.eclipse.jgit.util.time.MonotonicClock} based on + * {@code System.currentTimeMillis}. + * + * @since 4.6 + */ +public class MonotonicSystemClock implements MonotonicClock { + private static final AtomicLong before = new AtomicLong(); + + private static long nowMicros() { + long now = MILLISECONDS.toMicros(System.currentTimeMillis()); + for (;;) { + long o = before.get(); + long n = Math.max(o + 1, now); + if (before.compareAndSet(o, n)) { + return n; + } + } + } + + /** {@inheritDoc} */ + @Override + public ProposedTimestamp propose() { + final long u = nowMicros(); + return new ProposedTimestamp() { + @Override + public long read(TimeUnit unit) { + return unit.convert(u, MICROSECONDS); + } + + @Override + public void blockUntil(Duration maxWait) { + // Assume system clock never goes backwards. + } + }; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java --- jgit-4.1.2/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2016, 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.time; + +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A timestamp generated by + * {@link org.eclipse.jgit.util.time.MonotonicClock#propose()}. + *

        + * ProposedTimestamp implements AutoCloseable so that implementations can + * release resources associated with obtaining certainty about time elapsing. + * For example the constructing MonotonicClock may start network IO with peers + * when creating the ProposedTimestamp, and {@link #close()} can ensure those + * network resources are released in a timely fashion. + * + * @since 4.6 + */ +public abstract class ProposedTimestamp implements AutoCloseable { + /** + * Wait for several timestamps. + * + * @param times + * timestamps to wait on. + * @param maxWait + * how long to wait for the timestamps. + * @throws java.lang.InterruptedException + * current thread was interrupted before the waiting process + * completed normally. + * @throws java.util.concurrent.TimeoutException + * the timeout was reached without the proposed timestamp become + * certainly in the past. + */ + public static void blockUntil(Iterable times, + Duration maxWait) throws TimeoutException, InterruptedException { + Iterator itr = times.iterator(); + if (!itr.hasNext()) { + return; + } + + long now = System.currentTimeMillis(); + long deadline = now + maxWait.toMillis(); + for (;;) { + long w = deadline - now; + if (w < 0) { + throw new TimeoutException(); + } + itr.next().blockUntil(Duration.ofMillis(w)); + if (itr.hasNext()) { + now = System.currentTimeMillis(); + } else { + break; + } + } + } + + /** + * Read the timestamp as {@code unit} since the epoch. + *

        + * The timestamp value for a specific {@code ProposedTimestamp} object never + * changes, and can be read before {@link #blockUntil(Duration)}. + * + * @param unit + * what unit to return the timestamp in. The timestamp will be + * rounded if the unit is bigger than the clock's granularity. + * @return {@code unit} since the epoch. + */ + public abstract long read(TimeUnit unit); + + /** + * Wait for this proposed timestamp to be certainly in the recent past. + *

        + * This method forces the caller to wait up to {@code timeout} for + * {@code this} to pass sufficiently into the past such that the creating + * {@link org.eclipse.jgit.util.time.MonotonicClock} instance will not + * create an earlier timestamp. + * + * @param maxWait + * how long the implementation may block the caller. + * @throws java.lang.InterruptedException + * current thread was interrupted before the waiting process + * completed normally. + * @throws java.util.concurrent.TimeoutException + * the timeout was reached without the proposed timestamp + * becoming certainly in the past. + */ + public abstract void blockUntil(Duration maxWait) + throws InterruptedException, TimeoutException; + + /** + * Get milliseconds since epoch; {@code read(MILLISECONDS}). + * + * @return milliseconds since epoch; {@code read(MILLISECONDS}). + */ + public long millis() { + return read(MILLISECONDS); + } + + /** + * Get microseconds since epoch; {@code read(MICROSECONDS}). + * + * @return microseconds since epoch; {@code read(MICROSECONDS}). + */ + public long micros() { + return read(MICROSECONDS); + } + + /** + * Get time since epoch, with up to microsecond resolution. + * + * @return time since epoch, with up to microsecond resolution. + */ + public Instant instant() { + long usec = micros(); + long secs = usec / 1000000L; + long nanos = (usec % 1000000L) * 1000L; + return Instant.ofEpochSecond(secs, nanos); + } + + /** + * Get time since epoch, with up to microsecond resolution. + * + * @return time since epoch, with up to microsecond resolution. + */ + public Timestamp timestamp() { + return Timestamp.from(instant()); + } + + /** + * Get time since epoch, with up to millisecond resolution. + * + * @return time since epoch, with up to millisecond resolution. + */ + public Date date() { + return new Date(millis()); + } + + /** + * {@inheritDoc} + *

        + * Release resources allocated by this timestamp. + */ + @Override + public void close() { + // Do nothing by default. + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return instant().toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/.classpath jgit-4.11.9/org.eclipse.jgit.ant/.classpath --- jgit-4.1.2/org.eclipse.jgit.ant/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.ant/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.ant/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,12 +1,13 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name +Automatic-Module-Name: org.eclipse.jgit.ant Bundle-SymbolicName: org.eclipse.jgit.ant -Bundle-Version: 4.1.2.201602141800-r -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-Version: 4.11.9.201909030838-r +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.apache.tools.ant, - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)" + org.eclipse.jgit.storage.file;version="[4.11.9,4.12.0)" Bundle-Localization: plugin Bundle-Vendor: %Provider-Name -Export-Package: org.eclipse.jgit.ant.tasks;version="4.1.2"; +Export-Package: org.eclipse.jgit.ant.tasks;version="4.11.9"; uses:="org.apache.tools.ant.types,org.apache.tools.ant" diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/pom.xml jgit-4.11.9/org.eclipse.jgit.ant/pom.xml --- jgit-4.1.2/org.eclipse.jgit.ant/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.ant @@ -72,7 +72,7 @@ org.apache.ant ant - 1.9.2 + 1.9.6 @@ -102,24 +102,92 @@ - - org.codehaus.mojo - clirr-maven-plugin - + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + verify + + cmp + + + + - - - org.codehaus.mojo - clirr-maven-plugin - ${clirr-version} - - ${jgit-last-release-version} - info - - - - + + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + cmp-report + + + + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java --- jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,9 +53,10 @@ import org.apache.tools.ant.types.resources.Union; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; /** @@ -70,6 +71,8 @@ private Union path; /** + *

        Set the field src.

        + * * @param src * the src to set */ @@ -105,6 +108,7 @@ return path; } + /** {@inheritDoc} */ @Override public void execute() throws BuildException { if (src == null) { @@ -135,7 +139,7 @@ gitAdd.addFilepattern(toAdd); } gitAdd.call(); - } catch (Exception e) { + } catch (IOException | GitAPIException e) { throw new BuildException("Could not add files to index." + src, e); } diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java --- jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java 2019-09-03 12:37:49.000000000 +0000 @@ -67,6 +67,8 @@ private boolean force; /** + * Set the src + * * @param src * the src to set */ @@ -75,6 +77,8 @@ } /** + * Set branch + * * @param branch * the initial branch to check out */ @@ -83,6 +87,8 @@ } /** + * Set if branch should be created if not yet existing + * * @param createBranch * whether the branch should be created if it does not already * exist @@ -92,6 +98,8 @@ } /** + * Set force + * * @param force * if true and the branch with the given name * already exists, the start-point of an existing branch will be @@ -102,6 +110,7 @@ this.force = force; } + /** {@inheritDoc} */ @Override public void execute() throws BuildException { CheckoutCommand checkout; diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCloneTask.java jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCloneTask.java --- jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCloneTask.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCloneTask.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,12 +49,14 @@ import org.apache.tools.ant.Task; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.URIish; /** * Clone a repository into a new directory. - * + * * @see git-clone(1) */ @@ -66,6 +68,8 @@ private String branch = Constants.HEAD; /** + * Set the uri. + * * @param uri * the uri to clone from */ @@ -76,9 +80,8 @@ /** * The optional directory associated with the clone operation. If the * directory isn't set, a name associated with the source uri will be used. - * + * * @see URIish#getHumanishName() - * * @param destination * the directory to clone to */ @@ -87,6 +90,8 @@ } /** + * Set bare + * * @param bare * whether the cloned repository is bare or not */ @@ -95,6 +100,8 @@ } /** + * Set the branch + * * @param branch * the initial branch to check out when cloning the repository */ @@ -102,15 +109,16 @@ this.branch = branch; } + /** {@inheritDoc} */ @Override public void execute() throws BuildException { log("Cloning repository " + uri); - + CloneCommand clone = Git.cloneRepository(); try { clone.setURI(uri).setDirectory(destination).setBranch(branch).setBare(bare); clone.call().getRepository().close(); - } catch (Exception e) { + } catch (GitAPIException | JGitInternalException e) { log("Could not clone repository: " + e, e, Project.MSG_ERR); throw new BuildException("Could not clone repository: " + e.getMessage(), e); } diff -Nru jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitInitTask.java jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitInitTask.java --- jgit-4.1.2/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitInitTask.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitInitTask.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,6 +71,8 @@ } /** + * Configure if the repository should be bare + * * @param bare * whether the repository should be initialized to a bare * repository or not. @@ -79,6 +81,7 @@ this.bare = bare; } + /** {@inheritDoc} */ @Override public void execute() throws BuildException { if (bare) { diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/build.properties jgit-4.11.9/org.eclipse.jgit.ant.test/build.properties --- jgit-4.1.2/org.eclipse.jgit.ant.test/build.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -3,5 +3,5 @@ bin.includes = plugin.properties,\ META-INF/,\ . -jre.compilation.profile = JavaSE-1.7 +jre.compilation.profile = JavaSE-1.8 additional.bundles = org.eclipse.jgit diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/.classpath jgit-4.11.9/org.eclipse.jgit.ant.test/.classpath --- jgit-4.1.2/org.eclipse.jgit.ant.test/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -2,15 +2,15 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.ant.test Bundle-SymbolicName: org.eclipse.jgit.ant.test -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.apache.tools.ant, - 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)" + org.eclipse.jgit.ant.tasks;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.hamcrest.core;version="[1.1.0,2.0.0)", + org.junit;version="[4.12,5.0.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/pom.xml jgit-4.11.9/org.eclipse.jgit.ant.test/pom.xml --- jgit-4.1.2/org.eclipse.jgit.ant.test/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.ant.test diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 02:04:38 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/BUILD jgit-4.11.9/org.eclipse.jgit.archive/BUILD --- jgit-4.1.2/org.eclipse.jgit.archive/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,16 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "jgit-archive", + srcs = glob( + ["src/**/*.java"], + exclude = ["src/org/eclipse/jgit/archive/FormatActivator.java"], + ), + resource_strip_prefix = "org.eclipse.jgit.archive/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:commons-compress", + # We want these deps to be provided_deps + "//org.eclipse.jgit:jgit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/.classpath jgit-4.11.9/org.eclipse.jgit.archive/.classpath --- jgit-4.1.2/org.eclipse.jgit.archive/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,7 +1,7 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.archive/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.archive/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,27 +1,28 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.archive Bundle-SymbolicName: org.eclipse.jgit.archive -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Vendor: %provider_name Bundle-Localization: plugin -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.apache.commons.compress.archivers;version="[1.4,2.0)", org.apache.commons.compress.archivers.tar;version="[1.4,2.0)", org.apache.commons.compress.archivers.zip;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.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.eclipse.jgit.api;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.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.1.2"; +Export-Package: org.eclipse.jgit.archive;version="4.11.9"; 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 -Nru jgit-4.1.2/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -3,5 +3,5 @@ Bundle-Name: org.eclipse.jgit.archive - Sources Bundle-SymbolicName: org.eclipse.jgit.archive.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.1.2.201602141800-r -Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.1.2.201602141800-r";roots="." +Bundle-Version: 4.11.9.201909030838-r +Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.11.9.201909030838-r";roots="." diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/pom.xml jgit-4.11.9/org.eclipse.jgit.archive/pom.xml --- jgit-4.1.2/org.eclipse.jgit.archive/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,11 +50,11 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.archive - JGit Archive Formats + JGit - Archive Formats Support for archiving a Git tree in formats such as zip and tar. @@ -106,6 +106,93 @@ + + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + verify + + cmp + + + + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + cmp-report + + + + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,15 +1,15 @@ eclipse.preferences.version=1 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.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,14 +48,14 @@ import org.eclipse.jgit.api.ArchiveCommand; /** - * Registers all format types from the org.eclipse.jgit.archive - * package for use via the ArchiveCommand API. + * Registers all format types from the org.eclipse.jgit.archive package for use + * via the ArchiveCommand API. * - * See {@link FormatActivator} for an OSGi bundle activator - * that performs the same registration automatically. + * See {@link org.eclipse.jgit.archive.FormatActivator} for an OSGi bundle + * activator that performs the same registration automatically. */ public class ArchiveFormats { - private static final List myFormats = new ArrayList(); + private static final List myFormats = new ArrayList<>(); private static final void register(String name, ArchiveCommand.Format fmt) { myFormats.add(name); diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,24 +57,24 @@ */ public class FormatActivator implements BundleActivator { /** + * {@inheritDoc} + * * Registers all included archive formats by calling * {@link ArchiveFormats#registerAll()}. This method is called by the OSGi * framework when the bundle is started. - * - * @param context - * unused */ + @Override public void start(BundleContext context) { ArchiveFormats.registerAll(); } /** + * {@inheritDoc} + * * Cleans up after {@link #start(BundleContext)} by calling * {@link ArchiveFormats#unregisterAll}. - * - * @param context - * unused */ + @Override public void stop(BundleContext context) { ArchiveFormats.unregisterAll(); } diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/internal/ArchiveText.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,8 @@ */ public class ArchiveText extends TranslationBundle { /** + * Get an instance of this translation bundle. + * * @return an instance of this translation bundle */ public static ArchiveText get() { diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,7 +57,10 @@ import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.archive.internal.ArchiveText; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.revwalk.RevCommit; + /** * Unix TAR format (ustar + some PAX extensions). @@ -67,15 +70,16 @@ private static final List SUFFIXES = Collections .unmodifiableList(Arrays.asList(".tar")); //$NON-NLS-1$ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { TarArchiveOutputStream out = new TarArchiveOutputStream(s, "UTF-8"); //$NON-NLS-1$ @@ -84,9 +88,20 @@ return applyFormatOptions(out, o); } + /** {@inheritDoc} */ + @Deprecated + @Override public void putEntry(ArchiveOutputStream out, String path, FileMode mode, ObjectLoader loader) throws IOException { + putEntry(out, null, path, mode,loader); + } + + /** {@inheritDoc} */ + @Override + public void putEntry(ArchiveOutputStream out, + ObjectId tree, String path, FileMode mode, ObjectLoader loader) + throws IOException { if (mode == FileMode.SYMLINK) { final TarArchiveEntry entry = new TarArchiveEntry( path, TarConstants.LF_SYMLINK); @@ -106,6 +121,12 @@ path = path + "/"; //$NON-NLS-1$ final TarArchiveEntry entry = new TarArchiveEntry(path); + + if (tree instanceof RevCommit) { + long t = ((RevCommit) tree).getCommitTime() * 1000L; + entry.setModTime(t); + } + if (mode == FileMode.TREE) { out.putArchiveEntry(entry); out.closeArchiveEntry(); @@ -127,15 +148,19 @@ out.closeArchiveEntry(); } + /** {@inheritDoc} */ + @Override public Iterable suffixes() { return SUFFIXES; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { return (other instanceof TarFormat); } + /** {@inheritDoc} */ @Override public int hashCode() { return getClass().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; /** @@ -65,36 +66,52 @@ private final ArchiveCommand.Format tarFormat = new TarFormat(); + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(s); return tarFormat.createArchiveOutputStream(out, o); } + /** {@inheritDoc} */ + @Deprecated + @Override public void putEntry(ArchiveOutputStream out, String path, FileMode mode, ObjectLoader loader) throws IOException { - tarFormat.putEntry(out, path, mode, loader); + putEntry(out, null, path, mode,loader); } + /** {@inheritDoc} */ + @Override + public void putEntry(ArchiveOutputStream out, + ObjectId tree, String path, FileMode mode, ObjectLoader loader) + throws IOException { + tarFormat.putEntry(out, tree, path, mode, loader); + } + + /** {@inheritDoc} */ + @Override public Iterable suffixes() { return SUFFIXES; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { return (other instanceof Tbz2Format); } + /** {@inheritDoc} */ @Override public int hashCode() { return getClass().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; /** @@ -65,36 +66,52 @@ private final ArchiveCommand.Format tarFormat = new TarFormat(); + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { GzipCompressorOutputStream out = new GzipCompressorOutputStream(s); return tarFormat.createArchiveOutputStream(out, o); } + /** {@inheritDoc} */ + @Deprecated + @Override public void putEntry(ArchiveOutputStream out, String path, FileMode mode, ObjectLoader loader) throws IOException { - tarFormat.putEntry(out, path, mode, loader); + putEntry(out, null, path, mode,loader); } + /** {@inheritDoc} */ + @Override + public void putEntry(ArchiveOutputStream out, + ObjectId tree, String path, FileMode mode, ObjectLoader loader) + throws IOException { + tarFormat.putEntry(out, tree, path, mode, loader); + } + + /** {@inheritDoc} */ + @Override public Iterable suffixes() { return SUFFIXES; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { return (other instanceof TgzFormat); } + /** {@inheritDoc} */ @Override public int hashCode() { return getClass().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; /** @@ -65,36 +66,52 @@ private final ArchiveCommand.Format tarFormat = new TarFormat(); + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { XZCompressorOutputStream out = new XZCompressorOutputStream(s); return tarFormat.createArchiveOutputStream(out, o); } + /** {@inheritDoc} */ + @Deprecated + @Override public void putEntry(ArchiveOutputStream out, String path, FileMode mode, ObjectLoader loader) throws IOException { - tarFormat.putEntry(out, path, mode, loader); + putEntry(out, null, path, mode,loader); } + /** {@inheritDoc} */ + @Override + public void putEntry(ArchiveOutputStream out, + ObjectId tree, String path, FileMode mode, ObjectLoader loader) + throws IOException { + tarFormat.putEntry(out, tree, path, mode, loader); + } + + /** {@inheritDoc} */ + @Override public Iterable suffixes() { return SUFFIXES; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { return (other instanceof TxzFormat); } + /** {@inheritDoc} */ @Override public int hashCode() { return getClass().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java --- jgit-4.1.2/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,7 +56,9 @@ import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.archive.internal.ArchiveText; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.revwalk.RevCommit; /** * PKWARE's ZIP format. @@ -66,23 +68,35 @@ private static final List SUFFIXES = Collections .unmodifiableList(Arrays.asList(".zip")); //$NON-NLS-1$ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } - /** - * @since 4.0 - */ + /** {@inheritDoc} */ + @Override public ArchiveOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { return applyFormatOptions(new ZipArchiveOutputStream(s), o); } + /** {@inheritDoc} */ + @Deprecated + @Override public void putEntry(ArchiveOutputStream out, String path, FileMode mode, ObjectLoader loader) throws IOException { + putEntry(out, null, path, mode,loader); + } + + /** {@inheritDoc} */ + @Override + public void putEntry(ArchiveOutputStream out, + ObjectId tree, String path, FileMode mode, ObjectLoader loader) + throws IOException { // ZipArchiveEntry detects directories by checking // for '/' at the end of the filename. if (path.endsWith("/") && mode != FileMode.TREE) //$NON-NLS-1$ @@ -92,6 +106,12 @@ path = path + "/"; //$NON-NLS-1$ final ZipArchiveEntry entry = new ZipArchiveEntry(path); + + if (tree instanceof RevCommit) { + long t = ((RevCommit) tree).getCommitTime() * 1000L; + entry.setTime(t); + } + if (mode == FileMode.TREE) { out.putArchiveEntry(entry); out.closeArchiveEntry(); @@ -114,15 +134,19 @@ out.closeArchiveEntry(); } + /** {@inheritDoc} */ + @Override public Iterable suffixes() { return SUFFIXES; } + /** {@inheritDoc} */ @Override public boolean equals(Object other) { return (other instanceof ZipFormat); } + /** {@inheritDoc} */ @Override public int hashCode() { return getClass().hashCode(); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/BUILD jgit-4.11.9/org.eclipse.jgit.http.apache/BUILD --- jgit-4.1.2/org.eclipse.jgit.http.apache/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "http-apache", + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.http.apache/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:httpclient", + "//lib:httpcore", + "//org.eclipse.jgit:jgit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/.classpath jgit-4.11.9/org.eclipse.jgit.http.apache/.classpath --- jgit-4.1.2/org.eclipse.jgit.http.apache/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,31 +1,37 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name +Automatic-Module-Name: org.eclipse.jgit.http.apache Bundle-SymbolicName: org.eclipse.jgit.http.apache -Bundle-Version: 4.1.2.201602141800-r -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-Version: 4.11.9.201909030838-r +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 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)", - org.apache.http.client.params;version="[4.1.0,5.0.0)", - org.apache.http.conn;version="[4.1.0,5.0.0)", - org.apache.http.conn.params;version="[4.1.0,5.0.0)", - org.apache.http.conn.scheme;version="[4.1.0,5.0.0)", - org.apache.http.conn.ssl;version="[4.1.0,5.0.0)", - org.apache.http.entity;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.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, +Import-Package: org.apache.http;version="[4.3.0,5.0.0)", + org.apache.http.client;version="[4.3.0,5.0.0)", + org.apache.http.client.config;version="[4.3.0,5.0.0)", + org.apache.http.client.methods;version="[4.3.0,5.0.0)", + org.apache.http.client.params;version="[4.3.0,5.0.0)", + org.apache.http.config;version="[4.3.0,5.0.0)", + org.apache.http.conn;version="[4.3.0,5.0.0)", + org.apache.http.conn.params;version="[4.3.0,5.0.0)", + org.apache.http.conn.scheme;version="[4.3.0,5.0.0)", + org.apache.http.conn.socket;version="[4.3.0,5.0.0)", + org.apache.http.conn.ssl;version="[4.3.0,5.0.0)", + org.apache.http.entity;version="[4.3.0,5.0.0)", + org.apache.http.impl.client;version="[4.3.0,5.0.0)", + org.apache.http.impl.conn;version="[4.3.0,5.0.0)", + org.apache.http.params;version="[4.3.0,5.0.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)" +Export-Package: org.eclipse.jgit.transport.http.apache;version="4.11.9"; + uses:="org.apache.http.client, + org.eclipse.jgit.transport.http, + org.apache.http.entity, org.apache.http.client.methods, + javax.net.ssl, + org.eclipse.jgit.util, org.apache.http", org.eclipse.jgit.transport.http.apache.internal;x-internal:=true diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/pom.xml jgit-4.11.9/org.eclipse.jgit.http.apache/pom.xml --- jgit-4.1.2/org.eclipse.jgit.http.apache/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -48,7 +48,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.http.apache @@ -68,6 +68,14 @@ org.eclipse.jgit ${project.version} + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + @@ -96,24 +104,92 @@ - - org.codehaus.mojo - clirr-maven-plugin - + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + verify + + cmp + + + + - - - org.codehaus.mojo - clirr-maven-plugin - ${clirr-version} - - ${jgit-last-release-version} - info - - - - + + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + cmp-report + + + + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java --- jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,15 +50,20 @@ import org.eclipse.jgit.transport.http.HttpConnectionFactory; /** - * A factory returning instances of {@link HttpClientConnection} + * A factory returning instances of + * {@link org.eclipse.jgit.transport.http.apache.HttpClientConnection} * * @since 3.3 */ public class HttpClientConnectionFactory implements HttpConnectionFactory { + /** {@inheritDoc} */ + @Override public HttpConnection create(URL url) throws IOException { return new HttpClientConnection(url.toString()); } + /** {@inheritDoc} */ + @Override public HttpConnection create(URL url, Proxy proxy) throws IOException { return new HttpClientConnection(url.toString(), proxy); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java --- jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,11 @@ */ package org.eclipse.jgit.transport.http.apache; +import static org.eclipse.jgit.util.HttpSupport.METHOD_GET; +import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD; +import static org.eclipse.jgit.util.HttpSupport.METHOD_POST; +import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -53,7 +58,6 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -62,9 +66,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import org.apache.http.Header; @@ -75,32 +76,35 @@ import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.conn.params.ConnRoutePNames; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.conn.ssl.X509HostnameVerifier; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.HttpParams; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; /** - * A {@link HttpConnection} which uses {@link HttpClient} + * A {@link org.eclipse.jgit.transport.http.HttpConnection} which uses + * {@link org.apache.http.client.HttpClient} * * @since 3.3 */ public class HttpClientConnection implements HttpConnection { HttpClient client; - String urlStr; + URL url; HttpUriRequest req; @@ -120,34 +124,44 @@ private Boolean followRedirects; - private X509HostnameVerifier hostnameverifier; + private HostnameVerifier hostnameverifier; SSLContext ctx; private HttpClient getClient() { - if (client == null) - client = new DefaultHttpClient(); - HttpParams params = client.getParams(); - if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) { - isUsingProxy = true; - InetSocketAddress adr = (InetSocketAddress) proxy.address(); - params.setParameter(ConnRoutePNames.DEFAULT_PROXY, - new HttpHost(adr.getHostName(), adr.getPort())); - } - if (timeout != null) - params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, - timeout.intValue()); - if (readTimeout != null) - params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, - readTimeout.intValue()); - if (followRedirects != null) - params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, - followRedirects.booleanValue()); - if (hostnameverifier != null) { - SSLSocketFactory sf; - sf = new SSLSocketFactory(getSSLContext(), hostnameverifier); - Scheme https = new Scheme("https", 443, sf); //$NON-NLS-1$ - client.getConnectionManager().getSchemeRegistry().register(https); + if (client == null) { + HttpClientBuilder clientBuilder = HttpClients.custom(); + RequestConfig.Builder configBuilder = RequestConfig.custom(); + if (proxy != null && !Proxy.NO_PROXY.equals(proxy)) { + isUsingProxy = true; + InetSocketAddress adr = (InetSocketAddress) proxy.address(); + clientBuilder.setProxy( + new HttpHost(adr.getHostName(), adr.getPort())); + } + if (timeout != null) { + configBuilder.setConnectTimeout(timeout.intValue()); + } + if (readTimeout != null) { + configBuilder.setSocketTimeout(readTimeout.intValue()); + } + if (followRedirects != null) { + configBuilder + .setRedirectsEnabled(followRedirects.booleanValue()); + } + if (hostnameverifier != null) { + SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory( + getSSLContext(), hostnameverifier); + clientBuilder.setSSLSocketFactory(sslConnectionFactory); + Registry registry = RegistryBuilder + . create() + .register("https", sslConnectionFactory) + .register("http", PlainConnectionSocketFactory.INSTANCE) + .build(); + clientBuilder.setConnectionManager( + new BasicHttpClientConnectionManager(registry)); + } + clientBuilder.setDefaultRequestConfig(configBuilder.build()); + client = clientBuilder.build(); } return client; @@ -175,67 +189,90 @@ } /** + * Constructor for HttpClientConnection. + * * @param urlStr + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr) { + public HttpClientConnection(String urlStr) throws MalformedURLException { this(urlStr, null); } /** + * Constructor for HttpClientConnection. + * * @param urlStr * @param proxy + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr, Proxy proxy) { + public HttpClientConnection(String urlStr, Proxy proxy) + throws MalformedURLException { this(urlStr, proxy, null); } /** + * Constructor for HttpClientConnection. + * * @param urlStr * @param proxy * @param cl + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) { + public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) + throws MalformedURLException { this.client = cl; - this.urlStr = urlStr; + this.url = new URL(urlStr); this.proxy = proxy; } + /** {@inheritDoc} */ + @Override public int getResponseCode() throws IOException { execute(); return resp.getStatusLine().getStatusCode(); } + /** {@inheritDoc} */ + @Override public URL getURL() { - try { - return new URL(urlStr); - } catch (MalformedURLException e) { - return null; - } + return url; } + /** {@inheritDoc} */ + @Override public String getResponseMessage() throws IOException { execute(); return resp.getStatusLine().getReasonPhrase(); } private void execute() throws IOException, ClientProtocolException { - if (resp == null) - if (entity != null) { - if (req instanceof HttpEntityEnclosingRequest) { - HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req; - eReq.setEntity(entity); - } - resp = getClient().execute(req); - entity.getBuffer().close(); - entity = null; - } else - resp = getClient().execute(req); + if (resp != null) { + return; + } + + if (entity == null) { + resp = getClient().execute(req); + return; + } + + try { + if (req instanceof HttpEntityEnclosingRequest) { + HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req; + eReq.setEntity(entity); + } + resp = getClient().execute(req); + } finally { + entity.close(); + entity = null; + } } + /** {@inheritDoc} */ + @Override public Map> getHeaderFields() { - Map> ret = new HashMap>(); + Map> ret = new HashMap<>(); for (Header hdr : resp.getAllHeaders()) { - List list = new LinkedList(); + List list = new LinkedList<>(); for (HeaderElement hdrElem : hdr.getElements()) list.add(hdrElem.toString()); ret.put(hdr.getName(), list); @@ -243,36 +280,50 @@ return ret; } + /** {@inheritDoc} */ + @Override public void setRequestProperty(String name, String value) { req.addHeader(name, value); } + /** {@inheritDoc} */ + @Override public void setRequestMethod(String method) throws ProtocolException { this.method = method; - if ("GET".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpGet(urlStr); - else if ("PUT".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpPut(urlStr); - else if ("POST".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpPost(urlStr); - else { + if (METHOD_GET.equalsIgnoreCase(method)) { + req = new HttpGet(url.toString()); + } else if (METHOD_HEAD.equalsIgnoreCase(method)) { + req = new HttpHead(url.toString()); + } else if (METHOD_PUT.equalsIgnoreCase(method)) { + req = new HttpPut(url.toString()); + } else if (METHOD_POST.equalsIgnoreCase(method)) { + req = new HttpPost(url.toString()); + } else { this.method = null; throw new UnsupportedOperationException(); } } + /** {@inheritDoc} */ + @Override public void setUseCaches(boolean usecaches) { // not needed } + /** {@inheritDoc} */ + @Override public void setConnectTimeout(int timeout) { - this.timeout = new Integer(timeout); + this.timeout = Integer.valueOf(timeout); } + /** {@inheritDoc} */ + @Override public void setReadTimeout(int readTimeout) { - this.readTimeout = new Integer(readTimeout); + this.readTimeout = Integer.valueOf(readTimeout); } + /** {@inheritDoc} */ + @Override public String getContentType() { HttpEntity responseEntity = resp.getEntity(); if (responseEntity != null) { @@ -283,29 +334,50 @@ return null; } + /** {@inheritDoc} */ + @Override public InputStream getInputStream() throws IOException { return resp.getEntity().getContent(); } // will return only the first field + /** {@inheritDoc} */ + @Override public String getHeaderField(String name) { Header header = resp.getFirstHeader(name); return (header == null) ? null : header.getValue(); } + /** {@inheritDoc} */ + @Override public int getContentLength() { - return Integer.parseInt(resp.getFirstHeader("content-length") //$NON-NLS-1$ - .getValue()); + Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$ + if (contentLength == null) { + return -1; + } + + try { + int l = Integer.parseInt(contentLength.getValue()); + return l < 0 ? -1 : l; + } catch (NumberFormatException e) { + return -1; + } } + /** {@inheritDoc} */ + @Override public void setInstanceFollowRedirects(boolean followRedirects) { - this.followRedirects = new Boolean(followRedirects); + this.followRedirects = Boolean.valueOf(followRedirects); } + /** {@inheritDoc} */ + @Override public void setDoOutput(boolean dooutput) { // TODO: check whether we can really ignore this. } + /** {@inheritDoc} */ + @Override public void setFixedLengthStreamingMode(int contentLength) { if (entity != null) throw new IllegalArgumentException(); @@ -313,52 +385,48 @@ entity.setContentLength(contentLength); } + /** {@inheritDoc} */ + @Override public OutputStream getOutputStream() throws IOException { if (entity == null) entity = new TemporaryBufferEntity(new LocalFile(null)); return entity.getBuffer(); } + /** {@inheritDoc} */ + @Override public void setChunkedStreamingMode(int chunklen) { if (entity == null) entity = new TemporaryBufferEntity(new LocalFile(null)); entity.setChunked(true); } + /** {@inheritDoc} */ + @Override public String getRequestMethod() { return method; } + /** {@inheritDoc} */ + @Override public boolean usingProxy() { return isUsingProxy; } + /** {@inheritDoc} */ + @Override public void connect() throws IOException { execute(); } + /** {@inheritDoc} */ + @Override public void setHostnameVerifier(final HostnameVerifier hostnameverifier) { - this.hostnameverifier = new X509HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return hostnameverifier.verify(hostname, session); - } - - public void verify(String host, String[] cns, String[] subjectAlts) - throws SSLException { - throw new UnsupportedOperationException(); // TODO message - } - - public void verify(String host, X509Certificate cert) - throws SSLException { - throw new UnsupportedOperationException(); // TODO message - } - - public void verify(String host, SSLSocket ssl) throws IOException { - hostnameverifier.verify(host, ssl.getSession()); - } - }; + this.hostnameverifier = hostnameverifier; } + /** {@inheritDoc} */ + @Override public void configure(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws KeyManagementException { getSSLContext().init(km, tm, random); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java --- jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/internal/HttpApacheText.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,8 @@ */ public class HttpApacheText extends TranslationBundle { /** + * Get an instance of this translation bundle. + * * @return an instance of this translation bundle */ public static HttpApacheText get() { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java --- jgit-4.1.2/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,23 +46,24 @@ import java.io.InputStream; import java.io.OutputStream; -import org.apache.http.HttpEntity; import org.apache.http.entity.AbstractHttpEntity; import org.eclipse.jgit.util.TemporaryBuffer; /** - * A {@link HttpEntity} which takes it's content from a {@link TemporaryBuffer} + * A {@link org.apache.http.HttpEntity} which takes its content from a + * {@link org.eclipse.jgit.util.TemporaryBuffer} * * @since 3.3 */ -public class TemporaryBufferEntity extends AbstractHttpEntity { +public class TemporaryBufferEntity extends AbstractHttpEntity + implements AutoCloseable { private TemporaryBuffer buffer; private Integer contentLength; /** - * Construct a new {@link HttpEntity} which will contain the content stored - * in the specified buffer + * Construct a new {@link org.apache.http.HttpEntity} which will contain the + * content stored in the specified buffer * * @param buffer */ @@ -71,39 +72,66 @@ } /** + * Get the buffer containing the content + * * @return buffer containing the content */ public TemporaryBuffer getBuffer() { return buffer; } + /** {@inheritDoc} */ + @Override public boolean isRepeatable() { return true; } + /** {@inheritDoc} */ + @Override public long getContentLength() { if (contentLength != null) return contentLength.intValue(); return buffer.length(); } + /** {@inheritDoc} */ + @Override public InputStream getContent() throws IOException, IllegalStateException { return buffer.openInputStream(); } + /** {@inheritDoc} */ + @Override public void writeTo(OutputStream outstream) throws IOException { // TODO: dont we need a progressmonitor buffer.writeTo(outstream, null); } + /** {@inheritDoc} */ + @Override public boolean isStreaming() { return false; } /** + * Set the contentLength + * * @param contentLength */ public void setContentLength(int contentLength) { - this.contentLength = new Integer(contentLength); + this.contentLength = Integer.valueOf(contentLength); + } + + /** + * {@inheritDoc} + * + * Close destroys the associated buffer used to buffer the entity + * @since 4.5 + */ + @Override + public void close() { + if (buffer != null) { + buffer.destroy(); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/BUILD jgit-4.11.9/org.eclipse.jgit.http.server/BUILD --- jgit-4.1.2/org.eclipse.jgit.http.server/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "jgit-servlet", + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.http.server/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:servlet-api", + # We want these deps to be provided_deps + "//org.eclipse.jgit:jgit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/.classpath jgit-4.11.9/org.eclipse.jgit.http.server/.classpath --- jgit-4.1.2/org.eclipse.jgit.http.server/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,28 +1,29 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.http.server Bundle-SymbolicName: org.eclipse.jgit.http.server -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Localization: plugin Bundle-Vendor: %provider_name -Export-Package: org.eclipse.jgit.http.server;version="4.1.2", - org.eclipse.jgit.http.server.glue;version="4.1.2"; +Export-Package: org.eclipse.jgit.http.server;version="4.11.9", + org.eclipse.jgit.http.server.glue;version="4.11.9"; uses:="javax.servlet,javax.servlet.http", - org.eclipse.jgit.http.server.resolver;version="4.1.2"; + org.eclipse.jgit.http.server.resolver;version="4.11.9"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.lib, org.eclipse.jgit.transport, javax.servlet.http" Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 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.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)" + org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.resolver;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/pom.xml jgit-4.11.9/org.eclipse.jgit.http.server/pom.xml --- jgit-4.1.2/org.eclipse.jgit.http.server/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.http.server @@ -127,8 +127,45 @@ - org.codehaus.mojo - clirr-maven-plugin + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + + + + verify + + cmp + + + @@ -136,13 +173,44 @@ - org.codehaus.mojo - clirr-maven-plugin - ${clirr-version} - - ${jgit-last-release-version} - info - + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-version} + + + + cmp-report + + + + + + + ${project.groupId} + ${project.artifactId} + ${jgit-last-release-version} + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + org.eclipse.jgit.* + + public + false + false + false + false + false + true + + false + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,14 +70,20 @@ this.asIs = getAnyFile; } + /** {@inheritDoc} */ + @Override public void init(FilterConfig config) throws ServletException { // Do nothing. } + /** {@inheritDoc} */ + @Override public void destroy() { // Do nothing. } + /** {@inheritDoc} */ + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,13 +47,19 @@ import javax.servlet.http.HttpServletRequest; -/** Parses Git client User-Agent strings. */ +/** + * Parses Git client User-Agent strings. + */ public class ClientVersionUtil { private static final int[] v1_7_5 = { 1, 7, 5 }; private static final int[] v1_7_8_6 = { 1, 7, 8, 6 }; private static final int[] v1_7_9 = { 1, 7, 9 }; - /** @return maximum version array, indicating an invalid version of Git. */ + /** + * An invalid version of Git + * + * @return maximum version array, indicating an invalid version of Git. + */ public static int[] invalidVersion() { return new int[] { Integer.MAX_VALUE }; } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java 2019-09-03 12:37:49.000000000 +0000 @@ -137,8 +137,7 @@ rsp.setHeader(HDR_CONTENT_LENGTH, Long.toString(end - pos)); if (sendBody) { - final OutputStream out = rsp.getOutputStream(); - try { + try (OutputStream out = rsp.getOutputStream()) { final byte[] buf = new byte[4096]; source.seek(pos); while (pos < end) { @@ -151,8 +150,6 @@ pos += n; } out.flush(); - } finally { - out.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,8 +62,6 @@ import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.transport.ReceivePack; -import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.resolver.FileResolver; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.RepositoryResolver; @@ -74,15 +72,16 @@ * Handles Git repository access over HTTP. *

        * Applications embedding this filter should map a directory path within the - * application to this filter. For a servlet version, see {@link GitServlet}. + * application to this filter. For a servlet version, see + * {@link org.eclipse.jgit.http.server.GitServlet}. *

        * Applications may wish to add additional repository action URLs to this - * servlet by taking advantage of its extension from {@link MetaFilter}. - * Callers may register their own URL suffix translations through - * {@link #serve(String)}, or their regex translations through - * {@link #serveRegex(String)}. Each translation should contain a complete - * filter pipeline which ends with the HttpServlet that should handle the - * requested action. + * servlet by taking advantage of its extension from + * {@link org.eclipse.jgit.http.server.glue.MetaFilter}. Callers may register + * their own URL suffix translations through {@link #serve(String)}, or their + * regex translations through {@link #serveRegex(String)}. Each translation + * should contain a complete filter pipeline which ends with the HttpServlet + * that should handle the requested action. */ public class GitFilter extends MetaFilter { private volatile boolean initialized; @@ -95,9 +94,9 @@ private ReceivePackFactory receivePackFactory = new DefaultReceivePackFactory(); - private final List uploadPackFilters = new LinkedList(); + private final List uploadPackFilters = new LinkedList<>(); - private final List receivePackFilters = new LinkedList(); + private final List receivePackFilters = new LinkedList<>(); /** * New servlet that will load its base directory from {@code web.xml}. @@ -124,6 +123,8 @@ } /** + * Set AsIsFileService + * * @param f * the filter to validate direct access to repository files * through a dumb client. If {@code null} then dumb client @@ -135,9 +136,12 @@ } /** + * Set upload-pack factory + * * @param f - * the factory to construct and configure an {@link UploadPack} - * session when a fetch or clone is requested by a client. + * the factory to construct and configure an + * {@link org.eclipse.jgit.transport.UploadPack} session when a + * fetch or clone is requested by a client. */ @SuppressWarnings("unchecked") public void setUploadPackFactory(UploadPackFactory f) { @@ -146,10 +150,12 @@ } /** + * Add upload-pack filter + * * @param filter * filter to apply before any of the UploadPack operations. The * UploadPack instance is available in the request attribute - * {@link ServletUtils#ATTRIBUTE_HANDLER}. + * {@link org.eclipse.jgit.http.server.ServletUtils#ATTRIBUTE_HANDLER}. */ public void addUploadPackFilter(Filter filter) { assertNotInitialized(); @@ -157,9 +163,12 @@ } /** + * Set the receive-pack factory + * * @param f - * the factory to construct and configure a {@link ReceivePack} - * session when a push is requested by a client. + * the factory to construct and configure a + * {@link org.eclipse.jgit.transport.ReceivePack} session when a + * push is requested by a client. */ @SuppressWarnings("unchecked") public void setReceivePackFactory(ReceivePackFactory f) { @@ -168,10 +177,12 @@ } /** + * Add receive-pack filter + * * @param filter * filter to apply before any of the ReceivePack operations. The * ReceivePack instance is available in the request attribute - * {@link ServletUtils#ATTRIBUTE_HANDLER}. + * {@link org.eclipse.jgit.http.server.ServletUtils#ATTRIBUTE_HANDLER}. */ public void addReceivePackFilter(Filter filter) { assertNotInitialized(); @@ -183,6 +194,7 @@ throw new IllegalStateException(HttpServerText.get().alreadyInitializedByContainer); } + /** {@inheritDoc} */ @Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); @@ -297,6 +309,7 @@ } } + /** {@inheritDoc} */ @Override protected ServletBinder register(ServletBinder binder) { if (resolver == null) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,8 +54,6 @@ import org.eclipse.jgit.http.server.glue.MetaServlet; import org.eclipse.jgit.http.server.resolver.AsIsFileService; -import org.eclipse.jgit.transport.ReceivePack; -import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.RepositoryResolver; import org.eclipse.jgit.transport.resolver.UploadPackFactory; @@ -87,12 +85,12 @@ * *

        * Applications may wish to add additional repository action URLs to this - * servlet by taking advantage of its extension from {@link MetaServlet}. - * Callers may register their own URL suffix translations through - * {@link #serve(String)}, or their regex translations through - * {@link #serveRegex(String)}. Each translation should contain a complete - * filter pipeline which ends with the HttpServlet that should handle the - * requested action. + * servlet by taking advantage of its extension from + * {@link org.eclipse.jgit.http.server.glue.MetaServlet}. Callers may register + * their own URL suffix translations through {@link #serve(String)}, or their + * regex translations through {@link #serveRegex(String)}. Each translation + * should contain a complete filter pipeline which ends with the HttpServlet + * that should handle the requested action. */ public class GitServlet extends MetaServlet { private static final long serialVersionUID = 1L; @@ -124,6 +122,8 @@ } /** + * Set AsIsFileService + * * @param f * the filter to validate direct access to repository files * through a dumb client. If {@code null} then dumb client @@ -134,58 +134,73 @@ } /** + * Set upload-pack factory + * * @param f - * the factory to construct and configure an {@link UploadPack} - * session when a fetch or clone is requested by a client. + * the factory to construct and configure an + * {@link org.eclipse.jgit.transport.UploadPack} session when a + * fetch or clone is requested by a client. */ public void setUploadPackFactory(UploadPackFactory f) { gitFilter.setUploadPackFactory(f); } /** + * Add upload-pack filter + * * @param filter * filter to apply before any of the UploadPack operations. The * UploadPack instance is available in the request attribute - * {@link ServletUtils#ATTRIBUTE_HANDLER}. + * {@link org.eclipse.jgit.http.server.ServletUtils#ATTRIBUTE_HANDLER}. */ public void addUploadPackFilter(Filter filter) { gitFilter.addUploadPackFilter(filter); } /** + * Set receive-pack factory + * * @param f - * the factory to construct and configure a {@link ReceivePack} - * session when a push is requested by a client. + * the factory to construct and configure a + * {@link org.eclipse.jgit.transport.ReceivePack} session when a + * push is requested by a client. */ public void setReceivePackFactory(ReceivePackFactory f) { gitFilter.setReceivePackFactory(f); } /** + * Add receive-pack filter + * * @param filter * filter to apply before any of the ReceivePack operations. The * ReceivePack instance is available in the request attribute - * {@link ServletUtils#ATTRIBUTE_HANDLER}. + * {@link org.eclipse.jgit.http.server.ServletUtils#ATTRIBUTE_HANDLER}. */ public void addReceivePackFilter(Filter filter) { gitFilter.addReceivePackFilter(filter); } + /** {@inheritDoc} */ @Override public void init(final ServletConfig config) throws ServletException { gitFilter.init(new FilterConfig() { + @Override public String getFilterName() { return gitFilter.getClass().getName(); } + @Override public String getInitParameter(String name) { return config.getInitParameter(name); } + @Override public Enumeration getInitParameterNames() { return config.getInitParameterNames(); } + @Override public ServletContext getServletContext() { return config.getServletContext(); } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,15 +43,15 @@ package org.eclipse.jgit.http.server; +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; -import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.SMALL_BUF; -import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -154,9 +154,9 @@ * an HTTP response code is returned instead. *

        * This method may only be called before handing off the request to - * {@link UploadPack#upload(java.io.InputStream, OutputStream, OutputStream)} + * {@link org.eclipse.jgit.transport.UploadPack#upload(java.io.InputStream, OutputStream, OutputStream)} * or - * {@link ReceivePack#receive(java.io.InputStream, OutputStream, OutputStream)}. + * {@link org.eclipse.jgit.transport.ReceivePack#receive(java.io.InputStream, OutputStream, OutputStream)}. * * @param req * current request. @@ -201,7 +201,7 @@ } else { if (httpStatus < 400) ServletUtils.consumeRequestBody(req); - res.sendError(httpStatus); + res.sendError(httpStatus, textForGit); } } @@ -314,11 +314,8 @@ res.setStatus(HttpServletResponse.SC_OK); res.setContentType(type); res.setContentLength(buf.length); - OutputStream os = res.getOutputStream(); - try { + try (OutputStream os = res.getOutputStream()) { os.write(buf); - } finally { - os.close(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -/** Sends a fixed status code to the client. */ +/** + * Send a fixed status code to the client. + */ public class ErrorServlet extends HttpServlet { private static final long serialVersionUID = 1L; @@ -66,6 +68,7 @@ this.status = status; } + /** {@inheritDoc} */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -87,9 +87,11 @@ private volatile UrlPipeline[] pipelines; - /** Empty filter with no bindings. */ + /** + * Empty filter with no bindings. + */ public MetaFilter() { - this.bindings = new ArrayList(); + this.bindings = new ArrayList<>(); } /** @@ -128,10 +130,14 @@ return register(new RegexPipeline.Binder(pattern)); } + /** {@inheritDoc} */ + @Override public void init(FilterConfig filterConfig) throws ServletException { servletContext = filterConfig.getServletContext(); } + /** {@inheritDoc} */ + @Override public void destroy() { if (pipelines != null) { Set destroyed = newIdentitySet(); @@ -142,7 +148,7 @@ } private static Set newIdentitySet() { - final Map m = new IdentityHashMap(); + final Map m = new IdentityHashMap<>(); return new AbstractSet() { @Override public boolean add(Object o) { @@ -166,6 +172,8 @@ }; } + /** {@inheritDoc} */ + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,7 +74,9 @@ private final MetaFilter filter; - /** Empty servlet with no bindings. */ + /** + * Empty servlet with no bindings. + */ public MetaServlet() { this(new MetaFilter()); } @@ -89,7 +91,11 @@ filter = delegateFilter; } - /** @return filter this servlet delegates all routing logic to. */ + /** + * Get delegate filter + * + * @return filter this servlet delegates all routing logic to. + */ protected MetaFilter getDelegateFilter() { return filter; } @@ -116,6 +122,7 @@ return filter.serveRegex(expression); } + /** {@inheritDoc} */ @Override public void init(ServletConfig config) throws ServletException { String name = filter.getClass().getName(); @@ -123,14 +130,18 @@ filter.init(new NoParameterFilterConfig(name, ctx)); } + /** {@inheritDoc} */ + @Override public void destroy() { filter.destroy(); } + /** {@inheritDoc} */ @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { filter.doFilter(req, res, new FilterChain() { + @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,26 +59,36 @@ this.context = context; } + /** {@inheritDoc} */ + @Override public String getInitParameter(String name) { return null; } + /** {@inheritDoc} */ + @Override public Enumeration getInitParameterNames() { return new Enumeration() { + @Override public boolean hasMoreElements() { return false; } + @Override public String nextElement() { throw new NoSuchElementException(); } }; } + /** {@inheritDoc} */ + @Override public ServletContext getServletContext() { return context; } + /** {@inheritDoc} */ + @Override public String getFilterName() { return filterName; } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.http.server.glue; +import static java.lang.Integer.valueOf; + import java.io.IOException; import java.text.MessageFormat; @@ -53,22 +55,23 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import static java.lang.Integer.valueOf; - import org.eclipse.jgit.http.server.HttpServerText; /** * Switch servlet path and path info to use another regex match group. *

        * This filter is meant to be installed in the middle of a pipeline created by - * {@link MetaServlet#serveRegex(String)}. The passed request's servlet path is - * updated to be all text up to the start of the designated capture group, and - * the path info is changed to the contents of the capture group. - **/ + * {@link org.eclipse.jgit.http.server.glue.MetaServlet#serveRegex(String)}. The + * passed request's servlet path is updated to be all text up to the start of + * the designated capture group, and the path info is changed to the contents of + * the capture group. + */ public class RegexGroupFilter implements Filter { private final int groupIdx; /** + * Constructor for RegexGroupFilter + * * @param groupIdx * capture group number, 1 through the number of groups. */ @@ -79,14 +82,20 @@ this.groupIdx = groupIdx - 1; } + /** {@inheritDoc} */ + @Override public void init(FilterConfig config) throws ServletException { // Do nothing. } + /** {@inheritDoc} */ + @Override public void destroy() { // Do nothing. } + /** {@inheritDoc} */ + @Override public void doFilter(final ServletRequest request, final ServletResponse rsp, final FilterChain chain) throws IOException, ServletException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java 2019-09-03 12:37:49.000000000 +0000 @@ -95,6 +95,7 @@ pattern = p; } + @Override UrlPipeline create() { return new RegexPipeline(pattern, getFilters(), getServlet()); } @@ -108,6 +109,7 @@ this.pattern = pattern; } + @Override boolean match(final HttpServletRequest req) { final String pathInfo = req.getPathInfo(); return pathInfo != null && pattern.matcher(pathInfo).matches(); @@ -162,6 +164,7 @@ } } + /** {@inheritDoc} */ @Override public String toString() { return "Pipeline[regex: " + pattern + " ]"; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,9 +58,11 @@ private HttpServlet httpServlet; ServletBinderImpl() { - this.filters = new ArrayList(); + this.filters = new ArrayList<>(); } + /** {@inheritDoc} */ + @Override public ServletBinder through(Filter filter) { if (filter == null) throw new NullPointerException(HttpServerText.get().filterMustNotBeNull); @@ -68,6 +70,8 @@ return this; } + /** {@inheritDoc} */ + @Override public void with(HttpServlet servlet) { if (servlet == null) throw new NullPointerException(HttpServerText.get().servletMustNotBeNull); @@ -76,7 +80,11 @@ httpServlet = servlet; } - /** @return the configured servlet, or singleton returning 404 if none. */ + /** + * Get the servlet + * + * @return the configured servlet, or singleton returning 404 if none. + */ protected HttpServlet getServlet() { if (httpServlet != null) return httpServlet; @@ -84,7 +92,11 @@ return new ErrorServlet(HttpServletResponse.SC_NOT_FOUND); } - /** @return the configured filters; zero-length array if none. */ + /** + * Get filters + * + * @return the configured filters; zero-length array if none. + */ protected Filter[] getFilters() { return filters.toArray(new Filter[filters.size()]); } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,9 +46,13 @@ import javax.servlet.Filter; import javax.servlet.http.HttpServlet; -/** Binds a servlet to a URL. */ +/** + * Binds a servlet to a URL. + */ public interface ServletBinder { /** + * Set the filter to trigger while processing the path. + * * @param filter * the filter to trigger while processing the path. * @return {@code this}. @@ -56,8 +60,10 @@ public ServletBinder through(Filter filter); /** + * Set the servlet to execute on this path + * * @param servlet * the servlet to execute on this path. */ public void with(HttpServlet servlet); -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,6 +71,7 @@ this.suffix = suffix; } + @Override UrlPipeline create() { return new SuffixPipeline(suffix, getFilters(), getServlet()); } @@ -87,6 +88,7 @@ this.suffixLen = suffix.length(); } + @Override boolean match(final HttpServletRequest req) { final String pathInfo = req.getPathInfo(); return pathInfo != null && pathInfo.endsWith(suffix); @@ -101,6 +103,7 @@ super.service(new WrappedRequest(req, newPath, newInfo), rsp); } + /** {@inheritDoc} */ @Override public String toString() { return "Pipeline[ *" + suffix + " ]"; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java 2019-09-03 12:37:49.000000000 +0000 @@ -121,26 +121,32 @@ throws ServletException { if (!inited.contains(ref)) { ref.init(new ServletConfig() { + @Override public String getInitParameter(String name) { return null; } + @Override public Enumeration getInitParameterNames() { return new Enumeration() { + @Override public boolean hasMoreElements() { return false; } + @Override public String nextElement() { throw new NoSuchElementException(); } }; } + @Override public ServletContext getServletContext() { return context; } + @Override public String getServletName() { return ref.getClass().getName(); } @@ -229,6 +235,7 @@ this.servlet = servlet; } + @Override public void doFilter(ServletRequest req, ServletResponse rsp) throws IOException, ServletException { if (filterIdx < filters.length) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,7 +46,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; -/** Overrides the path and path info. */ +/** + * Overrides the path and path info. + */ public class WrappedRequest extends HttpServletRequestWrapper { private final String path; @@ -69,17 +71,20 @@ this.pathInfo = pathInfo; } + /** {@inheritDoc} */ @Override public String getPathTranslated() { final String p = getPathInfo(); - return p != null ? getRealPath(p) : null; + return p != null ? getSession().getServletContext().getRealPath(p) : null; } + /** {@inheritDoc} */ @Override public String getPathInfo() { return pathInfo; } + /** {@inheritDoc} */ @Override public String getServletPath() { return path; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/HttpServerText.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/HttpServerText.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/HttpServerText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/HttpServerText.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,6 +52,8 @@ public class HttpServerText extends TranslationBundle { /** + * Get an instance of this translation bundle + * * @return an instance of this translation bundle */ public static HttpServerText get() { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,8 @@ class InfoPacksServlet extends HttpServlet { private static final long serialVersionUID = 1L; + /** {@inheritDoc} */ + @Override public void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { sendPlainText(packList(req), req, rsp); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,6 +64,8 @@ class InfoRefsServlet extends HttpServlet { private static final long serialVersionUID = 1L; + /** {@inheritDoc} */ + @Override public void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { // Assume a dumb client and send back the dumb client @@ -72,29 +74,30 @@ rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING); final Repository db = getRepository(req); - final OutputStreamWriter out = new OutputStreamWriter( + try (OutputStreamWriter out = new OutputStreamWriter( new SmartOutputStream(req, rsp, true), - Constants.CHARSET); - final RefAdvertiser adv = new RefAdvertiser() { - @Override - protected void writeOne(final CharSequence line) throws IOException { - // Whoever decided that info/refs should use a different - // delimiter than the native git:// protocol shouldn't - // be allowed to design this sort of stuff. :-( - out.append(line.toString().replace(' ', '\t')); - } + Constants.CHARSET)) { + final RefAdvertiser adv = new RefAdvertiser() { + @Override + protected void writeOne(final CharSequence line) + throws IOException { + // Whoever decided that info/refs should use a different + // delimiter than the native git:// protocol shouldn't + // be allowed to design this sort of stuff. :-( + out.append(line.toString().replace(' ', '\t')); + } - @Override - protected void end() { - // No end marker required for info/refs format. - } - }; - adv.init(db); - adv.setDerefTags(true); + @Override + protected void end() { + // No end marker required for info/refs format. + } + }; + adv.init(db); + adv.setDerefTags(true); - Map refs = db.getRefDatabase().getRefs(ALL); - refs.remove(Constants.HEAD); - adv.send(refs); - out.close(); + Map refs = db.getRefDatabase().getRefs(ALL); + refs.remove(Constants.HEAD); + adv.send(refs); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,14 +66,20 @@ * downstream servlet can directly access its contents on disk. */ class IsLocalFilter implements Filter { + /** {@inheritDoc} */ + @Override public void init(FilterConfig config) throws ServletException { // Do nothing. } + /** {@inheritDoc} */ + @Override public void destroy() { // Do nothing. } + /** {@inheritDoc} */ + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (isLocal(getRepository(request))) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,16 +57,22 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; -/** Adds HTTP response headers to prevent caching by proxies/browsers. */ +/** Add HTTP response headers to prevent caching by proxies/browsers. */ class NoCacheFilter implements Filter { + /** {@inheritDoc} */ + @Override public void init(FilterConfig config) throws ServletException { // Do nothing. } + /** {@inheritDoc} */ + @Override public void destroy() { // Do nothing. } + /** {@inheritDoc} */ + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse rsp = (HttpServletResponse) response; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -117,12 +117,14 @@ abstract String etag(FileSender sender) throws IOException; + /** {@inheritDoc} */ @Override public void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { serve(req, rsp, true); } + /** {@inheritDoc} */ @Override protected void doHead(final HttpServletRequest req, final HttpServletResponse rsp) throws ServletException, IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -130,6 +130,7 @@ this.receivePackFactory = receivePackFactory; } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @@ -153,15 +154,18 @@ } } + @Override public void init(FilterConfig filterConfig) throws ServletException { // Nothing. } + @Override public void destroy() { // Nothing. } } + /** {@inheritDoc} */ @Override public void doPost(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,16 +71,19 @@ import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; /** - * Opens a repository named by the path info through {@link RepositoryResolver}. + * Open a repository named by the path info through + * {@link org.eclipse.jgit.transport.resolver.RepositoryResolver}. *

        - * This filter assumes it is invoked by {@link GitServlet} and is likely to not - * work as expected if called from any other class. This filter assumes the path - * info of the current request is a repository name which can be used by the - * configured {@link RepositoryResolver} to open a {@link Repository} and attach - * it to the current request. + * This filter assumes it is invoked by + * {@link org.eclipse.jgit.http.server.GitServlet} and is likely to not work as + * expected if called from any other class. This filter assumes the path info of + * the current request is a repository name which can be used by the configured + * {@link org.eclipse.jgit.transport.resolver.RepositoryResolver} to open a + * {@link org.eclipse.jgit.lib.Repository} and attach it to the current request. *

        - * This filter sets request attribute {@link ServletUtils#ATTRIBUTE_REPOSITORY} - * when it discovers the repository, and automatically closes and removes the + * This filter sets request attribute + * {@link org.eclipse.jgit.http.server.ServletUtils#ATTRIBUTE_REPOSITORY} when + * it discovers the repository, and automatically closes and removes the * attribute when the request is complete. */ public class RepositoryFilter implements Filter { @@ -93,21 +96,28 @@ * * @param resolver * the resolver which will be used to translate the URL name - * component to the actual {@link Repository} instance for the + * component to the actual + * {@link org.eclipse.jgit.lib.Repository} instance for the * current web request. */ public RepositoryFilter(final RepositoryResolver resolver) { this.resolver = resolver; } + /** {@inheritDoc} */ + @Override public void init(final FilterConfig config) throws ServletException { context = config.getServletContext(); } + /** {@inheritDoc} */ + @Override public void destroy() { context = null; } + /** {@inheritDoc} */ + @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { @@ -143,7 +153,7 @@ res.sendError(SC_UNAUTHORIZED, e.getMessage()); return; } catch (ServiceMayNotContinueException e) { - sendError(req, res, SC_FORBIDDEN, e.getMessage()); + sendError(req, res, e.getStatusCode(), e.getMessage()); return; } try { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,10 +45,8 @@ import javax.servlet.http.HttpServletRequest; -import org.eclipse.jgit.http.server.GitServlet; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; @@ -58,8 +56,9 @@ * Older HTTP clients which do not speak the smart HTTP variant of the Git * protocol fetch from a repository by directly getting its objects and pack * files. This class, along with the {@code http.getanyfile} per-repository - * configuration setting, can be used by {@link GitServlet} to control whether - * or not these older clients are permitted to read these direct files. + * configuration setting, can be used by + * {@link org.eclipse.jgit.http.server.GitServlet} to control whether or not + * these older clients are permitted to read these direct files. */ public class AsIsFileService { /** Always throws {@link ServiceNotEnabledException}. */ @@ -71,12 +70,6 @@ } }; - private static final SectionParser CONFIG = new SectionParser() { - public ServiceConfig parse(final Config cfg) { - return new ServiceConfig(cfg); - } - }; - private static class ServiceConfig { final boolean enabled; @@ -95,7 +88,7 @@ * {@code true}. */ protected static boolean isEnabled(Repository db) { - return db.getConfig().get(CONFIG).enabled; + return db.getConfig().get(ServiceConfig::new).enabled; } /** @@ -105,8 +98,10 @@ * throwing a checked exception if access should be denied. *

        * The default implementation of this method checks {@code http.getanyfile}, - * throwing {@link ServiceNotEnabledException} if it was explicitly set to - * {@code false}, and otherwise succeeding silently. + * throwing + * {@link org.eclipse.jgit.transport.resolver.ServiceNotEnabledException} if + * it was explicitly set to {@code false}, and otherwise succeeding + * silently. * * @param req * current HTTP request, in case information from the request may diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,19 +48,20 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; /** - * Create and configure {@link ReceivePack} service instance. + * Create and configure {@link org.eclipse.jgit.transport.ReceivePack} service + * instance. *

        * Writing by receive-pack is permitted if any of the following is true: *

          *
        • The container has authenticated the user and set - * {@link HttpServletRequest#getRemoteUser()} to the authenticated name. + * {@link javax.servlet.http.HttpServletRequest#getRemoteUser()} to the + * authenticated name. *
        • The repository configuration file has {@code http.receivepack} explicitly * set to true. *
        @@ -68,12 +69,6 @@ */ public class DefaultReceivePackFactory implements ReceivePackFactory { - private static final SectionParser CONFIG = new SectionParser() { - public ServiceConfig parse(final Config cfg) { - return new ServiceConfig(cfg); - } - }; - private static class ServiceConfig { final boolean set; @@ -85,9 +80,11 @@ } } + /** {@inheritDoc} */ + @Override public ReceivePack create(final HttpServletRequest req, final Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { - final ServiceConfig cfg = db.getConfig().get(CONFIG); + final ServiceConfig cfg = db.getConfig().get(ServiceConfig::new); String user = req.getRemoteUser(); if (cfg.set) { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,26 +47,20 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; /** - * Create and configure {@link UploadPack} service instance. + * Create and configure {@link org.eclipse.jgit.transport.UploadPack} service + * instance. *

        * Reading by upload-pack is permitted unless {@code http.uploadpack} is * explicitly set to false. */ public class DefaultUploadPackFactory implements UploadPackFactory { - private static final SectionParser CONFIG = new SectionParser() { - public ServiceConfig parse(final Config cfg) { - return new ServiceConfig(cfg); - } - }; - private static class ServiceConfig { final boolean enabled; @@ -75,9 +69,11 @@ } } + /** {@inheritDoc} */ + @Override public UploadPack create(final HttpServletRequest req, final Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { - if (db.getConfig().get(CONFIG).enabled) + if (db.getConfig().get(ServiceConfig::new).enabled) return new UploadPack(db); else throw new ServiceNotEnabledException(); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.http.server; import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; +import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG; @@ -67,7 +68,9 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -/** Common utility functions for servlets. */ +/** + * Common utility functions for servlets. + */ public final class ServletUtils { /** Request attribute which stores the {@link Repository} instance. */ public static final String ATTRIBUTE_REPOSITORY = "org.eclipse.jgit.Repository"; @@ -111,7 +114,7 @@ throws IOException { InputStream in = req.getInputStream(); final String enc = req.getHeader(HDR_CONTENT_ENCODING); - if (ENCODING_GZIP.equals(enc) || "x-gzip".equals(enc)) //$NON-NLS-1$ + if (ENCODING_GZIP.equals(enc) || ENCODING_X_GZIP.equals(enc)) in = new GZIPInputStream(in); else if (enc != null) throw new IOException(MessageFormat.format(HttpServerText.get().encodingNotSupportedByThisLibrary diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,6 +83,7 @@ this.compressStream = compressStream; } + /** {@inheritDoc} */ @Override protected OutputStream overflow() throws IOException { startedOutput = true; @@ -95,6 +96,8 @@ return out; } + /** {@inheritDoc} */ + @Override public void close() throws IOException { super.close(); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java 2019-09-03 12:37:49.000000000 +0000 @@ -80,14 +80,20 @@ this.filters = filters.toArray(new Filter[filters.size()]); } + /** {@inheritDoc} */ + @Override public void init(FilterConfig config) throws ServletException { // Do nothing. } + /** {@inheritDoc} */ + @Override public void destroy() { // Do nothing. } + /** {@inheritDoc} */ + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final HttpServletRequest req = (HttpServletRequest) request; @@ -139,14 +145,35 @@ if (e.isOutput()) buf.close(); else - sendError(req, res, SC_FORBIDDEN, e.getMessage()); + sendError(req, res, e.getStatusCode(), e.getMessage()); } } + /** + * Begin service. + * + * @param req + * request + * @param db + * repository + * @throws IOException + * @throws ServiceNotEnabledException + * @throws ServiceNotAuthorizedException + */ protected abstract void begin(HttpServletRequest req, Repository db) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException; + /** + * Advertise. + * + * @param req + * request + * @param pck + * @throws IOException + * @throws ServiceNotEnabledException + * @throws ServiceNotAuthorizedException + */ protected abstract void advertise(HttpServletRequest req, PacketLineOutRefAdvertiser pck) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException; @@ -154,6 +181,7 @@ private class Chain implements FilterChain { private int filterIdx; + @Override public void doFilter(ServletRequest req, ServletResponse rsp) throws IOException, ServletException { if (filterIdx < filters.length) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,6 +68,8 @@ this.fileName = name; } + /** {@inheritDoc} */ + @Override public void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { try { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java --- jgit-4.1.2/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -129,6 +129,7 @@ this.uploadPackFactory = uploadPackFactory; } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @@ -152,15 +153,18 @@ } } + @Override public void init(FilterConfig filterConfig) throws ServletException { // Nothing. } + @Override public void destroy() { // Nothing. } } + /** {@inheritDoc} */ @Override public void doPost(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { @@ -197,7 +201,7 @@ out.close(); } else if (!rsp.isCommitted()) { rsp.reset(); - sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); + sendError(req, rsp, e.getStatusCode(), e.getMessage()); } return; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/BUILD jgit-4.11.9/org.eclipse.jgit.http.test/BUILD --- jgit-4.1.2/org.eclipse.jgit.http.test/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,43 @@ +load( + "@com_googlesource_gerrit_bazlets//tools:junit.bzl", + "junit_tests", +) + +junit_tests( + name = "http", + srcs = glob(["tst/**/*.java"]), + tags = ["http"], + deps = [ + ":helpers", + "//lib:commons-logging", + "//lib:jetty-http", + "//lib:jetty-io", + "//lib:jetty-security", + "//lib:jetty-server", + "//lib:jetty-servlet", + "//lib:jetty-util", + "//lib:junit", + "//lib:servlet-api", + "//lib:slf4j-api", + "//lib:slf4j-simple", + "//org.eclipse.jgit.http.apache:http-apache", + "//org.eclipse.jgit.http.server:jgit-servlet", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit.http:junit-http", + "//org.eclipse.jgit.junit:junit", + ], +) + +java_library( + name = "helpers", + testonly = 1, + srcs = glob(["src/**/*.java"]), + deps = [ + "//lib:junit", + "//lib:servlet-api", + "//org.eclipse.jgit.http.server:jgit-servlet", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit.http:junit-http", + "//org.eclipse.jgit.junit:junit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/build.properties jgit-4.11.9/org.eclipse.jgit.http.test/build.properties --- jgit-4.1.2/org.eclipse.jgit.http.test/build.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,5 @@ -source.. = tst/ +source.. = tst/,\ + src/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/.classpath jgit-4.11.9/org.eclipse.jgit.http.test/.classpath --- jgit-4.1.2/org.eclipse.jgit.http.test/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,7 +1,8 @@ - + + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,45 +1,52 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.http.test Bundle-SymbolicName: org.eclipse.jgit.http.test -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Vendor: %provider_name Bundle-Localization: plugin -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: javax.servlet;version="[2.5.0,3.2.0)", javax.servlet.http;version="[2.5.0,3.2.0)", - org.eclipse.jetty.continuation;version="[9.0.0,10.0.0)", - org.eclipse.jetty.http;version="[9.0.0,10.0.0)", - org.eclipse.jetty.io;version="[9.0.0,10.0.0)", - org.eclipse.jetty.security;version="[9.0.0,10.0.0)", - org.eclipse.jetty.security.authentication;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server.handler;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server.nio;version="[9.0.0,10.0.0)", - org.eclipse.jetty.servlet;version="[9.0.0,10.0.0)", - org.eclipse.jetty.util;version="[9.0.0,10.0.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.jetty.util.thread;version="[9.0.0,10.0.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.apache.commons.codec;version="[1.6.0,2.0.0)", + org.apache.commons.codec.binary;version="[1.6.0,2.0.0)", + org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)", + org.eclipse.jetty.http;version="[9.4.5,10.0.0)", + org.eclipse.jetty.io;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)", + org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)", + org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.http.server;version="[4.11.9,4.12.0)", + org.eclipse.jgit.http.server.glue;version="[4.11.9,4.12.0)", + org.eclipse.jgit.http.server.resolver;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.reftable;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http.apache;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.resolver;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.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)", - org.junit.runners;version="[4.0.0,5.0.0)" + org.junit;version="[4.12,5.0.0)", + org.junit.rules;version="[4.12,5.0.0)", + org.junit.runner;version="[4.12,5.0.0)", + org.junit.runners;version="[4.12,5.0.0)" +Require-Bundle: org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/pom.xml jgit-4.11.9/org.eclipse.jgit.http.test/pom.xml --- jgit-4.1.2/org.eclipse.jgit.http.test/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.http.test @@ -60,7 +60,9 @@ Tests for the HTTP transport. - + + true + junit @@ -69,10 +71,16 @@ + org.hamcrest + hamcrest-library + test + [1.1.0,2.0.0) + + + org.eclipse.jgit org.eclipse.jgit ${project.version} - test @@ -86,14 +94,12 @@ org.eclipse.jgit org.eclipse.jgit.junit.http ${project.version} - test org.eclipse.jgit org.eclipse.jgit.junit ${project.version} - test @@ -106,11 +112,11 @@ org.eclipse.jetty jetty-servlet - test + src/ tst/ @@ -134,6 +140,10 @@ maven-surefire-plugin -Djava.io.tmpdir=${project.build.directory} -Xmx300m + + **/*Test.java + **/*Tests.java + diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java jgit-4.11.9/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java --- jgit-4.1.2/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016, 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.http.test; + +import java.io.IOException; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.internal.storage.reftable.Reftable; +import org.eclipse.jgit.lib.RefDatabase; + +/** + * An {@link InMemoryRepository} whose refs can be made unreadable for testing + * purposes. + */ +class RefsUnreadableInMemoryRepository extends InMemoryRepository { + + private final RefsUnreadableRefDatabase refs; + + private volatile boolean failing; + + RefsUnreadableInMemoryRepository(DfsRepositoryDescription repoDesc) { + super(repoDesc); + refs = new RefsUnreadableRefDatabase(); + failing = false; + } + + /** {@inheritDoc} */ + @Override + public RefDatabase getRefDatabase() { + return refs; + } + + /** + * Make the ref database unable to scan its refs. + *

        + * It may be useful to follow a call to startFailing with a call to + * {@link RefDatabase#refresh()}, ensuring the next ref read fails. + */ + void startFailing() { + failing = true; + } + + private class RefsUnreadableRefDatabase extends MemRefDatabase { + @Override + protected Reftable reader() throws IOException { + if (failing) { + throw new IOException("disk failed, no refs found"); + } + return super.reader(); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java jgit-4.11.9/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java --- jgit-4.1.2/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016, 2017 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.http.test; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.RepositoryResolver; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; + +/** + * A simple repository resolver for tests. + */ +public final class TestRepositoryResolver + implements RepositoryResolver { + + private final TestRepository repo; + + private final String repoName; + + /** + * Create a new {@link org.eclipse.jgit.http.test.TestRepositoryResolver} + * that resolves the given name to the given repository. + * + * @param repo + * to resolve to + * @param repoName + * to match + */ + public TestRepositoryResolver(TestRepository repo, String repoName) { + this.repo = repo; + this.repoName = repoName; + } + + /** {@inheritDoc} */ + @Override + public Repository open(HttpServletRequest req, String name) + throws RepositoryNotFoundException, ServiceNotEnabledException { + if (!name.equals(repoName)) { + throw new RepositoryNotFoundException(name); + } + Repository db = repo.getRepository(); + db.incrementOpen(); + return db; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,11 @@ package org.eclipse.jgit.http.server; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.eclipse.jgit.http.server.ClientVersionUtil.hasPushStatusBug; import static org.eclipse.jgit.http.server.ClientVersionUtil.invalidVersion; import static org.eclipse.jgit.http.server.ClientVersionUtil.parseVersion; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.junit.Assert; import org.junit.Test; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -80,6 +80,7 @@ private URIish remoteURI; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -90,6 +91,7 @@ ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { @@ -102,6 +104,7 @@ } }); gs.setReceivePackFactory(new DefaultReceivePackFactory() { + @Override public ReceivePack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { @@ -132,8 +135,7 @@ final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - final Transport t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -151,8 +153,6 @@ + "come back next year!", // error.getMessage()); } - } finally { - t.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,6 +64,7 @@ private AsIsFileService service; + @Override @Before public void setUp() throws Exception { super.setUp(); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -71,6 +71,7 @@ private ReceivePackFactory factory; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -79,6 +80,7 @@ factory = new DefaultReceivePackFactory(); } + @SuppressWarnings("unchecked") @Test public void testDisabledSingleton() throws ServiceNotAuthorizedException { factory = (ReceivePackFactory) ReceivePackFactory.DISABLED; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,6 +69,7 @@ private UploadPackFactory factory; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -77,6 +78,7 @@ factory = new DefaultUploadPackFactory(); } + @SuppressWarnings("unchecked") @Test public void testDisabledSingleton() throws ServiceNotAuthorizedException { factory = (UploadPackFactory) UploadPackFactory.DISABLED; diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -109,6 +109,7 @@ HttpTransport.setConnectionFactory(cf); } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -140,8 +141,7 @@ assertEquals("http", remoteURI.getScheme()); Map map; - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { // I didn't make up these public interface names, I just // approved them for inclusion into the code base. Sorry. // --spearce @@ -149,14 +149,9 @@ assertTrue("isa TransportHttp", t instanceof TransportHttp); assertTrue("isa HttpTransport", t instanceof HttpTransport); - FetchConnection c = t.openFetch(); - try { + try (FetchConnection c = t.openFetch()) { map = c.getRefsMap(); - } finally { - c.close(); } - } finally { - t.close(); } assertNotNull("have map of refs", map); @@ -201,15 +196,12 @@ Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List loose = getRequests(loose(remoteURI, A_txt)); @@ -221,20 +213,17 @@ @Test public void testInitialClone_Packed() throws Exception { - new TestRepository(remoteRepository).packAndPrune(); + new TestRepository<>(remoteRepository).packAndPrune(); Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List req; @@ -265,8 +254,7 @@ final RevCommit Q = src.commit().create(); final Repository db = src.getRepository(); - Transport t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { try { t.push(NullProgressMonitor.INSTANCE, push(src, Q)); fail("push incorrectly completed against a dumb server"); @@ -274,8 +262,6 @@ String exp = "remote does not support smart HTTP push"; assertEquals(exp, nse.getMessage()); } - } finally { - t.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Google Inc. + * Copyright (C) 2010, 2017 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -60,12 +60,9 @@ import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletRequest; - import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jgit.errors.NotSupportedException; -import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.http.server.GitServlet; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.http.AccessEvent; @@ -84,8 +81,6 @@ import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; -import org.eclipse.jgit.transport.resolver.RepositoryResolver; -import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -114,6 +109,7 @@ HttpTransport.setConnectionFactory(cf); } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -123,18 +119,7 @@ ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); - gs.setRepositoryResolver(new RepositoryResolver() { - public Repository open(HttpServletRequest req, String name) - throws RepositoryNotFoundException, - ServiceNotEnabledException { - if (!name.equals(srcName)) - throw new RepositoryNotFoundException(name); - - final Repository db = src.getRepository(); - db.incrementOpen(); - return db; - } - }); + gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName)); app.addServlet(new ServletHolder(gs), "/*"); server.setUp(); @@ -155,9 +140,8 @@ assertEquals("http", remoteURI.getScheme()); Map map; - Transport t = Transport.open(dst, remoteURI); + try (Transport t = Transport.open(dst, remoteURI)) { ((TransportHttp) t).setUseSmartHttp(false); - try { // I didn't make up these public interface names, I just // approved them for inclusion into the code base. Sorry. // --spearce @@ -165,14 +149,9 @@ assertTrue("isa TransportHttp", t instanceof TransportHttp); assertTrue("isa HttpTransport", t instanceof HttpTransport); - FetchConnection c = t.openFetch(); - try { + try (FetchConnection c = t.openFetch()) { map = c.getRefsMap(); - } finally { - c.close(); } - } finally { - t.close(); } assertNotNull("have map of refs", map); @@ -199,7 +178,7 @@ .startsWith("JGit/")); assertEquals("*/*", info.getRequestHeader(HDR_ACCEPT)); assertEquals(200, info.getStatus()); - assertEquals("text/plain; charset=UTF-8", + assertEquals("text/plain;charset=utf-8", info .getResponseHeader(HDR_CONTENT_TYPE)); @@ -216,16 +195,13 @@ Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); + try (Transport t = Transport.open(dst, remoteURI)) { ((TransportHttp) t).setUseSmartHttp(false); - try { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List loose = getRequests(loose(remoteURI, A_txt)); @@ -239,21 +215,18 @@ @Test public void testInitialClone_Packed() throws Exception { - new TestRepository(remoteRepository).packAndPrune(); + new TestRepository<>(remoteRepository).packAndPrune(); Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - ((TransportHttp) t).setUseSmartHttp(false); - try { + try (Transport t = Transport.open(dst, remoteURI)) { + ((TransportHttp) t).setUseSmartHttp(false); t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List req; @@ -269,7 +242,7 @@ assertEquals("GET", req.get(0).getMethod()); assertEquals(0, req.get(0).getParameters().size()); assertEquals(200, req.get(0).getStatus()); - assertEquals("text/plain; charset=UTF-8", + assertEquals("text/plain;charset=utf-8", req.get(0).getResponseHeader( HDR_CONTENT_TYPE)); } @@ -280,9 +253,8 @@ final RevCommit Q = src.commit().create(); final Repository db = src.getRepository(); - Transport t = Transport.open(db, remoteURI); - ((TransportHttp) t).setUseSmartHttp(false); - try { + try (Transport t = Transport.open(db, remoteURI)) { + ((TransportHttp) t).setUseSmartHttp(false); try { t.push(NullProgressMonitor.INSTANCE, push(src, Q)); fail("push incorrectly completed against a smart server"); @@ -290,8 +262,6 @@ String exp = "smart HTTP push disabled"; assertEquals(exp, nse.getMessage()); } - } finally { - t.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,7 +83,7 @@ private static void assertUnreasonable(String name) throws ServiceNotEnabledException { - FileResolver r = new FileResolver( + FileResolver r = new FileResolver<>( new File("."), false); try { r.open(null, name); @@ -103,7 +103,7 @@ FileResolver resolver; assertFalse("no git-daemon-export-ok", export.exists()); - resolver = new FileResolver(base, false /* + resolver = new FileResolver<>(base, false /* * require * flag */); @@ -114,7 +114,7 @@ assertEquals("Service not enabled", e.getMessage()); } - resolver = new FileResolver(base, true /* + resolver = new FileResolver<>(base, true /* * export * all */); @@ -125,7 +125,7 @@ } FileUtils.createNewFile(export); - resolver = new FileResolver(base, false /* + resolver = new FileResolver<>(base, false /* * require * flag */); @@ -142,7 +142,7 @@ final Repository a = createBareRepository(); final String name = a.getDirectory().getName() + "-not-a-git"; final File base = a.getDirectory().getParentFile(); - FileResolver resolver = new FileResolver( + FileResolver resolver = new FileResolver<>( base, false); try { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,7 @@ import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.http.HttpTestCase; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; @@ -106,6 +107,7 @@ * configure the maximum pack file size, the object checker and custom hooks * just before they talk to the server. */ + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -116,6 +118,7 @@ ServletContextHandler app = server.addContext("/git"); gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { @@ -128,6 +131,7 @@ } }); gs.setReceivePackFactory(new DefaultReceivePackFactory() { + @Override public ReceivePack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { @@ -172,7 +176,6 @@ final RevCommit Q = client.commit().add("Q", Q_txt).create(); final Repository clientRepo = client.getRepository(); final String srvBranchName = Constants.R_HEADS + "new.branch"; - Transport t; maxPackSize = 0; postHook = null; @@ -184,8 +187,7 @@ } }; - t = Transport.open(clientRepo, srvURI); - try { + try (Transport t = Transport.open(clientRepo, srvURI)) { RemoteRefUpdate update = new RemoteRefUpdate(clientRepo, Q.name(), srvBranchName, false, null, null); try { @@ -195,8 +197,6 @@ } catch (Exception e) { assertTrue(e instanceof TransportException); } - } finally { - t.close(); } } @@ -214,20 +214,19 @@ final RevCommit Q = client.commit().add("Q", Q_txt).create(); final Repository clientRepo = client.getRepository(); final String srvBranchName = Constants.R_HEADS + "new.branch"; - Transport t; maxPackSize = 0; postHook = null; preHook = null; oc = new ObjectChecker() { @Override - public void checkCommit(byte[] raw) throws CorruptObjectException { - throw new IllegalStateException(); + public void checkCommit(AnyObjectId id, byte[] raw) + throws CorruptObjectException { + throw new CorruptObjectException("refusing all commits"); } }; - t = Transport.open(clientRepo, srvURI); - try { + try (Transport t = Transport.open(clientRepo, srvURI)) { RemoteRefUpdate update = new RemoteRefUpdate(clientRepo, Q.name(), srvBranchName, false, null, null); try { @@ -237,8 +236,6 @@ } catch (Exception e) { assertTrue(e instanceof TransportException); } - } finally { - t.close(); } } @@ -261,13 +258,13 @@ final RevCommit Q = client.commit().add("Q", Q_txt).create(); final Repository clientRepo = client.getRepository(); final String srvBranchName = Constants.R_HEADS + "new.branch"; - Transport t; // this maxPackSize leads to an unPackError - maxPackSize = 400; + maxPackSize = 100; // this PostReceiveHook when called after an unsuccesfull unpack will // lead to an IllegalStateException postHook = new PostReceiveHook() { + @Override public void onPostReceive(ReceivePack rp, Collection commands) { // the maxPackSize setting caused that the packfile couldn't be @@ -277,8 +274,7 @@ } }; - t = Transport.open(clientRepo, srvURI); - try { + try (Transport t = Transport.open(clientRepo, srvURI)) { RemoteRefUpdate update = new RemoteRefUpdate(clientRepo, Q.name(), srvBranchName, false, null, null); try { @@ -288,8 +284,6 @@ } catch (Exception e) { assertTrue(e instanceof TooLargePackException); } - } finally { - t.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -88,6 +88,7 @@ private URIish remoteURI; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -98,6 +99,7 @@ ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { @@ -110,11 +112,13 @@ } }); gs.setReceivePackFactory(new DefaultReceivePackFactory() { + @Override public ReceivePack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { ReceivePack recv = super.create(req, db); recv.setPreReceiveHook(new PreReceiveHook() { + @Override public void onPreReceive(ReceivePack rp, Collection commands) { rp.sendMessage("message line 1"); @@ -145,11 +149,9 @@ final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; PushResult result; - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -159,13 +161,11 @@ srcExpr, dstName, forceUpdate, localName, oldId); result = t.push(NullProgressMonitor.INSTANCE, Collections .singleton(update)); - } finally { - t.close(); } assertTrue(remoteRepository.hasObject(Q_txt)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); List requests = getRequests(); @@ -189,12 +189,10 @@ final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; PushResult result; - t = Transport.open(db, remoteURI); OutputStream out = new ByteArrayOutputStream(); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -204,8 +202,6 @@ srcExpr, dstName, forceUpdate, localName, oldId); result = t.push(NullProgressMonitor.INSTANCE, Collections.singleton(update), out); - } finally { - t.close(); } String expectedMessage = "message line 1\n" // diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java 2019-09-03 12:37:49.000000000 +0000 @@ -94,6 +94,7 @@ private URIish smartAuthBasicURI; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -132,6 +133,7 @@ private ServletContextHandler smart(final String path) { GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { @@ -157,8 +159,7 @@ public void testRepositoryNotFound_Dumb() throws Exception { URIish uri = toURIish("/dumb.none/not-found"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("connection opened to not found repository"); @@ -167,8 +168,6 @@ + "/info/refs?service=git-upload-pack not found"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -176,8 +175,7 @@ public void testRepositoryNotFound_Smart() throws Exception { URIish uri = toURIish("/smart.none/not-found"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("connection opened to not found repository"); @@ -186,8 +184,6 @@ + "/info/refs?service=git-upload-pack not found"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -201,16 +197,9 @@ Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, dumbAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNotNull("has " + Constants.HEAD, head); assertEquals(Q, head.getObjectId()); @@ -225,16 +214,9 @@ Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, dumbAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNull("has no " + Constants.HEAD, head); } @@ -249,16 +231,9 @@ Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, smartAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, smartAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNotNull("has " + Constants.HEAD, head); assertEquals(Q, head.getObjectId()); @@ -268,16 +243,13 @@ public void testListRemote_Smart_WithQueryParameters() throws Exception { URIish myURI = toURIish("/snone/do?r=1&p=test.git"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, myURI); - try { + try (Transport t = Transport.open(dst, myURI)) { try { t.openFetch(); fail("test did not fail to find repository as expected"); } catch (NoRemoteRepositoryException err) { // expected } - } finally { - t.close(); } List requests = getRequests(); @@ -296,62 +268,52 @@ @Test public void testListRemote_Dumb_NeedsAuth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, dumbAuthBasicURI); - try { + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { try { t.openFetch(); fail("connection opened even info/refs needs auth basic"); } catch (TransportException err) { String exp = dumbAuthBasicURI + ": " - + JGitText.get().notAuthorized; + + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @Test public void testListRemote_Dumb_Auth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, dumbAuthBasicURI); - t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( - AppServer.username, AppServer.password)); - try { - t.openFetch(); - } finally { - t.close(); - } - t = Transport.open(dst, dumbAuthBasicURI); - t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( - AppServer.username, "")); - try { - t.openFetch(); - fail("connection opened even info/refs needs auth basic and we provide wrong password"); - } catch (TransportException err) { - String exp = dumbAuthBasicURI + ": " - + JGitText.get().notAuthorized; - assertEquals(exp, err.getMessage()); - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { + t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( + AppServer.username, AppServer.password)); + t.openFetch().close(); + } + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { + t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( + AppServer.username, "")); + try { + t.openFetch(); + fail("connection opened even info/refs needs auth basic and we provide wrong password"); + } catch (TransportException err) { + String exp = dumbAuthBasicURI + ": " + + JGitText.get().notAuthorized; + assertEquals(exp, err.getMessage()); + } } } @Test public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, smartAuthBasicURI); - try { + try (Transport t = Transport.open(dst, smartAuthBasicURI)) { try { t.openFetch(); fail("connection opened even though service disabled"); } catch (TransportException err) { String exp = smartAuthBasicURI + ": " - + JGitText.get().notAuthorized; + + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -363,33 +325,24 @@ cfg.save(); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, smartAuthNoneURI); - try { + try (Transport t = Transport.open(dst, smartAuthNoneURI)) { try { t.openFetch(); fail("connection opened even though service disabled"); } catch (TransportException err) { - String exp = smartAuthNoneURI + ": Git access forbidden"; + String exp = smartAuthNoneURI + ": " + + JGitText.get().serviceNotEnabledNoName; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @Test public void testListRemoteWithoutLocalRepository() throws Exception { - Transport t = Transport.open(smartAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - Ref head = c.getRef(Constants.HEAD); - assertNotNull(head); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(smartAuthNoneURI); + FetchConnection c = t.openFetch()) { + Ref head = c.getRef(Constants.HEAD); + assertNotNull(head); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,6 +83,7 @@ long packSize = -1; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -93,6 +94,7 @@ ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { @@ -105,12 +107,14 @@ } }); gs.setReceivePackFactory(new DefaultReceivePackFactory() { + @Override public ReceivePack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { ReceivePack recv = super.create(req, db); recv.setPostReceiveHook(new PostReceiveHook() { + @Override public void onPostReceive(ReceivePack rp, Collection commands) { packSize = rp.getPackSize(); @@ -140,11 +144,9 @@ final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; PushResult result; - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -154,8 +156,6 @@ srcExpr, dstName, forceUpdate, localName, oldId); result = t.push(NullProgressMonitor.INSTANCE, Collections.singleton(update)); - } finally { - t.close(); } assertEquals("expected 1 RemoteUpdate", 1, result.getRemoteUpdates() .size()); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -84,6 +84,7 @@ private RevBlob a_blob; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -94,6 +95,7 @@ ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,12 +93,14 @@ } } + @Override @Before public void setUp() throws Exception { server = new AppServer(); ctx = server.addContext("/"); } + @Override @After public void tearDown() throws Exception { server.tearDown(); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,6 +77,7 @@ private RevCommit A, B; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -111,7 +112,7 @@ assertTrue("isa TransportHttp", t instanceof TransportHttp); assertTrue("isa HttpTransport", t instanceof HttpTransport); - HashMap headers = new HashMap(); + HashMap headers = new HashMap<>(); headers.put("Cookie", "someTokenValue=23gBog34"); headers.put("AnotherKey", "someValue"); ((TransportHttp) t).setAdditionalHeaders(headers); diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2017 Thomas Wolf + * 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.http.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.http.AccessEvent; +import org.eclipse.jgit.junit.http.AppServer; +import org.eclipse.jgit.junit.http.HttpTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.eclipse.jgit.transport.http.HttpConnectionFactory; +import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; +import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; +import org.eclipse.jgit.util.HttpSupport; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class SmartClientSmartServerSslTest extends HttpTestCase { + + // We run these tests with a server on localhost with a self-signed + // certificate. We don't do authentication tests here, so there's no need + // for username and password. + // + // But the server certificate will not validate. We know that Transport will + // ask whether we trust the server all the same. This credentials provider + // blindly trusts the self-signed certificate by answering "Yes" to all + // questions. + private CredentialsProvider testCredentials = new CredentialsProvider() { + + @Override + public boolean isInteractive() { + return false; + } + + @Override + public boolean supports(CredentialItem... items) { + for (CredentialItem item : items) { + if (item instanceof CredentialItem.InformationalMessage) { + continue; + } + if (item instanceof CredentialItem.YesNoType) { + continue; + } + return false; + } + return true; + } + + @Override + public boolean get(URIish uri, CredentialItem... items) + throws UnsupportedCredentialItem { + for (CredentialItem item : items) { + if (item instanceof CredentialItem.InformationalMessage) { + continue; + } + if (item instanceof CredentialItem.YesNoType) { + ((CredentialItem.YesNoType) item).setValue(true); + continue; + } + return false; + } + return true; + } + }; + + private URIish remoteURI; + + private URIish secureURI; + + private RevBlob A_txt; + + private RevCommit A, B; + + @Parameters + public static Collection data() { + // run all tests with both connection factories we have + return Arrays.asList(new Object[][] { + { new JDKHttpConnectionFactory() }, + { new HttpClientConnectionFactory() } }); + } + + public SmartClientSmartServerSslTest(HttpConnectionFactory cf) { + HttpTransport.setConnectionFactory(cf); + } + + @Override + protected AppServer createServer() { + return new AppServer(0, 0); + } + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + final TestRepository src = createTestRepository(); + final String srcName = src.getRepository().getDirectory().getName(); + src.getRepository() + .getConfig() + .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); + + GitServlet gs = new GitServlet(); + + ServletContextHandler app = addNormalContext(gs, src, srcName); + + server.setUp(); + + remoteURI = toURIish(app, srcName); + secureURI = new URIish(rewriteUrl(remoteURI.toString(), "https", + server.getSecurePort())); + + A_txt = src.blob("A"); + A = src.commit().add("A_txt", A_txt).create(); + B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); + src.update(master, B); + + src.update("refs/garbage/a/very/long/ref/name/to/compress", B); + } + + private ServletContextHandler addNormalContext(GitServlet gs, TestRepository src, String srcName) { + ServletContextHandler app = server.addContext("/git"); + app.addFilter(new FilterHolder(new Filter() { + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // empty + } + + // Redirects http to https for requests containing "/https/". + @Override + public void doFilter(ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse httpServletResponse = (HttpServletResponse) response; + final HttpServletRequest httpServletRequest = (HttpServletRequest) request; + final StringBuffer fullUrl = httpServletRequest.getRequestURL(); + if (httpServletRequest.getQueryString() != null) { + fullUrl.append("?") + .append(httpServletRequest.getQueryString()); + } + String urlString = rewriteUrl(fullUrl.toString(), "https", + server.getSecurePort()); + httpServletResponse + .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); + httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, + urlString.replace("/https/", "/")); + } + + @Override + public void destroy() { + // empty + } + }), "/https/*", EnumSet.of(DispatcherType.REQUEST)); + app.addFilter(new FilterHolder(new Filter() { + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // empty + } + + // Redirects https back to http for requests containing "/back/". + @Override + public void doFilter(ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse httpServletResponse = (HttpServletResponse) response; + final HttpServletRequest httpServletRequest = (HttpServletRequest) request; + final StringBuffer fullUrl = httpServletRequest.getRequestURL(); + if (httpServletRequest.getQueryString() != null) { + fullUrl.append("?") + .append(httpServletRequest.getQueryString()); + } + String urlString = rewriteUrl(fullUrl.toString(), "http", + server.getPort()); + httpServletResponse + .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); + httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, + urlString.replace("/back/", "/")); + } + + @Override + public void destroy() { + // empty + } + }), "/back/*", EnumSet.of(DispatcherType.REQUEST)); + gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName)); + app.addServlet(new ServletHolder(gs), "/*"); + return app; + } + + @Test + public void testInitialClone_ViaHttps() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, secureURI)) { + t.setCredentialsProvider(testCredentials); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(2, requests.size()); + } + + @Test + public void testInitialClone_RedirectToHttps() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(remoteURI, "/https"); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.setCredentialsProvider(testCredentials); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(3, requests.size()); + } + + @Test + public void testInitialClone_RedirectBackToHttp() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(secureURI, "/back"); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.setCredentialsProvider(testCredentials); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (redirect from https to http)"); + } catch (TransportException e) { + assertTrue(e.getMessage().contains("not allowed")); + } + } + + @Test + public void testInitialClone_SslFailure() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, secureURI)) { + // Set a credentials provider that doesn't handle questions + t.setCredentialsProvider( + new UsernamePasswordCredentialsProvider("any", "anypwd")); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (SSL certificate not trusted)"); + } catch (TransportException e) { + assertTrue(e.getMessage().contains("Secure connection")); + } + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java --- jgit-4.1.2/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Google Inc. + * Copyright (C) 2010, 2017 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -43,6 +43,7 @@ package org.eclipse.jgit.http.test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; @@ -56,17 +57,21 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.URISyntaxException; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; +import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -77,18 +82,23 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jgit.errors.RemoteRepositoryException; -import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.junit.http.AccessEvent; +import org.eclipse.jgit.junit.http.AppServer; import org.eclipse.jgit.junit.http.HttpTestCase; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogReader; @@ -96,19 +106,35 @@ import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook; +import org.eclipse.jgit.transport.AdvertiseRefsHook; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.TransportHttp; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UploadPack; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; -import org.eclipse.jgit.transport.resolver.RepositoryResolver; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.HttpSupport; +import org.eclipse.jgit.util.SystemReader; +import org.hamcrest.Matchers; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -117,15 +143,29 @@ public class SmartClientSmartServerTest extends HttpTestCase { private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private AdvertiseRefsHook advertiseRefsHook; + private Repository remoteRepository; + private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider( + AppServer.username, AppServer.password); + private URIish remoteURI; private URIish brokenURI; + private URIish redirectURI; + + private URIish authURI; + + private URIish authOnPostURI; + private RevBlob A_txt; - private RevCommit A, B; + private RevCommit A, B, unreachableCommit; @Parameters public static Collection data() { @@ -139,6 +179,7 @@ HttpTransport.setConnectionFactory(cf); } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -150,24 +191,108 @@ .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); - ServletContextHandler app = server.addContext("/git"); GitServlet gs = new GitServlet(); - gs.setRepositoryResolver(new RepositoryResolver() { - public Repository open(HttpServletRequest req, String name) - throws RepositoryNotFoundException, - ServiceNotEnabledException { - if (!name.equals(srcName)) - throw new RepositoryNotFoundException(name); - - final Repository db = src.getRepository(); - db.incrementOpen(); - return db; + gs.setUploadPackFactory(new UploadPackFactory() { + @Override + public UploadPack create(HttpServletRequest req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + DefaultUploadPackFactory f = new DefaultUploadPackFactory(); + UploadPack up = f.create(req, db); + if (advertiseRefsHook != null) { + up.setAdvertiseRefsHook(advertiseRefsHook); + } + return up; } }); + + ServletContextHandler app = addNormalContext(gs, src, srcName); + + ServletContextHandler broken = addBrokenContext(gs, src, srcName); + + ServletContextHandler redirect = addRedirectContext(gs); + + ServletContextHandler auth = addAuthContext(gs, "auth"); + + ServletContextHandler authOnPost = addAuthContext(gs, "pauth", "POST"); + + server.setUp(); + + remoteRepository = src.getRepository(); + remoteURI = toURIish(app, srcName); + brokenURI = toURIish(broken, srcName); + redirectURI = toURIish(redirect, srcName); + authURI = toURIish(auth, srcName); + authOnPostURI = toURIish(authOnPost, srcName); + + A_txt = src.blob("A"); + A = src.commit().add("A_txt", A_txt).create(); + B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); + src.update(master, B); + + unreachableCommit = src.commit().add("A_txt", A_txt).create(); + + src.update("refs/garbage/a/very/long/ref/name/to/compress", B); + } + + private ServletContextHandler addNormalContext(GitServlet gs, TestRepository src, String srcName) { + ServletContextHandler app = server.addContext("/git"); + app.addFilter(new FilterHolder(new Filter() { + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // empty + } + + // Does an internal forward for GET requests containing "/post/", + // and issues a 301 redirect on POST requests for such URLs. Used + // in the POST redirect tests. + @Override + public void doFilter(ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse httpServletResponse = (HttpServletResponse) response; + final HttpServletRequest httpServletRequest = (HttpServletRequest) request; + final StringBuffer fullUrl = httpServletRequest.getRequestURL(); + if (httpServletRequest.getQueryString() != null) { + fullUrl.append("?") + .append(httpServletRequest.getQueryString()); + } + String urlString = fullUrl.toString(); + if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) { + httpServletResponse.setStatus( + HttpServletResponse.SC_MOVED_PERMANENTLY); + httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, + urlString.replace("/post/", "/")); + } else { + String path = httpServletRequest.getPathInfo(); + path = path.replace("/post/", "/"); + if (httpServletRequest.getQueryString() != null) { + path += '?' + httpServletRequest.getQueryString(); + } + RequestDispatcher dispatcher = httpServletRequest + .getRequestDispatcher(path); + dispatcher.forward(httpServletRequest, httpServletResponse); + } + } + + @Override + public void destroy() { + // empty + } + }), "/post/*", EnumSet.of(DispatcherType.REQUEST)); + gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName)); app.addServlet(new ServletHolder(gs), "/*"); + return app; + } + @SuppressWarnings("unused") + private ServletContextHandler addBrokenContext(GitServlet gs, TestRepository src, String srcName) { ServletContextHandler broken = server.addContext("/bad"); broken.addFilter(new FilterHolder(new Filter() { + + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -179,29 +304,114 @@ w.close(); } - public void init(FilterConfig filterConfig) throws ServletException { - // + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // empty } + @Override public void destroy() { - // + // empty } }), "/" + srcName + "/git-upload-pack", EnumSet.of(DispatcherType.REQUEST)); broken.addServlet(new ServletHolder(gs), "/*"); + return broken; + } - server.setUp(); + private ServletContextHandler addAuthContext(GitServlet gs, + String contextPath, String... methods) { + ServletContextHandler auth = server.addContext('/' + contextPath); + auth.addServlet(new ServletHolder(gs), "/*"); + return server.authBasic(auth, methods); + } - remoteRepository = src.getRepository(); - remoteURI = toURIish(app, srcName); - brokenURI = toURIish(broken, srcName); + private ServletContextHandler addRedirectContext(GitServlet gs) { + ServletContextHandler redirect = server.addContext("/redirect"); + redirect.addFilter(new FilterHolder(new Filter() { + + // Enables tests for different codes, and for multiple redirects. + // First parameter is the number of redirects, second one is the + // redirect status code that should be used + private Pattern responsePattern = Pattern + .compile("/response/(\\d+)/(30[1237])/"); + + // Enables tests to specify the context that the request should be + // redirected to in the end. If not present, redirects got to the + // normal /git context. + private Pattern targetPattern = Pattern.compile("/target(/\\w+)/"); + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // empty + } - A_txt = src.blob("A"); - A = src.commit().add("A_txt", A_txt).create(); - B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); - src.update(master, B); + @Override + public void doFilter(ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse httpServletResponse = (HttpServletResponse) response; + final HttpServletRequest httpServletRequest = (HttpServletRequest) request; + final StringBuffer fullUrl = httpServletRequest.getRequestURL(); + if (httpServletRequest.getQueryString() != null) { + fullUrl.append("?") + .append(httpServletRequest.getQueryString()); + } + String urlString = fullUrl.toString(); + if (urlString.contains("/loop/")) { + urlString = urlString.replace("/loop/", "/loop/x/"); + if (urlString.contains("/loop/x/x/x/x/x/x/x/x/")) { + // Go back to initial. + urlString = urlString.replace("/loop/x/x/x/x/x/x/x/x/", + "/loop/"); + } + httpServletResponse.setStatus( + HttpServletResponse.SC_MOVED_TEMPORARILY); + httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, + urlString); + return; + } + int responseCode = HttpServletResponse.SC_MOVED_PERMANENTLY; + int nofRedirects = 0; + Matcher matcher = responsePattern.matcher(urlString); + if (matcher.find()) { + nofRedirects = Integer + .parseUnsignedInt(matcher.group(1)); + responseCode = Integer.parseUnsignedInt(matcher.group(2)); + if (--nofRedirects <= 0) { + urlString = urlString.substring(0, matcher.start()) + + '/' + urlString.substring(matcher.end()); + } else { + urlString = urlString.substring(0, matcher.start()) + + "/response/" + nofRedirects + "/" + + responseCode + '/' + + urlString.substring(matcher.end()); + } + } + httpServletResponse.setStatus(responseCode); + if (nofRedirects <= 0) { + String targetContext = "/git"; + matcher = targetPattern.matcher(urlString); + if (matcher.find()) { + urlString = urlString.substring(0, matcher.start()) + + '/' + urlString.substring(matcher.end()); + targetContext = matcher.group(1); + } + urlString = urlString.replace("/redirect", targetContext); + } + httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, + urlString); + } - src.update("refs/garbage/a/very/long/ref/name/to/compress", B); + @Override + public void destroy() { + // empty + } + }), "/*", EnumSet.of(DispatcherType.REQUEST)); + redirect.addServlet(new ServletHolder(gs), "/*"); + return redirect; } @Test @@ -211,8 +421,7 @@ assertEquals("http", remoteURI.getScheme()); Map map; - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { // I didn't make up these public interface names, I just // approved them for inclusion into the code base. Sorry. // --spearce @@ -226,8 +435,6 @@ } finally { c.close(); } - } finally { - t.close(); } assertNotNull("have map of refs", map); @@ -257,8 +464,7 @@ public void testListRemote_BadName() throws IOException, URISyntaxException { Repository dst = createBareRepository(); URIish uri = new URIish(this.remoteURI.toString() + ".invalid"); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("fetch connection opened"); @@ -266,8 +472,6 @@ assertEquals(uri + ": Git repository not found", notFound.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -284,19 +488,66 @@ } @Test + public void testFetchBySHA1() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, remoteURI)) { + t.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(B.name()))); + } + + assertTrue(dst.hasObject(A_txt)); + } + + @Test + public void testFetchBySHA1Unreachable() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, remoteURI)) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + unreachableCommit.name() + " not valid")); + t.fetch(NullProgressMonitor.INSTANCE, Collections + .singletonList(new RefSpec(unreachableCommit.name()))); + } + } + + @Test + public void testFetchBySHA1UnreachableByAdvertiseRefsHook() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + advertiseRefsHook = new AbstractAdvertiseRefsHook() { + @Override + protected Map getAdvertisedRefs(Repository repository, + RevWalk revWalk) { + return Collections.emptyMap(); + } + }; + + try (Transport t = Transport.open(dst, remoteURI)) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + A.name() + " not valid")); + t.fetch(NullProgressMonitor.INSTANCE, Collections + .singletonList(new RefSpec(A.name()))); + } + } + + @Test public void testInitialClone_Small() throws Exception { Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List requests = getRequests(); @@ -326,18 +577,431 @@ .getResponseHeader(HDR_CONTENT_TYPE)); } + private void initialClone_Redirect(int nofRedirects, int code) + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = redirectURI; + if (code != 301 || nofRedirects > 1) { + cloneFrom = extendPath(cloneFrom, + "/response/" + nofRedirects + "/" + code); + } + try (Transport t = Transport.open(dst, cloneFrom)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(2 + nofRedirects, requests.size()); + + int n = 0; + while (n < nofRedirects) { + AccessEvent redirect = requests.get(n++); + assertEquals(code, redirect.getStatus()); + } + + AccessEvent info = requests.get(n++); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", + info.getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent service = requests.get(n++); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", + service.getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", + service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", + service.getResponseHeader(HDR_CONTENT_TYPE)); + } + + @Test + public void testInitialClone_Redirect301Small() throws Exception { + initialClone_Redirect(1, 301); + } + + @Test + public void testInitialClone_Redirect302Small() throws Exception { + initialClone_Redirect(1, 302); + } + + @Test + public void testInitialClone_Redirect303Small() throws Exception { + initialClone_Redirect(1, 303); + } + + @Test + public void testInitialClone_Redirect307Small() throws Exception { + initialClone_Redirect(1, 307); + } + + @Test + public void testInitialClone_RedirectMultiple() throws Exception { + initialClone_Redirect(4, 302); + } + + @Test + public void testInitialClone_RedirectMax() throws Exception { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + userConfig.setInt("http", null, "maxRedirects", 4); + userConfig.save(); + initialClone_Redirect(4, 302); + } + + @Test + public void testInitialClone_RedirectTooOften() throws Exception { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + userConfig.setInt("http", null, "maxRedirects", 3); + userConfig.save(); + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(redirectURI, "/response/4/302"); + String remoteUri = cloneFrom.toString(); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (too many redirects)"); + } catch (TransportException e) { + String expectedMessageBegin = remoteUri.toString() + ": " + + MessageFormat.format(JGitText.get().redirectLimitExceeded, + "3", remoteUri.replace("/4/", "/1/") + '/', ""); + String message = e.getMessage(); + if (message.length() > expectedMessageBegin.length()) { + message = message.substring(0, expectedMessageBegin.length()); + } + assertEquals(expectedMessageBegin, message); + } + } + + @Test + public void testInitialClone_RedirectLoop() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(redirectURI, "/loop"); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (redirect loop)"); + } catch (TransportException e) { + assertTrue(e.getMessage().contains("Redirected more than")); + } + } + + @Test + public void testInitialClone_RedirectOnPostAllowed() throws Exception { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + userConfig.setString("http", null, "followRedirects", "true"); + userConfig.save(); + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(remoteURI, "/post"); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(3, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(cloneFrom, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", + info.getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent redirect = requests.get(1); + assertEquals("POST", redirect.getMethod()); + assertEquals(301, redirect.getStatus()); + + AccessEvent service = requests.get(2); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", + service.getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", + service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", + service.getResponseHeader(HDR_CONTENT_TYPE)); + } + + @Test + public void testInitialClone_RedirectOnPostForbidden() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(remoteURI, "/post"); + try (Transport t = Transport.open(dst, cloneFrom)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (redirect on POST)"); + } catch (TransportException e) { + assertTrue(e.getMessage().contains("301")); + } + } + + @Test + public void testInitialClone_RedirectForbidden() throws Exception { + FileBasedConfig userConfig = SystemReader.getInstance() + .openUserConfig(null, FS.DETECTED); + userConfig.setString("http", null, "followRedirects", "false"); + userConfig.save(); + + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, redirectURI)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should have failed (redirects forbidden)"); + } catch (TransportException e) { + assertTrue( + e.getMessage().contains("http.followRedirects is false")); + } + } + + @Test + public void testInitialClone_WithAuthentication() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, authURI)) { + t.setCredentialsProvider(testCredentials); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(3, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(401, info.getStatus()); + + info = requests.get(1); + assertEquals("GET", info.getMethod()); + assertEquals(join(authURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", + info.getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent service = requests.get(2); + assertEquals("POST", service.getMethod()); + assertEquals(join(authURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", + service.getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", + service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", + service.getResponseHeader(HDR_CONTENT_TYPE)); + } + + @Test + public void testInitialClone_WithAuthenticationNoCredentials() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, authURI)) { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should not have succeeded -- no authentication"); + } catch (TransportException e) { + String msg = e.getMessage(); + assertTrue("Unexpected exception message: " + msg, + msg.contains("no CredentialsProvider")); + } + List requests = getRequests(); + assertEquals(1, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(401, info.getStatus()); + } + + @Test + public void testInitialClone_WithAuthenticationWrongCredentials() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, authURI)) { + t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( + AppServer.username, "wrongpassword")); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("Should not have succeeded -- wrong password"); + } catch (TransportException e) { + String msg = e.getMessage(); + assertTrue("Unexpected exception message: " + msg, + msg.contains("auth")); + } + List requests = getRequests(); + // Once without authentication plus three re-tries with authentication + assertEquals(4, requests.size()); + + for (AccessEvent event : requests) { + assertEquals("GET", event.getMethod()); + assertEquals(401, event.getStatus()); + } + } + + @Test + public void testInitialClone_WithAuthenticationAfterRedirect() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + URIish cloneFrom = extendPath(redirectURI, "/target/auth"); + CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider( + "unknown", "none") { + @Override + public boolean get(URIish uri, CredentialItem... items) + throws UnsupportedCredentialItem { + // Only return the true credentials if the uri path starts with + // /auth. This ensures that we do provide the correct + // credentials only for the URi after the redirect, making the + // test fail if we should be asked for the credentials for the + // original URI. + if (uri.getPath().startsWith("/auth")) { + return testCredentials.get(uri, items); + } + return super.get(uri, items); + } + }; + try (Transport t = Transport.open(dst, cloneFrom)) { + t.setCredentialsProvider(uriSpecificCredentialsProvider); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(4, requests.size()); + + AccessEvent redirect = requests.get(0); + assertEquals("GET", redirect.getMethod()); + assertEquals(join(cloneFrom, "info/refs"), redirect.getPath()); + assertEquals(301, redirect.getStatus()); + + AccessEvent info = requests.get(1); + assertEquals("GET", info.getMethod()); + assertEquals(join(authURI, "info/refs"), info.getPath()); + assertEquals(401, info.getStatus()); + + info = requests.get(2); + assertEquals("GET", info.getMethod()); + assertEquals(join(authURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", + info.getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent service = requests.get(3); + assertEquals("POST", service.getMethod()); + assertEquals(join(authURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", + service.getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", + service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", + service.getResponseHeader(HDR_CONTENT_TYPE)); + } + + @Test + public void testInitialClone_WithAuthenticationOnPostOnly() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, authOnPostURI)) { + t.setCredentialsProvider(testCredentials); + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.exactRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(3, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(authOnPostURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", + info.getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath()); + assertEquals(401, service.getStatus()); + + service = requests.get(2); + assertEquals("POST", service.getMethod()); + assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", + service.getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", + service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", + service.getResponseHeader(HDR_CONTENT_TYPE)); + } + @Test public void testFetch_FewLocalCommits() throws Exception { // Bootstrap by doing the clone. // TestRepository dst = createTestRepository(); - Transport t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } - assertEquals(B, dst.getRepository().getRef(master).getObjectId()); + assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); // Only create a few new commits. @@ -347,18 +1011,15 @@ // Create a new commit on the remote. // - b = new TestRepository(remoteRepository).branch(master); + b = new TestRepository<>(remoteRepository).branch(master); RevCommit Z = b.commit().message("Z").create(); // Now incrementally update. // - t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } - assertEquals(Z, dst.getRepository().getRef(master).getObjectId()); + assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); List requests = getRequests(); requests.removeAll(cloneRequests); @@ -394,13 +1055,10 @@ // Bootstrap by doing the clone. // TestRepository dst = createTestRepository(); - Transport t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } - assertEquals(B, dst.getRepository().getRef(master).getObjectId()); + assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); // Force enough into the local client that enumeration will @@ -413,18 +1071,15 @@ // Create a new commit on the remote. // - b = new TestRepository(remoteRepository).branch(master); + b = new TestRepository<>(remoteRepository).branch(master); RevCommit Z = b.commit().message("Z").create(); // Now incrementally update. // - t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } - assertEquals(Z, dst.getRepository().getRef(master).getObjectId()); + assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); List requests = getRequests(); requests.removeAll(cloneRequests); @@ -474,19 +1129,16 @@ Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, brokenURI); - try { + try (Transport t = Transport.open(dst, brokenURI)) { try { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); fail("fetch completed despite upload-pack being broken"); } catch (TransportException err) { String exp = brokenURI + ": expected" + " Content-Type application/x-git-upload-pack-result;" - + " received Content-Type text/plain; charset=UTF-8"; + + " received Content-Type text/plain;charset=utf-8"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -506,23 +1158,85 @@ assertEquals(join(brokenURI, "git-upload-pack"), service.getPath()); assertEquals(0, service.getParameters().size()); assertEquals(200, service.getStatus()); - assertEquals("text/plain; charset=UTF-8", + assertEquals("text/plain;charset=utf-8", service.getResponseHeader(HDR_CONTENT_TYPE)); } @Test + public void testInvalidWant() throws Exception { + @SuppressWarnings("resource") + ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB, + "testInvalidWant".getBytes(UTF_8)); + + Repository dst = createBareRepository(); + try (Transport t = Transport.open(dst, remoteURI); + FetchConnection c = t.openFetch()) { + Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), + id); + c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want), + Collections. emptySet()); + fail("Server accepted want " + id.name()); + } catch (TransportException err) { + assertEquals("want " + id.name() + " not valid", err.getMessage()); + } + } + + @Test + public void testFetch_RefsUnreadableOnUpload() throws Exception { + AppServer noRefServer = new AppServer(); + try { + final String repoName = "refs-unreadable"; + RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository( + new DfsRepositoryDescription(repoName)); + final TestRepository repo = new TestRepository<>( + badRefsRepo); + + ServletContextHandler app = noRefServer.addContext("/git"); + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver(new TestRepositoryResolver(repo, repoName)); + app.addServlet(new ServletHolder(gs), "/*"); + noRefServer.setUp(); + + RevBlob A2_txt = repo.blob("A2"); + RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create(); + RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2") + .add("B2", "B2").create(); + repo.update(master, B2); + + URIish badRefsURI = new URIish(noRefServer.getURI() + .resolve(app.getContextPath() + "/" + repoName).toString()); + + Repository dst = createBareRepository(); + try (Transport t = Transport.open(dst, badRefsURI); + FetchConnection c = t.openFetch()) { + // We start failing here to exercise the post-advertisement + // upload pack handler. + badRefsRepo.startFailing(); + // Need to flush caches because ref advertisement populated them. + badRefsRepo.getRefDatabase().refresh(); + c.fetch(NullProgressMonitor.INSTANCE, + Collections.singleton(c.getRef(master)), + Collections. emptySet()); + fail("Successfully served ref with value " + c.getRef(master)); + } catch (TransportException err) { + assertEquals("internal server error", err.getMessage()); + } + } finally { + noRefServer.tearDown(); + } + } + + @Test public void testPush_NotAuthorized() throws Exception { final TestRepository src = createTestRepository(); final RevBlob Q_txt = src.blob("new text"); final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; // push anonymous shouldn't be allowed. // - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -538,8 +1252,6 @@ + JGitText.get().authenticationNotSupported; assertEquals(exp, e.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -560,12 +1272,10 @@ final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; enableReceivePack(); - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -574,17 +1284,15 @@ RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), srcExpr, dstName, forceUpdate, localName, oldId); t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); - } finally { - t.close(); } assertTrue(remoteRepository.hasObject(Q_txt)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); final ReflogReader log = remoteRepository.getReflogReader(dstName); - assertNotNull("has log for " + dstName); + assertNotNull("has log for " + dstName, log); final ReflogEntry last = log.getLastEntry(); assertNotNull("has last entry", last); @@ -633,7 +1341,6 @@ final RevCommit Q = src.commit().add("Q", Q_bin).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; enableReceivePack(); @@ -642,8 +1349,7 @@ cfg.setInt("http", null, "postbuffer", 8 * 1024); cfg.save(); - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -652,13 +1358,11 @@ RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), srcExpr, dstName, forceUpdate, localName, oldId); t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); - } finally { - t.close(); } assertTrue(remoteRepository.hasObject(Q_bin)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); List requests = getRequests(); @@ -691,4 +1395,5 @@ cfg.setBoolean("http", null, "receivepack", true); cfg.save(); } + } diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/BUILD jgit-4.11.9/org.eclipse.jgit.junit/BUILD --- jgit-4.1.2/org.eclipse.jgit.junit/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,14 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "junit", + testonly = 1, + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.junit/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:junit", + # We want these deps to be provided_deps + "//org.eclipse.jgit:jgit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/.classpath jgit-4.11.9/org.eclipse.jgit.junit/.classpath --- jgit-4.1.2/org.eclipse.jgit.junit/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.junit/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.junit/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,28 +1,33 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.junit Bundle-SymbolicName: org.eclipse.jgit.junit -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -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.1.2"; +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.eclipse.jgit.api;version="[4.11.9,4.12.0)", + org.eclipse.jgit.api.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.dircache;version="[4.11.9,4.12.0)", + org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.merge;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk.filter;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util.io;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util.time;version="[4.11.9,4.12.0)", + org.junit;version="[4.12,5.0.0)", + org.junit.rules;version="[4.12,5.0.0)", + org.junit.runner;version="[4.12,5.0.0)", + org.junit.runners.model;version="[4.12,5.0.0)" +Export-Package: org.eclipse.jgit.junit;version="4.11.9"; uses:="org.eclipse.jgit.dircache, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, @@ -30,4 +35,5 @@ org.eclipse.jgit.treewalk, org.eclipse.jgit.util, org.eclipse.jgit.storage.file, - org.eclipse.jgit.api" + org.eclipse.jgit.api", + org.eclipse.jgit.junit.time;version="4.11.9" diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/pom.xml jgit-4.11.9/org.eclipse.jgit.junit/pom.xml --- jgit-4.1.2/org.eclipse.jgit.junit/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.junit diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Assert.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Assert.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Assert.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Assert.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,12 +44,33 @@ import static java.lang.Boolean.valueOf; +/** + * Assertion class + */ public class Assert { + /** + * Assert booleans are equal + * + * @param expect + * expected value + * @param actual + * actual value + */ public static void assertEquals(boolean expect, boolean actual) { org.junit.Assert.assertEquals(valueOf(expect), valueOf(actual)); } + /** + * Assert booleans are equal + * + * @param message + * message + * @param expect + * expected value + * @param actual + * actual value + */ public static void assertEquals(String message, boolean expect, boolean actual) { org.junit.Assert diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,8 @@ package org.eclipse.jgit.junit; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -55,6 +57,7 @@ import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Path; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FileUtils; @@ -63,13 +66,22 @@ import org.junit.Assert; import org.junit.Test; +/** + * Abstract test util class + */ public abstract class JGitTestUtil { + /** Constant CLASSPATH_TO_RESOURCES="org/eclipse/jgit/test/resources/" */ public static final String CLASSPATH_TO_RESOURCES = "org/eclipse/jgit/test/resources/"; private JGitTestUtil() { throw new UnsupportedOperationException(); } + /** + * Get name of current test by inspecting stack trace + * + * @return the name + */ public static String getName() { GatherStackTrace stack; try { @@ -108,6 +120,14 @@ // Thrown above to collect the stack frame. } + /** + * Assert byte arrays are equal + * + * @param exp + * expected value + * @param act + * actual value + */ public static void assertEquals(byte[] exp, byte[] act) { Assert.assertEquals(s(exp), s(act)); } @@ -116,6 +136,12 @@ return RawParseUtils.decode(raw); } + /** + * Get test resource file. + * + * @param fileName + * @return the test resource file + */ public static File getTestResourceFile(final String fileName) { if (fileName == null || fileName.length() <= 0) { return null; @@ -144,23 +170,23 @@ } } + /** + * Copy test resource. + * + * @param name + * @param dest + * @throws IOException + */ public static void copyTestResource(String name, File dest) throws IOException { URL url = cl().getResource(CLASSPATH_TO_RESOURCES + name); if (url == null) throw new FileNotFoundException(name); - InputStream in = url.openStream(); - try { - FileOutputStream out = new FileOutputStream(dest); - try { - byte[] buf = new byte[4096]; - for (int n; (n = in.read(buf)) > 0;) - out.write(buf, 0, n); - } finally { - out.close(); - } - } finally { - in.close(); + try (InputStream in = url.openStream(); + FileOutputStream out = new FileOutputStream(dest)) { + byte[] buf = new byte[4096]; + for (int n; (n = in.read(buf)) > 0;) + out.write(buf, 0, n); } } @@ -168,6 +194,15 @@ return JGitTestUtil.class.getClassLoader(); } + /** + * Write a trash file. + * + * @param db + * @param name + * @param data + * @return the trash file + * @throws IOException + */ public static File writeTrashFile(final Repository db, final String name, final String data) throws IOException { File path = new File(db.getWorkTree(), name); @@ -175,6 +210,16 @@ return path; } + /** + * Write a trash file. + * + * @param db + * @param subdir + * @param name + * @param data + * @return the trash file + * @throws IOException + */ public static File writeTrashFile(final Repository db, final String subdir, final String name, final String data) throws IOException { @@ -200,11 +245,9 @@ public static void write(final File f, final String body) throws IOException { FileUtils.mkdirs(f.getParentFile(), true); - Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8"); - try { + try (Writer w = new OutputStreamWriter(new FileOutputStream(f), + UTF_8)) { w.write(body); - } finally { - w.close(); } } @@ -220,24 +263,89 @@ */ public static String read(final File file) throws IOException { final byte[] body = IO.readFully(file); - return new String(body, 0, body.length, "UTF-8"); + return new String(body, 0, body.length, UTF_8); } + /** + * Read a file's content + * + * @param db + * @param name + * @return the content of the file + * @throws IOException + */ public static String read(final Repository db, final String name) throws IOException { File file = new File(db.getWorkTree(), name); return read(file); } + /** + * Check if file exists + * + * @param db + * @param name + * name of the file + * @return {@code true} if the file exists + */ public static boolean check(final Repository db, final String name) { File file = new File(db.getWorkTree(), name); return file.exists(); } + /** + * Delete a trash file. + * + * @param db + * @param name + * @throws IOException + */ public static void deleteTrashFile(final Repository db, final String name) throws IOException { File path = new File(db.getWorkTree(), name); FileUtils.delete(path); } + /** + * Write a symbolic link + * + * @param db + * the repository + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @return the path to the symbolic link + * @throws Exception + * @since 4.2 + */ + public static Path writeLink(Repository db, String link, + String target) throws Exception { + return FileUtils.createSymLink(new File(db.getWorkTree(), link), + target); + } + + /** + * Concatenate byte arrays. + * + * @param b + * byte arrays to combine together. + * @return a single byte array that contains all bytes copied from input + * byte arrays. + * @since 4.9 + */ + public static byte[] concat(byte[]... b) { + int n = 0; + for (byte[] a : b) { + n += a.length; + } + + byte[] data = new byte[n]; + n = 0; + for (byte[] a : b) { + System.arraycopy(a, 0, data, n, a.length); + n += a.length; + } + return data; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,18 +45,30 @@ package org.eclipse.jgit.junit; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; -import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; 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.*; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.FS; @@ -71,8 +83,9 @@ * A temporary directory is created for each test, allowing each test to use a * fresh environment. The temporary directory is cleaned up after the test ends. *

        - * Callers should not use {@link RepositoryCache} from within these tests as it - * may wedge file descriptors open past the end of the test. + * Callers should not use {@link org.eclipse.jgit.lib.RepositoryCache} from + * within these tests as it may wedge file descriptors open past the end of the + * test. *

        * A system property {@code jgit.junit.usemmap} defines whether memory mapping * is used. Memory mapping has an effect on the file system, in that memory @@ -92,11 +105,20 @@ /** A fake (but stable) identity for committer fields in the test. */ protected PersonIdent committer; - private final List toClose = new ArrayList(); - private File tmp; + /** + * A {@link SystemReader} used to coordinate time, envars, etc. + * @since 4.2 + */ + protected MockSystemReader mockSystemReader; - private MockSystemReader mockSystemReader; + private final Set toClose = new HashSet<>(); + private File tmp; + /** + * Setup test + * + * @throws Exception + */ @Before public void setUp() throws Exception { tmp = File.createTempFile("jgit_test_", "_tmp"); @@ -107,16 +129,17 @@ mockSystemReader = new MockSystemReader(); mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, "usergitconfig"), FS.DETECTED); + // We have to set autoDetach to false for tests, because tests expect to be able + // to clean up by recursively removing the repository, and background GC might be + // in the middle of writing or deleting files, which would disrupt this. + mockSystemReader.userGitConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, + null, ConfigConstants.CONFIG_KEY_AUTODETACH, false); + mockSystemReader.userGitConfig.save(); ceilTestDirectories(getCeilings()); SystemReader.setInstance(mockSystemReader); - final long now = mockSystemReader.getCurrentTime(); - final int tz = mockSystemReader.getTimezone(now); author = new PersonIdent("J. Author", "jauthor@example.com"); - author = new PersonIdent(author, now, tz); - committer = new PersonIdent("J. Committer", "jcommitter@example.com"); - committer = new PersonIdent(committer, now, tz); final WindowCacheConfig c = new WindowCacheConfig(); c.setPackedGitLimit(128 * WindowCacheConfig.KB); @@ -126,10 +149,20 @@ c.install(); } + /** + * Get temporary directory. + * + * @return the temporary directory + */ protected File getTemporaryDirectory() { return tmp.getAbsoluteFile(); } + /** + * Get list of ceiling directories + * + * @return list of ceiling directories + */ protected List getCeilings() { return Collections.singletonList(getTemporaryDirectory()); } @@ -148,6 +181,11 @@ return stringBuilder.toString(); } + /** + * Tear down the test + * + * @throws Exception + */ @After public void tearDown() throws Exception { RepositoryCache.clear(); @@ -169,11 +207,12 @@ SystemReader.setInstance(null); } - /** Increment the {@link #author} and {@link #committer} times. */ + /** + * Increment the {@link #author} and {@link #committer} times. + */ protected void tick() { - final long delta = TimeUnit.MILLISECONDS.convert(5 * 60, - TimeUnit.SECONDS); - final long now = author.getWhen().getTime() + delta; + mockSystemReader.tick(5 * 60); + final long now = mockSystemReader.getCurrentTime(); final int tz = mockSystemReader.getTimezone(now); author = new PersonIdent(author, now, tz); @@ -224,16 +263,22 @@ System.err.println(msg); } + /** Constant MOD_TIME=1 */ public static final int MOD_TIME = 1; + /** Constant SMUDGE=2 */ public static final int SMUDGE = 2; + /** Constant LENGTH=4 */ public static final int LENGTH = 4; + /** Constant CONTENT_ID=8 */ public static final int CONTENT_ID = 8; + /** Constant CONTENT=16 */ public static final int CONTENT = 16; + /** Constant ASSUME_UNCHANGED=32 */ public static final int ASSUME_UNCHANGED = 32; /** @@ -264,7 +309,6 @@ * * @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 @@ -278,11 +322,10 @@ throws IllegalStateException, IOException { DirCache dc = repo.readDirCache(); StringBuilder sb = new StringBuilder(); - TreeSet timeStamps = null; + TreeSet timeStamps = new TreeSet<>(); // iterate once over the dircache just to collect all time stamps if (0 != (includedOptions & MOD_TIME)) { - timeStamps = new TreeSet(); for (int i=0; i cloneEnv() { - return new HashMap(System.getenv()); + return new HashMap<>(System.getenv()); } private static final class CleanupThread extends Thread { @@ -547,7 +618,7 @@ } } - private final List toDelete = new ArrayList(); + private final List toDelete = new ArrayList<>(); @Override public void run() { diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,10 +50,12 @@ import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Duration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; @@ -61,7 +63,12 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; +import org.eclipse.jgit.util.time.MonotonicClock; +import org.eclipse.jgit.util.time.ProposedTimestamp; +/** + * Mock {@link org.eclipse.jgit.util.SystemReader} for tests. + */ public class MockSystemReader extends SystemReader { private final class MockConfig extends FileBasedConfig { private MockConfig(File cfgLocation, FS fs) { @@ -79,12 +86,17 @@ } } - final Map values = new HashMap(); + long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + + final Map values = new HashMap<>(); FileBasedConfig userGitConfig; FileBasedConfig systemGitConfig; + /** + * Constructor for MockSystemReader + */ public MockSystemReader() { init(Constants.OS_USER_NAME_KEY); init(Constants.GIT_AUTHOR_NAME_KEY); @@ -101,66 +113,119 @@ setProperty(n, n); } + /** + * Clear properties + */ public void clearProperties() { values.clear(); } + /** + * Set a property + * + * @param key + * @param value + */ public void setProperty(String key, String value) { values.put(key, value); } + /** {@inheritDoc} */ @Override public String getenv(String variable) { return values.get(variable); } + /** {@inheritDoc} */ @Override public String getProperty(String key) { return values.get(key); } + /** {@inheritDoc} */ @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { assert parent == null || parent == systemGitConfig; return userGitConfig; } + /** {@inheritDoc} */ @Override public FileBasedConfig openSystemConfig(Config parent, FS fs) { assert parent == null; return systemGitConfig; } + /** {@inheritDoc} */ @Override public String getHostname() { return "fake.host.example.com"; } + /** {@inheritDoc} */ @Override public long getCurrentTime() { - return 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + return now; + } + + /** {@inheritDoc} */ + @Override + public MonotonicClock getClock() { + return new MonotonicClock() { + @Override + public ProposedTimestamp propose() { + long t = getCurrentTime(); + return new ProposedTimestamp() { + @Override + public long read(TimeUnit unit) { + return unit.convert(t, TimeUnit.MILLISECONDS); + } + + @Override + public void blockUntil(Duration maxWait) { + // Do not wait. + } + }; + } + }; + } + + /** + * Adjusts the current time in seconds. + * + * @param secDelta + * number of seconds to add to the current time. + * @since 4.2 + */ + public void tick(final int secDelta) { + now += secDelta * 1000L; } + /** {@inheritDoc} */ @Override public int getTimezone(long when) { return getTimeZone().getOffset(when) / (60 * 1000); } + /** {@inheritDoc} */ @Override public TimeZone getTimeZone() { return TimeZone.getTimeZone("GMT-03:30"); } + /** {@inheritDoc} */ @Override public Locale getLocale() { return Locale.US; } + /** {@inheritDoc} */ @Override public SimpleDateFormat getSimpleDateFormat(String pattern) { return new SimpleDateFormat(pattern, getLocale()); } + /** {@inheritDoc} */ @Override public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) { return DateFormat diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * 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.junit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation enabling to run tests repeatedly + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ java.lang.annotation.ElementType.METHOD }) +public @interface Repeat { + /** + * Number of repetitions + */ + public abstract int n(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * 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.junit; + +import java.text.MessageFormat; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * {@link org.junit.rules.TestRule} which enables to run the same JUnit test + * repeatedly. Add this rule to the test class + * + *

        + * public class MyTest {
        + * 	@Rule
        + * 	public RepeatRule repeatRule = new RepeatRule();
        + * 	...
        + * }
        + * 
        + * + * and annotate the test to be repeated with the + * {@code @Repeat(n=)} annotation + * + *
        + * @Test
        + * @Repeat(n = 100)
        + * public void test() {
        + * 	...
        + * }
        + * 
        + * + * then this test will be repeated 100 times. If any test execution fails test + * repetition will be stopped. + */ +public class RepeatRule implements TestRule { + + private static Logger LOG = Logger + .getLogger(RepeatRule.class.getName()); + + public static class RepeatedTestException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public RepeatedTestException(String message, Throwable cause) { + super(message, cause); + } + } + + private static class RepeatStatement extends Statement { + + private final int repetitions; + + private final Statement statement; + + private RepeatStatement(int repetitions, Statement statement) { + this.repetitions = repetitions; + this.statement = statement; + } + + @Override + public void evaluate() throws Throwable { + for (int i = 0; i < repetitions; i++) { + try { + statement.evaluate(); + } catch (Throwable e) { + RepeatedTestException ex = new RepeatedTestException( + MessageFormat.format( + "Repeated test failed when run for the {0}. time", + Integer.valueOf(i + 1)), + e); + LOG.log(Level.SEVERE, ex.getMessage(), ex); + throw ex; + } + } + } + } + + /** {@inheritDoc} */ + @Override + public Statement apply(Statement statement, Description description) { + Statement result = statement; + Repeat repeat = description.getAnnotation(Repeat.class); + if (repeat != null) { + int n = repeat.n(); + result = new RepeatStatement(n, statement); + } + return result; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ package org.eclipse.jgit.junit; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import java.io.File; @@ -55,6 +56,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.file.Path; import java.util.Map; import org.eclipse.jgit.api.Git; @@ -83,6 +85,13 @@ * repositories and destroying them when the tests are finished. */ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { + /** + * Copy a file + * + * @param src + * @param dst + * @throws IOException + */ protected static void copyFile(final File src, final File dst) throws IOException { final FileInputStream fis = new FileInputStream(src); @@ -102,39 +111,101 @@ } } + /** + * Write a trash file + * + * @param name + * @param data + * @return the trash file + * @throws IOException + */ protected File writeTrashFile(final String name, final String data) throws IOException { return JGitTestUtil.writeTrashFile(db, name, data); } + /** + * Create a symbolic link + * + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @return the path to the symbolic link + * @throws Exception + * @since 4.2 + */ + protected Path writeLink(final String link, final String target) + throws Exception { + return JGitTestUtil.writeLink(db, link, target); + } + + /** + * Write a trash file + * + * @param subdir + * @param name + * @param data + * @return the trash file + * @throws IOException + */ protected File writeTrashFile(final String subdir, final String name, final String data) throws IOException { return JGitTestUtil.writeTrashFile(db, subdir, name, data); } + /** + * Read content of a file + * + * @param name + * @return the file's content + * @throws IOException + */ protected String read(final String name) throws IOException { return JGitTestUtil.read(db, name); } + /** + * Check if file exists + * + * @param name + * file name + * @return if the file exists + */ protected boolean check(final String name) { return JGitTestUtil.check(db, name); } + /** + * Delete a trash file + * + * @param name + * file name + * @throws IOException + */ protected void deleteTrashFile(final String name) throws IOException { JGitTestUtil.deleteTrashFile(db, name); } + /** + * Check content of a file. + * + * @param f + * @param checkData + * expected content + * @throws IOException + */ protected static void checkFile(File f, final String checkData) throws IOException { - Reader r = new InputStreamReader(new FileInputStream(f), "ISO-8859-1"); - try { - char[] data = new char[(int) f.length()]; - if (f.length() != r.read(data)) - throw new IOException("Internal error reading file data from "+f); - assertEquals(checkData, new String(data)); - } finally { - r.close(); + try (Reader r = new InputStreamReader(new FileInputStream(f), + UTF_8)) { + if (checkData.length() > 0) { + char[] data = new char[checkData.length()]; + assertEquals(data.length, r.read(data)); + assertEquals(checkData, new String(data)); + } + assertEquals(-1, r.read()); } } @@ -144,6 +215,7 @@ /** Working directory of {@link #db}. */ protected File trash; + /** {@inheritDoc} */ @Override @Before public void setUp() throws Exception { @@ -203,8 +275,8 @@ * have an index which matches their prepared content. * * @param treeItr - * a {@link FileTreeIterator} which determines which files should - * go into the new index + * a {@link org.eclipse.jgit.treewalk.FileTreeIterator} which + * determines which files should go into the new index * @throws FileNotFoundException * @throws IOException */ @@ -244,13 +316,13 @@ * * @param l * the object to lookup + * @param lookupTable + * a table storing object-name mappings. * @param nameTemplate * the name for that object. Can contain "%n" which will be * replaced by a running number before used as a name. If the * lookup table already contains the object this parameter will * be ignored - * @param lookupTable - * a table storing object-name mappings. * @return a name of that object. Is not guaranteed to be unique. Use * nameTemplates containing "%n" to always have unique names */ @@ -266,6 +338,19 @@ } /** + * Replaces '\' by '/' + * + * @param str + * the string in which backslashes should be replaced + * @return the resulting string with slashes + * @since 4.2 + */ + public static String slashify(String str) { + str = str.replace('\\', '/'); + return str; + } + + /** * Waits until it is guaranteed that a subsequent file modification has a * younger modification timestamp than the modification timestamp of the * given file. This is done by touching a temporary file, reading the @@ -305,6 +390,13 @@ } } + /** + * Create a branch + * + * @param objectId + * @param branchName + * @throws IOException + */ protected void createBranch(ObjectId objectId, String branchName) throws IOException { RefUpdate updateRef = db.updateRef(branchName); @@ -312,6 +404,13 @@ updateRef.update(); } + /** + * Checkout a branch + * + * @param branchName + * @throws IllegalStateException + * @throws IOException + */ protected void checkoutBranch(String branchName) throws IllegalStateException, IOException { try (RevWalk walk = new RevWalk(db)) { @@ -371,13 +470,12 @@ * @return the created commit */ protected RevCommit commitFile(String filename, String contents, String branch) { - try { - Git git = new Git(db); + try (Git git = new Git(db)) { Repository repo = git.getRepository(); String originalBranch = repo.getFullBranch(); boolean empty = repo.resolve(Constants.HEAD) == null; if (!empty) { - if (repo.getRef(branch) == null) + if (repo.findRef(branch) == null) git.branchCreate().setName(branch).call(); git.checkout().setName(branch).call(); } @@ -400,24 +498,57 @@ } } + /** + * Create DirCacheEntry + * + * @param path + * @param mode + * @return the DirCacheEntry + */ protected DirCacheEntry createEntry(final String path, final FileMode mode) { return createEntry(path, mode, DirCacheEntry.STAGE_0, path); } + /** + * Create DirCacheEntry + * + * @param path + * @param mode + * @param content + * @return the DirCacheEntry + */ protected DirCacheEntry createEntry(final String path, final FileMode mode, final String content) { return createEntry(path, mode, DirCacheEntry.STAGE_0, content); } + /** + * Create DirCacheEntry + * + * @param path + * @param mode + * @param stage + * @param content + * @return the DirCacheEntry + */ protected DirCacheEntry createEntry(final String path, final FileMode mode, final int stage, final String content) { final DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setFileMode(mode); - entry.setObjectId(new ObjectInserter.Formatter().idFor( - Constants.OBJ_BLOB, Constants.encode(content))); + try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) { + entry.setObjectId(formatter.idFor( + Constants.OBJ_BLOB, Constants.encode(content))); + } return entry; } + /** + * Assert files are equal + * + * @param expected + * @param actual + * @throws IOException + */ public static void assertEqualsFile(File expected, File actual) throws IOException { assertEquals(expected.getCanonicalFile(), actual.getCanonicalFile()); diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 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.junit; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.lib.ProgressMonitor; + +/** + * Strict work monitor + */ +public final class StrictWorkMonitor implements ProgressMonitor { + private int lastWork, totalWork; + + /** {@inheritDoc} */ + @Override + public void start(int totalTasks) { + // empty + } + + /** {@inheritDoc} */ + @Override + public void beginTask(String title, int total) { + this.totalWork = total; + lastWork = 0; + } + + /** {@inheritDoc} */ + @Override + public void update(int completed) { + lastWork += completed; + } + + /** {@inheritDoc} */ + @Override + public void endTask() { + assertEquals("Units of work recorded", totalWork, lastWork); + } + + /** {@inheritDoc} */ + @Override + public boolean isCancelled() { + return false; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,9 +43,11 @@ package org.eclipse.jgit.junit; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -103,7 +105,6 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.util.ChangeIdUtil; import org.eclipse.jgit.util.FileUtils; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; /** * Wrapper to make creating test data easier. @@ -112,23 +113,22 @@ * type of Repository the test data is stored on. */ public class TestRepository { - private static final PersonIdent defaultAuthor; - private static final PersonIdent defaultCommitter; + /** Constant AUTHOR="J. Author" */ + public static final String AUTHOR = "J. Author"; - static { - final MockSystemReader m = new MockSystemReader(); - final long now = m.getCurrentTime(); - final int tz = m.getTimezone(now); - - final String an = "J. Author"; - final String ae = "jauthor@example.com"; - defaultAuthor = new PersonIdent(an, ae, now, tz); - - final String cn = "J. Committer"; - final String ce = "jcommitter@example.com"; - defaultCommitter = new PersonIdent(cn, ce, now, tz); - } + /** Constant AUTHOR_EMAIL="jauthor@example.com" */ + public static final String AUTHOR_EMAIL = "jauthor@example.com"; + + /** Constant COMMITTER="J. Committer" */ + public static final String COMMITTER = "J. Committer"; + + /** Constant COMMITTER_EMAIL="jcommitter@example.com" */ + public static final String COMMITTER_EMAIL = "jcommitter@example.com"; + + private final PersonIdent defaultAuthor; + + private final PersonIdent defaultCommitter; private final R db; @@ -138,7 +138,7 @@ private final ObjectInserter inserter; - private long now; + private final MockSystemReader mockSystemReader; /** * Wrap a repository with test building tools. @@ -148,7 +148,7 @@ * @throws IOException */ public TestRepository(R db) throws IOException { - this(db, new RevWalk(db)); + this(db, new RevWalk(db), new MockSystemReader()); } /** @@ -161,39 +161,81 @@ * @throws IOException */ public TestRepository(R db, RevWalk rw) throws IOException { + this(db, rw, new MockSystemReader()); + } + + /** + * Wrap a repository with test building tools. + * + * @param db + * the test repository to write into. + * @param rw + * the RevObject pool to use for object lookup. + * @param reader + * the MockSystemReader to use for clock and other system + * operations. + * @throws IOException + * @since 4.2 + */ + public TestRepository(R db, RevWalk rw, MockSystemReader reader) + throws IOException { this.db = db; this.git = Git.wrap(db); this.pool = rw; this.inserter = db.newObjectInserter(); - this.now = 1236977987000L; + this.mockSystemReader = reader; + long now = mockSystemReader.getCurrentTime(); + int tz = mockSystemReader.getTimezone(now); + defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz); + defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz); } - /** @return the repository this helper class operates against. */ + /** + * Get repository + * + * @return the repository this helper class operates against. + */ public R getRepository() { return db; } - /** @return get the RevWalk pool all objects are allocated through. */ + /** + * Get RevWalk + * + * @return get the RevWalk pool all objects are allocated through. + */ public RevWalk getRevWalk() { return pool; } /** + * Return Git API wrapper + * * @return an API wrapper for the underlying repository. This wrapper does - * not allocate any new resources and need not be closed (but closing - * it is harmless). */ + * not allocate any new resources and need not be closed (but + * closing it is harmless). + */ public Git git() { return git; } - /** @return current time adjusted by {@link #tick(int)}. */ - public Date getClock() { - return new Date(now); + /** + * Get date + * + * @return current date. + * @since 4.2 + */ + public Date getDate() { + return new Date(mockSystemReader.getCurrentTime()); } - /** @return timezone used for default identities. */ + /** + * Get timezone + * + * @return timezone used for default identities. + */ public TimeZone getTimeZone() { - return defaultCommitter.getTimeZone(); + return mockSystemReader.getTimeZone(); } /** @@ -203,18 +245,18 @@ * number of seconds to add to the current time. */ public void tick(final int secDelta) { - now += secDelta * 1000L; + mockSystemReader.tick(secDelta); } /** - * Set the author and committer using {@link #getClock()}. + * Set the author and committer using {@link #getDate()}. * * @param c * the commit builder to store. */ public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) { - c.setAuthor(new PersonIdent(defaultAuthor, new Date(now))); - c.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + c.setAuthor(new PersonIdent(defaultAuthor, getDate())); + c.setCommitter(new PersonIdent(defaultCommitter, getDate())); } /** @@ -226,7 +268,7 @@ * @throws Exception */ public RevBlob blob(final String content) throws Exception { - return blob(content.getBytes("UTF-8")); + return blob(content.getBytes(UTF_8)); } /** @@ -392,8 +434,8 @@ c = new org.eclipse.jgit.lib.CommitBuilder(); c.setTreeId(tree); c.setParentIds(parents); - c.setAuthor(new PersonIdent(defaultAuthor, new Date(now))); - c.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + c.setAuthor(new PersonIdent(defaultAuthor, getDate())); + c.setCommitter(new PersonIdent(defaultCommitter, getDate())); c.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -403,7 +445,11 @@ return pool.lookupCommit(id); } - /** @return a new commit builder. */ + /** + * Create commit builder + * + * @return a new commit builder. + */ public CommitBuilder commit() { return new CommitBuilder(); } @@ -428,7 +474,7 @@ final TagBuilder t = new TagBuilder(); t.setObjectId(dst); t.setTag(name); - t.setTagger(new PersonIdent(defaultCommitter, new Date(now))); + t.setTagger(new PersonIdent(defaultCommitter, getDate())); t.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -470,7 +516,7 @@ */ public CommitBuilder amendRef(String ref) throws Exception { String name = normalizeRef(ref); - Ref r = db.getRef(name); + Ref r = db.exactRef(name); if (r == null) throw new IOException("Not a ref: " + ref); return amend(pool.parseCommit(r.getObjectId()), branch(name).commit()); @@ -555,6 +601,32 @@ } } + /** + * Delete a reference. + * + * @param ref + * the name of the reference to delete. This is normalized + * in the same way as {@link #update(String, AnyObjectId)}. + * @throws Exception + * @since 4.4 + */ + public void delete(String ref) throws Exception { + ref = normalizeRef(ref); + RefUpdate u = db.updateRef(ref); + u.setForceUpdate(true); + switch (u.delete()) { + case FAST_FORWARD: + case FORCED: + case NEW: + case NO_CHANGE: + updateServerInfo(); + return; + + default: + throw new IOException("Cannot delete " + ref + " " + u.getResult()); + } + } + private static String normalizeRef(String ref) { if (Constants.HEAD.equals(ref)) { // nothing @@ -571,7 +643,7 @@ /** * Soft-reset HEAD to a detached state. - *

        + * * @param id * ID of detached head. * @throws Exception @@ -647,7 +719,7 @@ RevCommit parent = commit.getParent(0); pool.parseHeaders(parent); - Ref headRef = db.getRef(Constants.HEAD); + Ref headRef = db.exactRef(Constants.HEAD); if (headRef == null) throw new IOException("Missing HEAD"); RevCommit head = pool.parseCommit(headRef.getObjectId()); @@ -663,7 +735,7 @@ b.setParentId(head); b.setTreeId(merger.getResultTreeId()); b.setAuthor(commit.getAuthorIdent()); - b.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + b.setCommitter(new PersonIdent(defaultCommitter, getDate())); b.setMessage(commit.getFullMessage()); ObjectId result; try (ObjectInserter ins = inserter) { @@ -711,7 +783,8 @@ * Ensure the body of the given object has been parsed. * * @param - * type of object, e.g. {@link RevTag} or {@link RevCommit}. + * type of object, e.g. {@link org.eclipse.jgit.revwalk.RevTag} + * or {@link org.eclipse.jgit.revwalk.RevCommit}. * @param object * reference to the (possibly unparsed) object to force body * parsing of. @@ -790,7 +863,7 @@ break; final byte[] bin = db.open(o, o.getType()).getCachedBytes(); - oc.checkCommit(bin); + oc.checkCommit(o, bin); assertHash(o, bin); } @@ -800,7 +873,7 @@ break; final byte[] bin = db.open(o, o.getType()).getCachedBytes(); - oc.check(o.getType(), bin); + oc.check(o, o.getType(), bin); assertHash(o, bin); } } @@ -831,23 +904,23 @@ final File pack, idx; try (PackWriter pw = new PackWriter(db)) { - Set all = new HashSet(); + Set all = new HashSet<>(); for (Ref r : db.getAllRefs().values()) all.add(r.getObjectId()); - pw.preparePack(m, all, Collections. emptySet()); + pw.preparePack(m, all, PackWriter.NONE); final ObjectId name = pw.computeName(); pack = nameFor(odb, name, ".pack"); try (OutputStream out = - new SafeBufferedOutputStream(new FileOutputStream(pack))) { + new BufferedOutputStream(new FileOutputStream(pack))) { pw.writePack(m, m, out); } pack.setReadOnly(); idx = nameFor(odb, name, ".idx"); try (OutputStream out = - new SafeBufferedOutputStream(new FileOutputStream(idx))) { + new BufferedOutputStream(new FileOutputStream(idx))) { pw.writeIndex(out); } idx.setReadOnly(); @@ -867,13 +940,13 @@ } private static File nameFor(ObjectDirectory odb, ObjectId name, String t) { - File packdir = new File(odb.getDirectory(), "pack"); + File packdir = odb.getPackDirectory(); return new File(packdir, "pack-" + name.name() + t); } private void writeFile(final File p, final byte[] bin) throws IOException, ObjectWritingException { - final LockFile lck = new LockFile(p, db.getFS()); + final LockFile lck = new LockFile(p); if (!lck.lock()) throw new ObjectWritingException("Can't write " + p); try { @@ -928,6 +1001,15 @@ public RevCommit update(RevCommit to) throws Exception { return TestRepository.this.update(ref, to); } + + /** + * Delete this branch. + * @throws Exception + * @since 4.4 + */ + public void delete() throws Exception { + TestRepository.this.delete(ref); + } } /** Helper to generate a commit. */ @@ -938,7 +1020,7 @@ private ObjectId topLevelTree; - private final List parents = new ArrayList(2); + private final List parents = new ArrayList<>(2); private int tick = 1; @@ -960,7 +1042,7 @@ CommitBuilder(BranchBuilder b) throws Exception { branch = b; - Ref ref = db.getRef(branch.ref); + Ref ref = db.exactRef(branch.ref); if (ref != null && ref.getObjectId() != null) parent(pool.parseCommit(ref.getObjectId())); } @@ -1100,7 +1182,7 @@ c.setAuthor(author); if (committer != null) { if (updateCommitterTime) - committer = new PersonIdent(committer, new Date(now)); + committer = new PersonIdent(committer, getDate()); c.setCommitter(committer); } @@ -1123,8 +1205,7 @@ return self; } - private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) - throws IOException { + private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) { if (changeId == null) return; int idx = ChangeIdUtil.indexOfChangeId(message, "\n"); diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRng.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRng.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRng.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRng.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,7 +43,9 @@ package org.eclipse.jgit.junit; -/** Toy RNG to ensure we get predictable numbers during unit tests. */ +/** + * Toy RNG to ensure we get predictable numbers during unit tests. + */ public class TestRng { private int next; @@ -74,6 +76,8 @@ } /** + * Next int + * * @return the next random integer. */ public int nextInt() { diff -Nru jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/MonotonicFakeClock.java jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/MonotonicFakeClock.java --- jgit-4.1.2/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/MonotonicFakeClock.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/MonotonicFakeClock.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016, 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.junit.time; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.util.time.MonotonicClock; +import org.eclipse.jgit.util.time.ProposedTimestamp; + +/** + * Fake {@link org.eclipse.jgit.util.time.MonotonicClock} for testing code that + * uses Clock. + * + * @since 4.6 + */ +public class MonotonicFakeClock implements MonotonicClock { + private long now = TimeUnit.SECONDS.toMicros(42); + + /** + * Advance the time returned by future calls to {@link #propose()}. + * + * @param add + * amount of time to add; must be {@code > 0}. + * @param unit + * unit of {@code add}. + */ + public void tick(long add, TimeUnit unit) { + if (add <= 0) { + throw new IllegalArgumentException(); + } + now += unit.toMillis(add); + } + + /** {@inheritDoc} */ + @Override + public ProposedTimestamp propose() { + long t = now++; + return new ProposedTimestamp() { + @Override + public long read(TimeUnit unit) { + return unit.convert(t, TimeUnit.MILLISECONDS); + } + + @Override + public void blockUntil(Duration maxWait) { + // Nothing to do, since fake time does not go backwards. + } + }; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/BUILD jgit-4.11.9/org.eclipse.jgit.junit.http/BUILD --- jgit-4.1.2/org.eclipse.jgit.junit.http/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,22 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "junit-http", + testonly = 1, + srcs = glob(["src/**/*.java"]), + resources = glob(["resources/**"]), + # TODO(davido): we want here provided deps + deps = [ + "//lib:jetty-http", + "//lib:jetty-security", + "//lib:jetty-server", + "//lib:jetty-servlet", + "//lib:jetty-util", + "//lib:junit", + "//lib:servlet-api", + "//lib:slf4j-api", + "//org.eclipse.jgit.http.server:jgit-servlet", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit:junit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/.classpath jgit-4.11.9/org.eclipse.jgit.junit.http/.classpath --- jgit-4.1.2/org.eclipse.jgit.junit.http/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -1,41 +1,44 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.junit.http Bundle-SymbolicName: org.eclipse.jgit.junit.http -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: javax.servlet;version="[2.5.0,3.2.0)", javax.servlet.http;version="[2.5.0,3.2.0)", org.apache.commons.logging;version="[1.1.1,2.0.0)", - org.eclipse.jetty.http;version="[9.0.0,10.0.0)", - org.eclipse.jetty.security;version="[9.0.0,10.0.0)", - org.eclipse.jetty.security.authentication;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server.handler;version="[9.0.0,10.0.0)", - org.eclipse.jetty.server.nio;version="[9.0.0,10.0.0)", - org.eclipse.jetty.servlet;version="[9.0.0,10.0.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.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.1.2"; + org.eclipse.jetty.http;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)", + org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.ssl;version="[9.4.5,10.0.0)", + org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.http.server;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.resolver;version="[4.11.9,4.12.0)", + org.junit;version="[4.12,5.0.0)" +Export-Package: org.eclipse.jgit.junit.http;version="4.11.9"; 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, + org.eclipse.jetty.security, javax.servlet, org.eclipse.jetty.server, org.eclipse.jetty.util.log, diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/pom.xml jgit-4.11.9/org.eclipse.jgit.junit.http/pom.xml --- jgit-4.1.2/org.eclipse.jgit.junit.http/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.junit.http diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,9 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; -/** A single request made through {@link AppServer}. */ +/** + * A single request made through {@link org.eclipse.jgit.junit.http.AppServer}. + */ public class AccessEvent { private final String method; @@ -76,7 +78,7 @@ } private static Map cloneHeaders(final Request req) { - Map r = new TreeMap(); + Map r = new TreeMap<>(); Enumeration hn = req.getHeaderNames(); while (hn.hasMoreElements()) { String key = (String) hn.nextElement(); @@ -88,7 +90,7 @@ } private static Map cloneHeaders(final Response rsp) { - Map r = new TreeMap(); + Map r = new TreeMap<>(); Enumeration hn = rsp.getHttpFields().getFieldNames(); while (hn.hasMoreElements()) { String key = hn.nextElement(); @@ -102,20 +104,30 @@ @SuppressWarnings("unchecked") private static Map clone(Map parameterMap) { - return new TreeMap(parameterMap); + return new TreeMap<>(parameterMap); } - /** @return {@code "GET"} or {@code "POST"} */ + /** + * Get the method. + * + * @return {@code "GET"} or {@code "POST"} + */ public String getMethod() { return method; } - /** @return path of the file on the server, e.g. {@code /git/HEAD}. */ + /** + * Get path. + * + * @return path of the file on the server, e.g. {@code /git/HEAD}. + */ public String getPath() { return uri; } /** + * Get request header + * * @param name * name of the request header to read. * @return first value of the request header; null if not sent. @@ -125,6 +137,8 @@ } /** + * Get parameter + * * @param name * name of the request parameter to read. * @return first value of the request parameter; null if not sent. @@ -134,17 +148,27 @@ return r != null && 1 <= r.length ? r[0] : null; } - /** @return all parameters in the request. */ + /** + * Get parameters + * + * @return all parameters in the request. + */ public Map getParameters() { return parameters; } - /** @return HTTP status code of the response, e.g. 200, 403, 500. */ + /** + * Get the status. + * + * @return HTTP status code of the response, e.g. 200, 403, 500. + */ public int getStatus() { return status; } /** + * Get response header. + * * @param name * name of the response header to read. * @return first value of the response header; null if not sent. @@ -153,6 +177,8 @@ return responseHeaders.get(name); } + /** {@inheritDoc} */ + @Override public String toString() { StringBuilder b = new StringBuilder(); b.append(method); diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, 2012 Google Inc. + * Copyright (C) 2010, 2017 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -46,29 +46,36 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.security.AbstractLoginService; import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; -import org.eclipse.jetty.security.MappedLoginService; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Password; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jgit.transport.URIish; /** @@ -88,6 +95,12 @@ /** Password for {@link #username} in secured access areas. */ public static final String password = "letmein"; + /** SSL keystore password; must have at least 6 characters. */ + private static final String keyPassword = "mykeys"; + + /** Role for authentication. */ + private static final String authRole = "can-access"; + static { // Install a logger that throws warning messages. // @@ -97,39 +110,148 @@ private final Server server; + private final HttpConfiguration config; + private final ServerConnector connector; + private final HttpConfiguration secureConfig; + + private final ServerConnector secureConnector; + private final ContextHandlerCollection contexts; private final TestRequestLog log; + private List filesToDelete = new ArrayList<>(); + + /** + * Constructor for AppServer. + */ public AppServer() { + this(0, -1); + } + + /** + * Constructor for AppServer. + * + * @param port + * the http port number; may be zero to allocate a port + * dynamically + * @since 4.2 + */ + public AppServer(int port) { + this(port, -1); + } + + /** + * Constructor for AppServer. + * + * @param port + * for http, may be zero to allocate a port dynamically + * @param sslPort + * for https,may be zero to allocate a port dynamically. If + * negative, the server will be set up without https support. + * @since 4.9 + */ + public AppServer(int port, int sslPort) { server = new Server(); - HttpConfiguration http_config = new HttpConfiguration(); - http_config.setSecureScheme("https"); - http_config.setSecurePort(8443); - http_config.setOutputBufferSize(32768); + config = new HttpConfiguration(); + config.setSecureScheme("https"); + config.setSecurePort(0); + config.setOutputBufferSize(32768); connector = new ServerConnector(server, - new HttpConnectionFactory(http_config)); - connector.setPort(0); + new HttpConnectionFactory(config)); + connector.setPort(port); + String ip; + String hostName; try { final InetAddress me = InetAddress.getByName("localhost"); - connector.setHost(me.getHostAddress()); + ip = me.getHostAddress(); + connector.setHost(ip); + hostName = InetAddress.getLocalHost().getCanonicalHostName(); } catch (UnknownHostException e) { throw new RuntimeException("Cannot find localhost", e); } + if (sslPort >= 0) { + SslContextFactory sslContextFactory = createTestSslContextFactory( + hostName); + secureConfig = new HttpConfiguration(config); + secureConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, + HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(secureConfig)); + secureConnector.setPort(sslPort); + secureConnector.setHost(ip); + } else { + secureConfig = null; + secureConnector = null; + } + contexts = new ContextHandlerCollection(); log = new TestRequestLog(); log.setHandler(contexts); - server.setConnectors(new Connector[] { connector }); + if (secureConnector == null) { + server.setConnectors(new Connector[] { connector }); + } else { + server.setConnectors( + new Connector[] { connector, secureConnector }); + } server.setHandler(log); } + private SslContextFactory createTestSslContextFactory(String hostName) { + SslContextFactory factory = new SslContextFactory(true); + + String dName = "CN=,OU=,O=,ST=,L=,C="; + + try { + File tmpDir = Files.createTempDirectory("jks").toFile(); + tmpDir.deleteOnExit(); + makePrivate(tmpDir); + File keyStore = new File(tmpDir, "keystore.jks"); + Runtime.getRuntime().exec( + new String[] { + "keytool", // + "-keystore", keyStore.getAbsolutePath(), // + "-storepass", keyPassword, + "-alias", hostName, // + "-genkeypair", // + "-keyalg", "RSA", // + "-keypass", keyPassword, // + "-dname", dName, // + "-validity", "2" // + }).waitFor(); + keyStore.deleteOnExit(); + makePrivate(keyStore); + filesToDelete.add(keyStore); + filesToDelete.add(tmpDir); + factory.setKeyStorePath(keyStore.getAbsolutePath()); + factory.setKeyStorePassword(keyPassword); + factory.setKeyManagerPassword(keyPassword); + factory.setTrustStorePath(keyStore.getAbsolutePath()); + factory.setTrustStorePassword(keyPassword); + } catch (InterruptedException | IOException e) { + throw new RuntimeException("Cannot create ssl key/certificate", e); + } + return factory; + } + + private void makePrivate(File file) { + file.setReadable(false); + file.setWritable(false); + file.setExecutable(false); + file.setReadable(true, true); + file.setWritable(true, true); + if (file.isDirectory()) { + file.setExecutable(true, true); + } + } + /** * Create a new servlet context within the server. *

        @@ -153,39 +275,81 @@ return ctx; } - public ServletContextHandler authBasic(ServletContextHandler ctx) { + /** + * Configure basic authentication. + * + * @param ctx + * @param methods + * @return servlet context handler + */ + public ServletContextHandler authBasic(ServletContextHandler ctx, + String... methods) { assertNotYetSetUp(); - auth(ctx, new BasicAuthenticator()); + auth(ctx, new BasicAuthenticator(), methods); return ctx; } - private void auth(ServletContextHandler ctx, Authenticator authType) { - final String role = "can-access"; + static class TestMappedLoginService extends AbstractLoginService { + private String role; + + protected final ConcurrentMap users = new ConcurrentHashMap<>(); + + TestMappedLoginService(String role) { + this.role = role; + } + + @Override + protected void doStart() throws Exception { + UserPrincipal p = new UserPrincipal(username, + new Password(password)); + users.put(username, p); + super.doStart(); + } - MappedLoginService users = new MappedLoginService() { - @Override - protected UserIdentity loadUser(String who) { + @Override + protected String[] loadRoleInfo(UserPrincipal user) { + if (users.get(user.getName()) == null) return null; - } + else + return new String[] { role }; + } - @Override - protected void loadUsers() throws IOException { - putUser(username, new Password(password), new String[] { role }); - } - }; + @Override + protected UserPrincipal loadUserInfo(String user) { + return users.get(user); + } + } + private ConstraintMapping createConstraintMapping() { ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint(new Constraint()); cm.getConstraint().setAuthenticate(true); cm.getConstraint().setDataConstraint(Constraint.DC_NONE); - cm.getConstraint().setRoles(new String[] { role }); + cm.getConstraint().setRoles(new String[] { authRole }); cm.setPathSpec("/*"); + return cm; + } + + private void auth(ServletContextHandler ctx, Authenticator authType, + String... methods) { + AbstractLoginService users = new TestMappedLoginService(authRole); + List mappings = new ArrayList<>(); + if (methods == null || methods.length == 0) { + mappings.add(createConstraintMapping()); + } else { + for (String method : methods) { + ConstraintMapping cm = createConstraintMapping(); + cm.setMethod(method.toUpperCase(Locale.ROOT)); + mappings.add(cm); + } + } ConstraintSecurityHandler sec = new ConstraintSecurityHandler(); sec.setRealmName(realm); sec.setAuthenticator(authType); sec.setLoginService(users); - sec.setConstraintMappings(new ConstraintMapping[] { cm }); + sec.setConstraintMappings( + mappings.toArray(new ConstraintMapping[mappings.size()])); sec.setHandler(ctx); contexts.removeHandler(ctx); @@ -202,6 +366,10 @@ RecordingLogger.clear(); log.clear(); server.start(); + config.setSecurePort(getSecurePort()); + if (secureConfig != null) { + secureConfig.setSecurePort(getSecurePort()); + } } /** @@ -214,6 +382,10 @@ RecordingLogger.clear(); log.clear(); server.stop(); + for (File f : filesToDelete) { + f.delete(); + } + filesToDelete.clear(); } /** @@ -237,18 +409,38 @@ } } - /** @return the local port number the server is listening on. */ + /** + * Get port. + * + * @return the local port number the server is listening on. + */ public int getPort() { assertAlreadySetUp(); return connector.getLocalPort(); } - /** @return all requests since the server was started. */ + /** + * Get secure port. + * + * @return the HTTPS port or -1 if not configured. + */ + public int getSecurePort() { + assertAlreadySetUp(); + return secureConnector != null ? secureConnector.getLocalPort() : -1; + } + + /** + * Get requests. + * + * @return all requests since the server was started. + */ public List getRequests() { - return new ArrayList(log.getEvents()); + return new ArrayList<>(log.getEvents()); } /** + * Get requests. + * * @param base * base URI used to access the server. * @param path @@ -260,12 +452,14 @@ } /** + * Get requests. + * * @param path * the path to locate requests for. * @return all requests which match the given path. */ public List getRequests(String path) { - ArrayList r = new ArrayList(); + ArrayList r = new ArrayList<>(); for (AccessEvent event : log.getEvents()) { if (event.getPath().equals(path)) { r.add(event); diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2009-2017, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -67,33 +67,75 @@ import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; -/** Base class for HTTP related transport testing. */ +/** + * Base class for HTTP related transport testing. + */ public abstract class HttpTestCase extends LocalDiskRepositoryTestCase { + /** Constant master="Constants.R_HEADS + Constants.MASTER" */ protected static final String master = Constants.R_HEADS + Constants.MASTER; /** In-memory application server; subclass must start. */ protected AppServer server; + /** {@inheritDoc} */ + @Override public void setUp() throws Exception { super.setUp(); - server = new AppServer(); + server = createServer(); } + /** {@inheritDoc} */ + @Override public void tearDown() throws Exception { server.tearDown(); super.tearDown(); } + /** + * Create the {@link AppServer}.This default implementation creates a server + * without SSLsupport listening for HTTP connections on a dynamically chosen + * port, which can be gotten once the server has been started via its + * {@link org.eclipse.jgit.junit.http.AppServer#getPort()} method. + * Subclasses may override if they need a more specialized server. + * + * @return the {@link org.eclipse.jgit.junit.http.AppServer}. + * @since 4.9 + */ + protected AppServer createServer() { + return new AppServer(); + } + + /** + * Create TestRepository + * + * @return the TestRepository + * @throws IOException + */ protected TestRepository createTestRepository() throws IOException { - return new TestRepository(createBareRepository()); + return new TestRepository<>(createBareRepository()); } + /** + * Convert path to URIish + * + * @param path + * @return the URIish + * @throws URISyntaxException + */ protected URIish toURIish(String path) throws URISyntaxException { URI u = server.getURI().resolve(path); return new URIish(u.toString()); } + /** + * Convert a path relative to the app's context path to a URIish + * + * @param app + * @param name + * @return the warnings (if any) from the last execution + * @throws URISyntaxException + */ protected URIish toURIish(ServletContextHandler app, String name) throws URISyntaxException { String p = app.getContextPath(); @@ -103,25 +145,60 @@ return toURIish(p); } + /** + * Get requests. + * + * @return list of events + */ protected List getRequests() { return server.getRequests(); } + /** + * Get requests. + * + * @param base + * @param path + * + * @return list of events + */ protected List getRequests(URIish base, String path) { return server.getRequests(base, path); } + /** + * Get requests. + * + * @param path + * + * @return list of events + */ protected List getRequests(String path) { return server.getRequests(path); } + /** + * Run fsck + * + * @param db + * @param tips + * @throws Exception + */ protected static void fsck(Repository db, RevObject... tips) throws Exception { - new TestRepository(db).fsck(tips); + TestRepository tr = + new TestRepository<>(db); + tr.fsck(tips); } + /** + * Mirror refs + * + * @param refs + * @return set of RefSpecs + */ protected static Set mirror(String... refs) { - HashSet r = new HashSet(); + HashSet r = new HashSet<>(); for (String name : refs) { RefSpec rs = new RefSpec(name); rs = rs.setDestination(name); @@ -131,6 +208,14 @@ return r; } + /** + * Push a commit + * + * @param from + * @param q + * @return collection of RefUpdates + * @throws IOException + */ protected static Collection push(TestRepository from, RevCommit q) throws IOException { final Repository db = from.getRepository(); @@ -145,6 +230,13 @@ return Collections.singleton(u); } + /** + * Create loose object path + * + * @param base + * @param id + * @return path of the loose object + */ public static String loose(URIish base, AnyObjectId id) { final String objectName = id.name(); final String d = objectName.substring(0, 2); @@ -152,6 +244,14 @@ return join(base, "objects/" + d + "/" + f); } + /** + * Join a base URIish and a path + * + * @param base + * @param path + * a relative path + * @return the joined path + */ public static String join(URIish base, String path) { if (path.startsWith("/")) fail("Cannot join absolute path " + path + " to URIish " + base); @@ -161,4 +261,53 @@ dir += "/"; return dir + path; } + + /** + * Rewrite a url + * + * @param url + * @param newProtocol + * @param newPort + * @return the rewritten url + */ + protected static String rewriteUrl(String url, String newProtocol, + int newPort) { + String newUrl = url; + if (newProtocol != null && !newProtocol.isEmpty()) { + int schemeEnd = newUrl.indexOf("://"); + if (schemeEnd >= 0) { + newUrl = newProtocol + newUrl.substring(schemeEnd); + } + } + if (newPort > 0) { + newUrl = newUrl.replaceFirst(":\\d+/", ":" + newPort + "/"); + } else { + // Remove the port, if any + newUrl = newUrl.replaceFirst(":\\d+/", "/"); + } + return newUrl; + } + + /** + * Extend a path + * + * @param uri + * @param pathComponents + * @return the extended URIish + * @throws URISyntaxException + */ + protected static URIish extendPath(URIish uri, String pathComponents) + throws URISyntaxException { + String raw = uri.toString(); + String newComponents = pathComponents; + if (!newComponents.startsWith("/")) { + newComponents = '/' + newComponents; + } + if (!newComponents.endsWith("/")) { + newComponents += '/'; + } + int i = raw.lastIndexOf('/'); + raw = raw.substring(0, i) + newComponents + raw.substring(i + 1); + return new URIish(raw); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,35 +51,54 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletContext; +/** + * Mock ServletConfig + */ public class MockServletConfig implements ServletConfig { - private final Map parameters = new HashMap(); + private final Map parameters = new HashMap<>(); + /** + * Set init parameter. + * + * @param name + * @param value + */ public void setInitParameter(String name, String value) { parameters.put(name, value); } + /** {@inheritDoc} */ + @Override public String getInitParameter(String name) { return parameters.get(name); } - public Enumeration getInitParameterNames() { + /** {@inheritDoc} */ + @Override + public Enumeration getInitParameterNames() { final Iterator i = parameters.keySet().iterator(); return new Enumeration() { + @Override public boolean hasMoreElements() { return i.hasNext(); } + @Override public String nextElement() { return i.next(); } }; } + /** {@inheritDoc} */ + @Override public String getServletName() { return "MOCK_SERVLET"; } + /** {@inheritDoc} */ + @Override public ServletContext getServletContext() { return null; } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,21 +50,30 @@ import org.eclipse.jetty.util.log.Logger; -/** Logs warnings into an array for later inspection. */ +/** + * Log warnings into an array for later inspection. + */ public class RecordingLogger implements Logger { - private static List warnings = new ArrayList(); + private static List warnings = new ArrayList<>(); - /** Clear the warnings, automatically done by {@link AppServer#setUp()} */ + /** + * Clear the warnings, automatically done by + * {@link org.eclipse.jgit.junit.http.AppServer#setUp()} + */ public static void clear() { synchronized (warnings) { warnings.clear(); } } - /** @return the warnings (if any) from the last execution */ + /** + * Get the warnings. + * + * @return the warnings (if any) from the last execution + */ public static List getWarnings() { synchronized (warnings) { - ArrayList copy = new ArrayList(warnings); + ArrayList copy = new ArrayList<>(warnings); return Collections.unmodifiableList(copy); } } @@ -86,108 +95,185 @@ private final String name; + /** + * Constructor for RecordingLogger. + */ public RecordingLogger() { this(""); } + /** + * Constructor for RecordingLogger. + * + * @param name + */ public RecordingLogger(final String name) { this.name = name; } + /** {@inheritDoc} */ + @Override public Logger getLogger(@SuppressWarnings("hiding") String name) { return new RecordingLogger(name); } + /** {@inheritDoc} */ + @Override public String getName() { return name; } + /** + * Warning + * + * @param msg + * @param arg0 + * @param arg1 + */ public void warn(String msg, Object arg0, Object arg1) { synchronized (warnings) { warnings.add(new Warning(MessageFormat.format(msg, arg0, arg1))); } } + /** {@inheritDoc} */ + @Override public void warn(String msg, Throwable th) { synchronized (warnings) { warnings.add(new Warning(msg, th)); } } + /** + * Warning + * + * @param msg + * warning message + */ public void warn(String msg) { synchronized (warnings) { warnings.add(new Warning(msg)); } } - public void debug(@SuppressWarnings("unused") String msg, - @SuppressWarnings("unused") Object arg0, - @SuppressWarnings("unused") Object arg1) { + /** + * Debug log + * + * @param msg + * @param arg0 + * @param arg1 + */ + public void debug(String msg, Object arg0, Object arg1) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void debug(String msg, Throwable th) { // Ignore (not relevant to test failures) } - public void debug(@SuppressWarnings("unused") String msg) { + /** + * Debug log + * + * @param msg + * debug message + */ + public void debug(String msg) { // Ignore (not relevant to test failures) } - public void info(@SuppressWarnings("unused") String msg, - @SuppressWarnings("unused") Object arg0, - @SuppressWarnings("unused") Object arg1) { + /** + * Info + * + * @param msg + * @param arg0 + * @param arg1 + */ + public void info(String msg, Object arg0, Object arg1) { // Ignore (not relevant to test failures) } - public void info(@SuppressWarnings("unused") String msg) { + /** + * Info + * + * @param msg + */ + public void info(String msg) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public boolean isDebugEnabled() { return false; } + /** {@inheritDoc} */ + @Override public void setDebugEnabled(boolean enabled) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void warn(String msg, Object... args) { synchronized (warnings) { + int i = 0; + int index = msg.indexOf("{}"); + while (index >= 0) { + msg = msg.replaceFirst("\\{\\}", "{" + i++ + "}"); + index = msg.indexOf("{}"); + } warnings.add(new Warning(MessageFormat.format(msg, args))); } } + /** {@inheritDoc} */ + @Override public void warn(Throwable thrown) { synchronized (warnings) { warnings.add(new Warning(thrown)); } } + /** {@inheritDoc} */ + @Override public void info(String msg, Object... args) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void info(Throwable thrown) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void info(String msg, Throwable thrown) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void debug(String msg, Object... args) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void debug(Throwable thrown) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ + @Override public void ignore(Throwable arg0) { // Ignore (not relevant to test failures) } + /** {@inheritDoc} */ @Override public void debug(String msg, long value) { // Ignore (not relevant to test failures) diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,29 +69,75 @@ private URIish uri; + private URIish secureUri; + + /** + * Constructor for SimpleHttpServer. + * + * @param repository + */ public SimpleHttpServer(Repository repository) { + this(repository, false); + } + + /** + * Constructor for SimpleHttpServer. + * + * @param repository + * @param withSsl + */ + public SimpleHttpServer(Repository repository, boolean withSsl) { this.db = repository; - server = new AppServer(); + server = new AppServer(0, withSsl ? 0 : -1); } + /** + * Start the server + * + * @throws Exception + */ public void start() throws Exception { ServletContextHandler sBasic = server.authBasic(smart("/sbasic")); server.setUp(); final String srcName = db.getDirectory().getName(); uri = toURIish(sBasic, srcName); + int sslPort = server.getSecurePort(); + if (sslPort > 0) { + secureUri = uri.setPort(sslPort).setScheme("https"); + } } + /** + * Stop the server. + * + * @throws Exception + */ public void stop() throws Exception { server.tearDown(); } + /** + * Get the uri. + * + * @return the uri + */ public URIish getUri() { return uri; } + /** + * Get the secureUri. + * + * @return the secure uri + */ + public URIish getSecureUri() { + return secureUri; + } + private ServletContextHandler smart(final String path) { GitServlet gs = new GitServlet(); gs.setRepositoryResolver(new RepositoryResolver() { + @Override public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { diff -Nru jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java --- jgit-4.1.2/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,78 +59,80 @@ /** Logs request made through {@link AppServer}. */ class TestRequestLog extends HandlerWrapper { - private static final int MAX = 16; + private static final int MAX = 16; - private final List events = new ArrayList(); + private final List events = new ArrayList<>(); - private final Semaphore active = new Semaphore(MAX); + private final Semaphore active = new Semaphore(MAX, true); - /** Reset the log back to its original empty state. */ - void clear() { - try { - for (;;) { - try { - active.acquire(MAX); - break; - } catch (InterruptedException e) { - continue; - } - } - - synchronized (events) { - events.clear(); - } - } finally { - active.release(MAX); - } - } - - /** @return all of the events made since the last clear. */ - List getEvents() { - try { - for (;;) { - try { - active.acquire(MAX); - break; - } catch (InterruptedException e) { - continue; - } - } - - synchronized (events) { - return events; - } - } finally { - active.release(MAX); - } - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { - try { - for (;;) { - try { - active.acquire(); - break; - } catch (InterruptedException e) { - continue; - } - } - - super.handle(target, baseRequest, request, response); - - if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) - log((Request) request, (Response) response); - - } finally { - active.release(); - } - } - - private void log(Request request, Response response) { - synchronized (events) { - events.add(new AccessEvent(request, response)); - } - } + /** Reset the log back to its original empty state. */ + void clear() { + try { + for (;;) { + try { + active.acquire(MAX); + break; + } catch (InterruptedException e) { + continue; + } + } + + synchronized (events) { + events.clear(); + } + } finally { + active.release(MAX); + } + } + + /** @return all of the events made since the last clear. */ + List getEvents() { + try { + for (;;) { + try { + active.acquire(MAX); + break; + } catch (InterruptedException e) { + continue; + } + } + + synchronized (events) { + return events; + } + } finally { + active.release(MAX); + } + } + + /** {@inheritDoc} */ + @Override + public void handle(String target, Request baseRequest, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + try { + for (;;) { + try { + active.acquire(); + break; + } catch (InterruptedException e) { + continue; + } + } + + super.handle(target, baseRequest, request, response); + + if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) + log((Request) request, (Response) response); + + } finally { + active.release(); + } + } + + private void log(Request request, Response response) { + synchronized (events) { + events.add(new AccessEvent(request, response)); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/about.html jgit-4.11.9/org.eclipse.jgit.lfs/about.html --- jgit-4.1.2/org.eclipse.jgit.lfs/about.html 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/about.html 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,59 @@ + + + + + + +Eclipse Distribution License - Version 1.0 + + + + + + +

        Eclipse Distribution License - v 1.0

        + +

        Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.

        + +

        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.

        + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/BUILD jgit-4.11.9/org.eclipse.jgit.lfs/BUILD --- jgit-4.1.2/org.eclipse.jgit.lfs/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,12 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "jgit-lfs", + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.lfs/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:gson", + "//org.eclipse.jgit:jgit", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/build.properties jgit-4.11.9/org.eclipse.jgit.lfs/build.properties --- jgit-4.1.2/org.eclipse.jgit.lfs/build.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,7 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + about.html diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.classpath jgit-4.11.9/org.eclipse.jgit.lfs/.classpath --- jgit-4.1.2/org.eclipse.jgit.lfs/.classpath 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.fbprefs jgit-4.11.9/org.eclipse.jgit.lfs/.fbprefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.fbprefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.fbprefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,125 @@ +#FindBugs User Preferences +#Mon May 04 16:24:13 PDT 2009 +detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true +detectorBadAppletConstructor=BadAppletConstructor|false +detectorBadResultSetAccess=BadResultSetAccess|true +detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true +detectorBadUseOfReturnValue=BadUseOfReturnValue|true +detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true +detectorBooleanReturnNull=BooleanReturnNull|true +detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true +detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true +detectorCheckTypeQualifiers=CheckTypeQualifiers|true +detectorCloneIdiom=CloneIdiom|false +detectorComparatorIdiom=ComparatorIdiom|true +detectorConfusedInheritance=ConfusedInheritance|true +detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true +detectorCrossSiteScripting=CrossSiteScripting|true +detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true +detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true +detectorDontUseEnum=DontUseEnum|true +detectorDroppedException=DroppedException|true +detectorDumbMethodInvocations=DumbMethodInvocations|true +detectorDumbMethods=DumbMethods|true +detectorDuplicateBranches=DuplicateBranches|true +detectorEmptyZipFileEntry=EmptyZipFileEntry|true +detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true +detectorFinalizerNullsFields=FinalizerNullsFields|true +detectorFindBadCast2=FindBadCast2|true +detectorFindBadForLoop=FindBadForLoop|true +detectorFindCircularDependencies=FindCircularDependencies|false +detectorFindDeadLocalStores=FindDeadLocalStores|true +detectorFindDoubleCheck=FindDoubleCheck|true +detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true +detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true +detectorFindFinalizeInvocations=FindFinalizeInvocations|true +detectorFindFloatEquality=FindFloatEquality|true +detectorFindHEmismatch=FindHEmismatch|true +detectorFindInconsistentSync2=FindInconsistentSync2|true +detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true +detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true +detectorFindMaskedFields=FindMaskedFields|true +detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true +detectorFindNakedNotify=FindNakedNotify|true +detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true +detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true +detectorFindNonShortCircuit=FindNonShortCircuit|true +detectorFindNullDeref=FindNullDeref|true +detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true +detectorFindOpenStream=FindOpenStream|true +detectorFindPuzzlers=FindPuzzlers|true +detectorFindRefComparison=FindRefComparison|true +detectorFindReturnRef=FindReturnRef|true +detectorFindRunInvocations=FindRunInvocations|true +detectorFindSelfComparison=FindSelfComparison|true +detectorFindSelfComparison2=FindSelfComparison2|true +detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true +detectorFindSpinLoop=FindSpinLoop|true +detectorFindSqlInjection=FindSqlInjection|true +detectorFindTwoLockWait=FindTwoLockWait|true +detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true +detectorFindUnconditionalWait=FindUnconditionalWait|true +detectorFindUninitializedGet=FindUninitializedGet|true +detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true +detectorFindUnreleasedLock=FindUnreleasedLock|true +detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true +detectorFindUnsyncGet=FindUnsyncGet|true +detectorFindUselessControlFlow=FindUselessControlFlow|true +detectorFormatStringChecker=FormatStringChecker|true +detectorHugeSharedStringConstants=HugeSharedStringConstants|true +detectorIDivResultCastToDouble=IDivResultCastToDouble|true +detectorIncompatMask=IncompatMask|true +detectorInconsistentAnnotations=InconsistentAnnotations|true +detectorInefficientMemberAccess=InefficientMemberAccess|false +detectorInefficientToArray=InefficientToArray|true +detectorInfiniteLoop=InfiniteLoop|true +detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true +detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false +detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true +detectorInitializationChain=InitializationChain|true +detectorInstantiateStaticClass=InstantiateStaticClass|true +detectorInvalidJUnitTest=InvalidJUnitTest|true +detectorIteratorIdioms=IteratorIdioms|true +detectorLazyInit=LazyInit|true +detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true +detectorMethodReturnCheck=MethodReturnCheck|true +detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true +detectorMutableLock=MutableLock|true +detectorMutableStaticFields=MutableStaticFields|true +detectorNaming=Naming|true +detectorNumberConstructor=NumberConstructor|true +detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true +detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true +detectorPublicSemaphores=PublicSemaphores|false +detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true +detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true +detectorRedundantInterfaces=RedundantInterfaces|true +detectorRepeatedConditionals=RepeatedConditionals|true +detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true +detectorSerializableIdiom=SerializableIdiom|true +detectorStartInConstructor=StartInConstructor|true +detectorStaticCalendarDetector=StaticCalendarDetector|true +detectorStringConcatenation=StringConcatenation|true +detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true +detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true +detectorSwitchFallthrough=SwitchFallthrough|true +detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true +detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true +detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true +detectorURLProblems=URLProblems|true +detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true +detectorUnnecessaryMath=UnnecessaryMath|true +detectorUnreadFields=UnreadFields|true +detectorUseObjectEquals=UseObjectEquals|false +detectorUselessSubclassMethod=UselessSubclassMethod|false +detectorVarArgsProblems=VarArgsProblems|true +detectorVolatileUsage=VolatileUsage|true +detectorWaitInLoop=WaitInLoop|true +detectorWrongMapIterator=WrongMapIterator|true +detectorXMLFactoryBypass=XMLFactoryBypass|true +detector_threshold=2 +effort=default +excludefilter0=findBugs/FindBugsExcludeFilter.xml +filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false +filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| +run_at_full_build=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.gitignore jgit-4.11.9/org.eclipse.jgit.lfs/.gitignore --- jgit-4.1.2/org.eclipse.jgit.lfs/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +/bin +/target diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,35 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.lfs +Bundle-SymbolicName: org.eclipse.jgit.lfs +Bundle-Version: 4.11.9.201909030838-r +Bundle-Localization: plugin +Bundle-Vendor: %provider_name +Export-Package: org.eclipse.jgit.lfs;version="4.11.9", + org.eclipse.jgit.lfs.errors;version="4.11.9", + org.eclipse.jgit.lfs.internal;version="4.11.9";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server", + org.eclipse.jgit.lfs.lib;version="4.11.9" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: com.google.gson;version="[2.8.2,3.0.0)", + com.google.gson.stream;version="[2.8.2,3.0.0)", + org.apache.http.impl.client;version="[4.2.6,5.0.0)", + org.apache.http.impl.conn;version="[4.2.6,5.0.0)", + org.eclipse.jgit.annotations;version="[4.11.9,4.12.0)";resolution:=optional, + org.eclipse.jgit.api.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.attributes;version="[4.11.9,4.12.0)", + org.eclipse.jgit.diff;version="[4.11.9,4.12.0)", + org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.hooks;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.storage.pack;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk.filter;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util.io;version="[4.11.9,4.12.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/plugin.properties jgit-4.11.9/org.eclipse.jgit.lfs/plugin.properties --- jgit-4.1.2/org.eclipse.jgit.lfs/plugin.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/plugin.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +plugin_name=JGit Large File Storage +provider_name=Eclipse JGit diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/pom.xml jgit-4.11.9/org.eclipse.jgit.lfs/pom.xml --- jgit-4.1.2/org.eclipse.jgit.lfs/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,125 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 4.11.9.201909030838-r + + + org.eclipse.jgit.lfs + JGit - Large File Storage + + + JGit Large File Storage (LFS) implementation. + + + + + + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + + + com.google.code.gson + gson + + + + src/ + + + + . + + plugin.properties + about.html + + + + resources/ + + + + + + org.apache.maven.plugins + maven-source-plugin + true + + + attach-sources + process-classes + + jar + + + + ${source-bundle-manifest} + + + + + + + + maven-jar-plugin + + + ${bundle-manifest} + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.project jgit-4.11.9/org.eclipse.jgit.lfs/.project --- jgit-4.1.2/org.eclipse.jgit.lfs/.project 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.project 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,34 @@ + + + org.eclipse.jgit.lfs + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.eclipse.pde.api.tools.apiAnalysisNature + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties jgit-4.11.9/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties --- jgit-4.1.2/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,19 @@ +corruptLongObject=The content hash ''{0}'' of the long object ''{1}'' doesn''t match its id, the corrupt object will be deleted. +incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH. +inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}. +inconsistentContentLength=Unexpected content length reported by LFS server ({0}), expected {1} but reported was {2} +invalidLongId=Invalid id: {0} +invalidLongIdLength=Invalid id length {0}; should be {1} +lfsUnavailable=LFS is not available for repository {0} +protocolError=LFS Protocol Error {0}: {1} +requiredHashFunctionNotAvailable=Required hash function {0} not available. +repositoryNotFound=Repository {0} not found +repositoryReadOnly=Repository {0} is read-only +lfsUnavailable=LFS is not available for repository {0} +lfsUnathorized=Not authorized to perform operation {0} on repository {1} +lfsFailedToGetRepository=failed to get repository {0} +lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL" +serverFailure=When trying to open a connection to {0} the server responded with an error code. rc={1} +wrongAmoutOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected +userConfigInvalid="User config file {0} invalid {1}" +missingLocalObject="Local Object {0} is missing" \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/.api_filters jgit-4.11.9/org.eclipse.jgit.lfs/.settings/.api_filters --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/.api_filters 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/.api_filters 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.core.resources.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.core.resources.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.core.resources.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.core.resources.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.core.runtime.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.core.runtime.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.core.runtime.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.core.runtime.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,399 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +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.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +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.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.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.tasks.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.tasks.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.tasks.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.tasks.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +resolve.requirebundle=false diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017, Markus Duft + * 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.lfs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.LfsFactory; + +/** + * Implementation of {@link LfsFactory}, using built-in (optional) LFS support. + * + * @since 4.11 + */ +public class BuiltinLFS extends LfsFactory { + + private BuiltinLFS() { + SmudgeFilter.register(); + CleanFilter.register(); + } + + /** + * Activates the built-in LFS support. + */ + public static void register() { + setInstance(new BuiltinLFS()); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public ObjectLoader applySmudgeFilter(Repository db, ObjectLoader loader, + Attribute attribute) throws IOException { + if (isEnabled(db) && (attribute == null || isEnabled(db, attribute))) { + return LfsBlobFilter.smudgeLfsBlob(db, loader); + } else { + return loader; + } + } + + @Override + public LfsInputStream applyCleanFilter(Repository db, InputStream input, + long length, Attribute attribute) throws IOException { + if (isEnabled(db, attribute)) { + return new LfsInputStream(LfsBlobFilter.cleanLfsBlob(db, input)); + } else { + return new LfsInputStream(input, length); + } + } + + @Override + public @Nullable PrePushHook getPrePushHook(Repository repo, + PrintStream outputStream) { + if (isEnabled(repo)) { + return new LfsPrePushHook(repo, outputStream); + } + return null; + } + + /** + * @param db + * the repository + * @return whether LFS is requested for the given repo. + */ + @Override + public boolean isEnabled(Repository db) { + if (db == null) { + return false; + } + return db.getConfig().getBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, + false); + } + + /** + * @param db + * the repository + * @param attribute + * the attribute to check + * @return whether LFS filter is enabled for the given .gitattribute + * attribute. + */ + private boolean isEnabled(Repository db, Attribute attribute) { + if (attribute == null) { + return false; + } + return isEnabled(db) && ConfigConstants.CONFIG_SECTION_LFS + .equals(attribute.getValue()); + } + + @Override + public LfsInstallCommand getInstallCommand() { + return new InstallBuiltinLfsCommand(); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandFactory; +import org.eclipse.jgit.attributes.FilterCommandRegistry; +import org.eclipse.jgit.lfs.errors.CorruptMediaFile; +import org.eclipse.jgit.lfs.internal.AtomicObjectOutputStream; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FileUtils; + +/** + * Built-in LFS clean filter + * + * When new content is about to be added to the git repository and this filter + * is configured for that content, then this filter will replace the original + * content with content of a so-called LFS pointer file. The pointer file + * content will then be added to the git repository. Additionally this filter + * writes the original content in a so-called 'media file' to '.git/lfs/objects/ + * <first-two-characters-of-contentid>/<rest-of-contentid>' + * + * @see Git + * LFS Specification + * @since 4.6 + */ +public class CleanFilter extends FilterCommand { + /** + * The factory is responsible for creating instances of + * {@link org.eclipse.jgit.lfs.CleanFilter} + */ + public final static FilterCommandFactory FACTORY = new FilterCommandFactory() { + + @Override + public FilterCommand create(Repository db, InputStream in, + OutputStream out) throws IOException { + return new CleanFilter(db, in, out); + } + }; + + /** + * Registers this filter by calling + * {@link FilterCommandRegistry#register(String, FilterCommandFactory)} + */ + static void register() { + FilterCommandRegistry + .register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX + + Constants.ATTR_FILTER_DRIVER_PREFIX + + org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_CLEAN, + FACTORY); + } + + // Used to compute the hash for the original content + private AtomicObjectOutputStream aOut; + + private Lfs lfsUtil; + + // the size of the original content + private long size; + + // a temporary file into which the original content is written. When no + // errors occur this file will be renamed to the mediafile + private Path tmpFile; + + /** + * Constructor for CleanFilter. + * + * @param db + * the repository + * @param in + * an {@link java.io.InputStream} providing the original content + * @param out + * the {@link java.io.OutputStream} into which the content of the + * pointer file should be written. That's the content which will + * be added to the git repository + * @throws java.io.IOException + * when the creation of the temporary file fails or when no + * {@link java.io.OutputStream} for this file can be created + */ + public CleanFilter(Repository db, InputStream in, OutputStream out) + throws IOException { + super(in, out); + lfsUtil = new Lfs(db); + Files.createDirectories(lfsUtil.getLfsTmpDir()); + tmpFile = lfsUtil.createTmpFile(); + this.aOut = new AtomicObjectOutputStream(tmpFile.toAbsolutePath()); + } + + /** {@inheritDoc} */ + @Override + public int run() throws IOException { + try { + byte[] buf = new byte[8192]; + int length = in.read(buf); + if (length != -1) { + aOut.write(buf, 0, length); + size += length; + return length; + } else { + aOut.close(); + AnyLongObjectId loid = aOut.getId(); + aOut = null; + Path mediaFile = lfsUtil.getMediaFile(loid); + if (Files.isRegularFile(mediaFile)) { + long fsSize = Files.size(mediaFile); + if (fsSize != size) { + throw new CorruptMediaFile(mediaFile, size, fsSize); + } else { + FileUtils.delete(tmpFile.toFile()); + } + } else { + Path parent = mediaFile.getParent(); + if (parent != null) { + FileUtils.mkdirs(parent.toFile(), true); + } + FileUtils.rename(tmpFile.toFile(), mediaFile.toFile(), + StandardCopyOption.ATOMIC_MOVE); + } + LfsPointer lfsPointer = new LfsPointer(loid, size); + lfsPointer.encode(out); + in.close(); + out.close(); + return -1; + } + } catch (IOException e) { + if (aOut != null) { + aOut.abort(); + } + in.close(); + out.close(); + throw e; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptLongObjectException.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptLongObjectException.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptLongObjectException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptLongObjectException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.errors; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; + +/** + * Thrown when an object id is given that doesn't match the hash of the object's + * content + * + * @since 4.3 + */ +public class CorruptLongObjectException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + private final AnyLongObjectId id; + + private final AnyLongObjectId contentHash; + + /** + * Corrupt long object detected. + * + * @param id + * id of the long object + * @param contentHash + * hash of the long object's content + * @param message a {@link java.lang.String} object. + */ + public CorruptLongObjectException(AnyLongObjectId id, + AnyLongObjectId contentHash, + String message) { + super(message); + this.id = id; + this.contentHash = contentHash; + } + + /** + * Get the id of the object. + * + * @return the id of the object, i.e. the expected hash of the object's + * content + */ + public AnyLongObjectId getId() { + return id; + } + + /** + * Get the contentHash. + * + * @return the actual hash of the object's content which doesn't match the + * object's id when this exception is thrown which signals that the + * object has been corrupted + */ + public AnyLongObjectId getContentHash() { + return contentHash; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptMediaFile.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptMediaFile.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptMediaFile.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/CorruptMediaFile.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs.errors; + +import java.io.IOException; +import java.nio.file.Path; +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when a LFS mediafile is found which doesn't have the expected size + * + * @since 4.6 + */ +public class CorruptMediaFile extends IOException { + private static final long serialVersionUID = 1L; + + private Path mediaFile; + + private long expectedSize; + + private long size; + + /** + *

        Constructor for CorruptMediaFile.

        + * + * @param mediaFile a {@link java.nio.file.Path} object. + * @param expectedSize a long. + * @param size a long. + */ + @SuppressWarnings("boxing") + public CorruptMediaFile(Path mediaFile, long expectedSize, + long size) { + super(MessageFormat.format(LfsText.get().inconsistentMediafileLength, + mediaFile, expectedSize, size)); + this.mediaFile = mediaFile; + this.expectedSize = expectedSize; + this.size = size; + } + + /** + * Get the mediaFile. + * + * @return the media file which seems to be corrupt + */ + public Path getMediaFile() { + return mediaFile; + } + + /** + * Get the expectedSize. + * + * @return the expected size of the media file + */ + public long getExpectedSize() { + return expectedSize; + } + + /** + * Get the size. + * + * @return the actual size of the media file in the file system + */ + public long getSize() { + return size; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.lfs.errors; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when an invalid long object id is passed in as an argument. + * + * @since 4.3 + */ +public class InvalidLongObjectIdException extends IllegalArgumentException { + private static final long serialVersionUID = 1L; + + /** + * Create exception with bytes of the invalid object id. + * + * @param bytes containing the invalid id. + * @param offset in the byte array where the error occurred. + * @param length of the sequence of invalid bytes. + */ + public InvalidLongObjectIdException(byte[] bytes, int offset, int length) { + super(MessageFormat.format(LfsText.get().invalidLongId, + asAscii(bytes, offset, length))); + } + + /** + *

        Constructor for InvalidLongObjectIdException.

        + * + * @param idString + * String containing the invalid id + */ + public InvalidLongObjectIdException(String idString) { + super(MessageFormat.format(LfsText.get().invalidLongId, idString)); + } + + private static String asAscii(byte[] bytes, int offset, int length) { + try { + return new String(bytes, offset, length, US_ASCII); + } catch (StringIndexOutOfBoundsException e) { + return ""; //$NON-NLS-1$ + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +/** + * Thrown when the bandwidth limit for the user or repository has been exceeded. + * + * @since 4.5 + */ +public class LfsBandwidthLimitExceeded extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsBandwidthLimitExceeded.

        + * + * @param message + * error message, which may be shown to an end-user. + */ + public LfsBandwidthLimitExceeded(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs.errors; + +import java.io.IOException; + +/** + * Thrown when a LFS configuration problem has been detected (i.e. unable to + * find the remote LFS repository URL). + * + * @since 4.11 + */ +public class LfsConfigInvalidException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for LfsConfigInvalidException. + * + * @param msg + * the error description + */ + public LfsConfigInvalidException(String msg) { + super(msg); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +/** + * Thrown when an error occurs during LFS operation. + * + * @since 4.5 + */ +public class LfsException extends Exception { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsException.

        + * + * @param message + * error message, which may be shown to an end-user. + */ + public LfsException(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +/** + * Thrown when there is insufficient storage on the server. + * + * @since 4.5 + */ +public class LfsInsufficientStorage extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsInsufficientStorage.

        + * + * @param message + * error message, which may be shown to an end-user. + */ + public LfsInsufficientStorage(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +/** + * Thrown when the user has hit a rate limit with the server. + * + * @since 4.5 + */ +public class LfsRateLimitExceeded extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsRateLimitExceeded.

        + * + * @param message + * error message, which may be shown to an end-user. + */ + public LfsRateLimitExceeded(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when the repository does not exist for the user. + * + * @since 4.5 + */ +public class LfsRepositoryNotFound extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsRepositoryNotFound.

        + * + * @param name + * the repository name. + */ + public LfsRepositoryNotFound(String name) { + super(MessageFormat.format(LfsText.get().repositoryNotFound, name)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when the user has read, but not write access. Only applicable when the + * operation in the request is "upload". + * + * @since 4.5 + */ +public class LfsRepositoryReadOnly extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsRepositoryReadOnly.

        + * + * @param name + * the repository name. + */ + public LfsRepositoryReadOnly(String name) { + super(MessageFormat.format(LfsText.get().repositoryReadOnly, name)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017, David Pursehouse + * 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.lfs.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when authorization was refused for an LFS operation. + * + * @since 4.7 + */ +public class LfsUnauthorized extends LfsException { + private static final long serialVersionUID = 1L; + + /** + *

        Constructor for LfsUnauthorized.

        + * + * @param operation + * the operation that was attempted. + * @param name + * the repository name. + */ + public LfsUnauthorized(String operation, String name) { + super(MessageFormat.format(LfsText.get().lfsUnathorized, operation, + name)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Thrown when LFS is not available. + * + * @since 4.5 + */ +public class LfsUnavailable extends LfsException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for LfsUnavailable. + * + * @param name + * the repository name. + */ + public LfsUnavailable(String name) { + super(MessageFormat.format(LfsText.get().lfsUnavailable, name)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016, David Pursehouse + * 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.lfs.errors; + +/** + * Thrown when there is a validation error with one or more of the objects in + * the request. + * + * @since 4.5 + */ +public class LfsValidationError extends LfsException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for LfsValidationError. + * + * @param message + * error message, which may be shown to an end-user. + */ + public LfsValidationError(String message) { + super(message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018, Markus Duft + * 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.lfs; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.LfsFactory.LfsInstallCommand; +import org.eclipse.jgit.util.SystemReader; + +/** + * Installs all required LFS properties for the current user, analogous to 'git + * lfs install', but defaulting to using JGit builtin hooks. + * + * @since 4.11 + */ +public class InstallBuiltinLfsCommand implements LfsInstallCommand { + + private static final String[] ARGS_USER = new String[] { "lfs", "install" }; //$NON-NLS-1$//$NON-NLS-2$ + + private static final String[] ARGS_LOCAL = new String[] { "lfs", "install", //$NON-NLS-1$//$NON-NLS-2$ + "--local" }; //$NON-NLS-1$ + + private Repository repository; + + /** {@inheritDoc} */ + @Override + public Void call() throws Exception { + StoredConfig cfg = null; + if (repository == null) { + cfg = loadUserConfig(); + } else { + cfg = repository.getConfig(); + } + + cfg.setBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, true); + cfg.setBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_REQUIRED, true); + + cfg.save(); + + // try to run git lfs install, we really don't care if it is present + // and/or works here (yet). + ProcessBuilder builder = FS.DETECTED.runInShell("git", //$NON-NLS-1$ + repository == null ? ARGS_USER : ARGS_LOCAL); + if (repository != null) { + builder.directory(repository.isBare() ? repository.getDirectory() + : repository.getWorkTree()); + } + FS.DETECTED.runProcess(builder, null, null, (String) null); + + return null; + } + + /** + * Set the repository to install LFS for + * + * @param repo + * the repository to install LFS into locally instead of the user + * configuration + * @return this command + */ + @Override + public LfsInstallCommand setRepository(Repository repo) { + this.repository = repo; + return this; + } + + private StoredConfig loadUserConfig() throws IOException { + FileBasedConfig c = SystemReader.getInstance().openUserConfig(null, + FS.DETECTED); + try { + c.load(); + } catch (ConfigInvalidException e1) { + throw new IOException(MessageFormat + .format(LfsText.get().userConfigInvalid, c.getFile() + .getAbsolutePath(), e1), + e1); + } + + return c; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.internal; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; +import java.security.DigestOutputStream; +import java.text.MessageFormat; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.file.LockFile; +import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; + +/** + * Output stream writing content to a + * {@link org.eclipse.jgit.internal.storage.file.LockFile} which is committed on + * close(). The stream checks if the hash of the stream content matches the id. + */ +public class AtomicObjectOutputStream extends OutputStream { + + private LockFile locked; + + private DigestOutputStream out; + + private boolean aborted; + + private AnyLongObjectId id; + + /** + * Constructor for AtomicObjectOutputStream. + * + * @param path + * a {@link java.nio.file.Path} object. + * @param id + * a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object. + * @throws java.io.IOException + */ + public AtomicObjectOutputStream(Path path, AnyLongObjectId id) + throws IOException { + locked = new LockFile(path.toFile()); + locked.lock(); + this.id = id; + out = new DigestOutputStream(locked.getOutputStream(), + Constants.newMessageDigest()); + } + + /** + * Constructor for AtomicObjectOutputStream. + * + * @param path + * a {@link java.nio.file.Path} object. + * @throws java.io.IOException + */ + public AtomicObjectOutputStream(Path path) throws IOException { + this(path, null); + } + + /** + * Get the id. + * + * @return content hash of the object which was streamed through this + * stream. May return {@code null} if called before closing this + * stream. + */ + public @Nullable AnyLongObjectId getId() { + return id; + } + + /** {@inheritDoc} */ + @Override + public void write(int b) throws IOException { + out.write(b); + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] b) throws IOException { + out.write(b); + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + out.close(); + if (!aborted) { + if (id != null) { + verifyHash(); + } else { + id = LongObjectId.fromRaw(out.getMessageDigest().digest()); + } + locked.commit(); + } + } + + private void verifyHash() { + AnyLongObjectId contentHash = LongObjectId + .fromRaw(out.getMessageDigest().digest()); + if (!contentHash.equals(id)) { + abort(); + throw new CorruptLongObjectException(id, contentHash, + MessageFormat.format(LfsText.get().corruptLongObject, + contentHash, id)); + } + } + + /** + * Aborts the stream. Temporary file will be deleted + */ + public void abort() { + locked.unlock(); + aborted = true; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2017, Markus Duft + * 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.lfs.internal; + +import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; +import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; +import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ProxySelector; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.lfs.LfsPointer; +import org.eclipse.jgit.lfs.Protocol; +import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.HttpConfig; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.RemoteSession; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.HttpSupport; +import org.eclipse.jgit.util.io.MessageWriter; +import org.eclipse.jgit.util.io.StreamCopyThread; + +/** + * Provides means to get a valid LFS connection for a given repository. + */ +public class LfsConnectionFactory { + + private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$ + private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$ + private static final Map sshAuthCache = new TreeMap<>(); + + /** + * Determine URL of LFS server by looking into config parameters lfs.url, + * lfs.[remote].url or remote.[remote].url. The LFS server URL is computed + * from remote.[remote].url by appending "/info/lfs". In case there is no + * URL configured, a SSH remote URI can be used to auto-detect the LFS URI + * by using the remote "git-lfs-authenticate" command. + * + * @param db + * the repository to work with + * @param method + * the method (GET,PUT,...) of the request this connection will + * be used for + * @param purpose + * the action, e.g. Protocol.OPERATION_DOWNLOAD + * @return the url for the lfs server. e.g. + * "https://github.com/github/git-lfs.git/info/lfs" + * @throws IOException + */ + public static HttpConnection getLfsConnection(Repository db, String method, + String purpose) throws IOException { + StoredConfig config = db.getConfig(); + Map additionalHeaders = new TreeMap<>(); + String lfsUrl = getLfsUrl(db, purpose, additionalHeaders); + URL url = new URL(lfsUrl + Protocol.OBJECTS_LFS_ENDPOINT); + HttpConnection connection = HttpTransport.getConnectionFactory().create( + url, HttpSupport.proxyFor(ProxySelector.getDefault(), url)); + connection.setDoOutput(true); + if (url.getProtocol().equals(SCHEME_HTTPS) + && !config.getBoolean(HttpConfig.HTTP, + HttpConfig.SSL_VERIFY_KEY, true)) { + HttpSupport.disableSslVerify(connection); + } + connection.setRequestMethod(method); + connection.setRequestProperty(HDR_ACCEPT, + Protocol.CONTENTTYPE_VND_GIT_LFS_JSON); + connection.setRequestProperty(HDR_CONTENT_TYPE, + Protocol.CONTENTTYPE_VND_GIT_LFS_JSON); + additionalHeaders + .forEach((k, v) -> connection.setRequestProperty(k, v)); + return connection; + } + + private static String getLfsUrl(Repository db, String purpose, + Map additionalHeaders) + throws LfsConfigInvalidException { + StoredConfig config = db.getConfig(); + String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS, + null, + ConfigConstants.CONFIG_KEY_URL); + if (lfsUrl == null) { + String remoteUrl = null; + for (String remote : db.getRemoteNames()) { + lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS, + remote, + ConfigConstants.CONFIG_KEY_URL); + // This could be done better (more precise logic), but according + // to https://github.com/git-lfs/git-lfs/issues/1759 git-lfs + // generally only supports 'origin' in an integrated workflow. + if (lfsUrl == null && (remote.equals( + org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME))) { + remoteUrl = config.getString( + ConfigConstants.CONFIG_KEY_REMOTE, remote, + ConfigConstants.CONFIG_KEY_URL); + } + break; + } + if (lfsUrl == null && remoteUrl != null) { + lfsUrl = discoverLfsUrl(db, purpose, additionalHeaders, + remoteUrl); + } else { + lfsUrl = lfsUrl + Protocol.INFO_LFS_ENDPOINT; + } + } + if (lfsUrl == null) { + throw new LfsConfigInvalidException(LfsText.get().lfsNoDownloadUrl); + } + return lfsUrl; + } + + private static String discoverLfsUrl(Repository db, String purpose, + Map additionalHeaders, String remoteUrl) { + try { + URIish u = new URIish(remoteUrl); + if (SCHEME_SSH.equals(u.getScheme())) { + Protocol.ExpiringAction action = getSshAuthentication( + db, purpose, remoteUrl, u); + additionalHeaders.putAll(action.header); + return action.href; + } else { + return remoteUrl + Protocol.INFO_LFS_ENDPOINT; + } + } catch (Exception e) { + return null; // could not discover + } + } + + private static Protocol.ExpiringAction getSshAuthentication( + Repository db, String purpose, String remoteUrl, URIish u) + throws IOException { + AuthCache cached = sshAuthCache.get(remoteUrl); + Protocol.ExpiringAction action = null; + if (cached != null && cached.validUntil > System.currentTimeMillis()) { + action = cached.cachedAction; + } + + if (action == null) { + // discover and authenticate; git-lfs does "ssh + // -p -- git-lfs-authenticate + // " + String json = runSshCommand(u.setPath(""), //$NON-NLS-1$ + db.getFS(), + "git-lfs-authenticate " + extractProjectName(u) + " " //$NON-NLS-1$//$NON-NLS-2$ + + purpose); + + action = Protocol.gson().fromJson(json, + Protocol.ExpiringAction.class); + + // cache the result as long as possible. + AuthCache c = new AuthCache(action); + sshAuthCache.put(remoteUrl, c); + } + return action; + } + + /** + * Create a connection for the specified + * {@link org.eclipse.jgit.lfs.Protocol.Action}. + * + * @param repo + * the repo to fetch required configuration from + * @param action + * the action for which to create a connection + * @param method + * the target method (GET or PUT) + * @return a connection. output mode is not set. + * @throws IOException + * in case of any error. + */ + public static @NonNull HttpConnection getLfsContentConnection( + Repository repo, Protocol.Action action, String method) + throws IOException { + URL contentUrl = new URL(action.href); + HttpConnection contentServerConn = HttpTransport.getConnectionFactory() + .create(contentUrl, HttpSupport + .proxyFor(ProxySelector.getDefault(), contentUrl)); + contentServerConn.setRequestMethod(method); + action.header + .forEach((k, v) -> contentServerConn.setRequestProperty(k, v)); + if (contentUrl.getProtocol().equals(SCHEME_HTTPS) + && !repo.getConfig().getBoolean(HttpConfig.HTTP, + HttpConfig.SSL_VERIFY_KEY, true)) { + HttpSupport.disableSslVerify(contentServerConn); + } + + contentServerConn.setRequestProperty(HDR_ACCEPT_ENCODING, + ENCODING_GZIP); + + return contentServerConn; + } + + private static String extractProjectName(URIish u) { + String path = u.getPath().substring(1); + if (path.endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT)) { + return path.substring(0, path.length() - 4); + } else { + return path; + } + } + + private static String runSshCommand(URIish sshUri, FS fs, String command) + throws IOException { + RemoteSession session = null; + Process process = null; + StreamCopyThread errorThread = null; + try (MessageWriter stderr = new MessageWriter()) { + session = SshSessionFactory.getInstance().getSession(sshUri, null, + fs, 5_000); + process = session.exec(command, 0); + errorThread = new StreamCopyThread(process.getErrorStream(), + stderr.getRawStream()); + errorThread.start(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), + org.eclipse.jgit.lib.Constants.CHARSET))) { + return reader.readLine(); + } + } finally { + if (process != null) { + process.destroy(); + } + if (errorThread != null) { + try { + errorThread.halt(); + } catch (InterruptedException e) { + // Stop waiting and return anyway. + } finally { + errorThread = null; + } + } + if (session != null) { + SshSessionFactory.getInstance().releaseSession(session); + } + } + } + + /** + * @param operation + * the operation to perform, e.g. Protocol.OPERATION_DOWNLOAD + * @param resources + * the LFS resources affected + * @return a request that can be serialized to JSON + */ + public static Protocol.Request toRequest(String operation, + LfsPointer... resources) { + Protocol.Request req = new Protocol.Request(); + req.operation = operation; + if (resources != null) { + req.objects = new LinkedList<>(); + for (LfsPointer res : resources) { + Protocol.ObjectSpec o = new Protocol.ObjectSpec(); + o.oid = res.getOid().getName(); + o.size = res.getSize(); + req.objects.add(o); + } + } + return req; + } + + private static final class AuthCache { + private static final long AUTH_CACHE_EAGER_TIMEOUT = 100; + + private static final SimpleDateFormat ISO_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSX"); //$NON-NLS-1$ + + /** + * Creates a cache entry for an authentication response. + *

        + * The timeout of the cache token is extracted from the given action. If + * no timeout can be determined, the token will be used only once. + * + * @param action + */ + public AuthCache(Protocol.ExpiringAction action) { + this.cachedAction = action; + try { + if (action.expiresIn != null && !action.expiresIn.isEmpty()) { + this.validUntil = System.currentTimeMillis() + + Long.parseLong(action.expiresIn); + } else if (action.expiresAt != null + && !action.expiresAt.isEmpty()) { + this.validUntil = ISO_FORMAT.parse(action.expiresAt) + .getTime() - AUTH_CACHE_EAGER_TIMEOUT; + } else { + this.validUntil = System.currentTimeMillis(); + } + } catch (Exception e) { + this.validUntil = System.currentTimeMillis(); + } + } + + long validUntil; + + Protocol.ExpiringAction cachedAction; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.internal; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Translation bundle for JGit LFS server + */ +public class LfsText extends TranslationBundle { + + /** + * Get an instance of this translation bundle. + * + * @return an instance of this translation bundle + */ + public static LfsText get() { + return NLS.getBundleFor(LfsText.class); + } + + // @formatter:off + /***/ public String corruptLongObject; + /***/ public String inconsistentMediafileLength; + /***/ public String inconsistentContentLength; + /***/ public String incorrectLONG_OBJECT_ID_LENGTH; + /***/ public String invalidLongId; + /***/ public String invalidLongIdLength; + /***/ public String lfsUnavailable; + /***/ public String protocolError; + /***/ public String requiredHashFunctionNotAvailable; + /***/ public String repositoryNotFound; + /***/ public String repositoryReadOnly; + /***/ public String lfsUnathorized; + /***/ public String lfsFailedToGetRepository; + /***/ public String lfsNoDownloadUrl; + /***/ public String serverFailure; + /***/ public String wrongAmoutOfDataReceived; + /***/ public String userConfigInvalid; + /***/ public String missingLocalObject; +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobFilter.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobFilter.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017, Markus Duft + * 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.lfs; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; + +/** + * Provides transparently either a stream to the blob or a LFS media file if + * managed by LFS. + * + * @since 4.11 + */ +public class LfsBlobFilter { + + /** + * In case the given {@link ObjectLoader} points to a LFS pointer file + * replace the loader with one pointing to the LFS media file contents. + * Missing LFS files are downloaded on the fly - same logic as the smudge + * filter. + * + * @param db + * the repo + * @param loader + * the loader for the blob + * @return either the original loader, or a loader for the LFS media file if + * managed by LFS. Files are downloaded on demand if required. + * @throws IOException + * in case of an error + */ + public static ObjectLoader smudgeLfsBlob(Repository db, ObjectLoader loader) + throws IOException { + if (loader.getSize() > LfsPointer.SIZE_THRESHOLD) { + return loader; + } + + try (InputStream is = loader.openStream()) { + LfsPointer ptr = LfsPointer.parseLfsPointer(is); + if (ptr != null) { + Lfs lfs = new Lfs(db); + AnyLongObjectId oid = ptr.getOid(); + Path mediaFile = lfs.getMediaFile(oid); + if (!Files.exists(mediaFile)) { + SmudgeFilter.downloadLfsResource(lfs, db, ptr); + } + + return new LfsBlobLoader(mediaFile); + } + } + + return loader; + } + + /** + * Run the LFS clean filter on the given stream and return a stream to the + * LFS pointer file buffer. Used when inserting objects. + * + * @param db + * the {@link Repository} + * @param originalContent + * the {@link InputStream} to the original content + * @return a {@link TemporaryBuffer} representing the LFS pointer. The + * caller is responsible to destroy the buffer. + * @throws IOException + * in case of any error. + */ + public static TemporaryBuffer cleanLfsBlob(Repository db, + InputStream originalContent) throws IOException { + LocalFile buffer = new TemporaryBuffer.LocalFile(null); + CleanFilter f = new CleanFilter(db, originalContent, buffer); + try { + while (f.run() != -1) { + // loop as long as f.run() tells there is work to do + } + } catch (IOException e) { + buffer.destroy(); + throw e; + } + return buffer; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobLoader.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobLoader.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobLoader.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsBlobLoader.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017, Markus Duft + * 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.lfs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.util.IO; + +/** + * An {@link ObjectLoader} implementation that reads a media file from the LFS + * storage. + * + * @since 4.11 + */ +public class LfsBlobLoader extends ObjectLoader { + + private Path mediaFile; + + private BasicFileAttributes attributes; + + private byte[] cached; + + /** + * Create a loader for the LFS media file at the given path. + * + * @param mediaFile + * path to the file + * @throws IOException + * in case of an error reading attributes + */ + public LfsBlobLoader(Path mediaFile) throws IOException { + this.mediaFile = mediaFile; + this.attributes = Files.readAttributes(mediaFile, + BasicFileAttributes.class); + } + + @Override + public int getType() { + return Constants.OBJ_BLOB; + } + + @Override + public long getSize() { + return attributes.size(); + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + if (getSize() > PackConfig.DEFAULT_BIG_FILE_THRESHOLD) { + throw new LargeObjectException(); + } + + if (cached == null) { + try { + cached = IO.readFully(mediaFile.toFile()); + } catch (IOException ioe) { + throw new LargeObjectException(ioe); + } + } + return cached; + } + + @Override + public ObjectStream openStream() + throws MissingObjectException, IOException { + return new ObjectStream.Filter(getType(), getSize(), + Files.newInputStream(mediaFile)); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Lfs.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Lfs.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Lfs.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Lfs.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lib.Repository; + +/** + * Class which represents the lfs folder hierarchy inside a {@code .git} folder + * + * @since 4.6 + */ +public class Lfs { + private Path root; + + private Path objDir; + + private Path tmpDir; + + /** + * Constructor for Lfs. + * + * @param root + * the path to the LFS media directory. Will be + * {@code "/.git/lfs"} + * @deprecated use {@link #Lfs(Repository)} instead. + */ + @Deprecated + public Lfs(Path root) { + this.root = root; + } + + /** + * Constructor for Lfs. + * + * @param db + * the associated repo + * + * @since 4.11 + */ + public Lfs(Repository db) { + this.root = db.getDirectory().toPath().resolve(Constants.LFS); + } + + /** + * Get the LFS root directory + * + * @return the path to the LFS directory + */ + public Path getLfsRoot() { + return root; + } + + /** + * Get the path to the temporary directory used by LFS. + * + * @return the path to the temporary directory used by LFS. Will be + * {@code /.git/lfs/tmp} + */ + public Path getLfsTmpDir() { + if (tmpDir == null) { + tmpDir = root.resolve("tmp"); //$NON-NLS-1$ + } + return tmpDir; + } + + /** + * Get the object directory used by LFS + * + * @return the path to the object directory used by LFS. Will be + * {@code /.git/lfs/objects} + */ + public Path getLfsObjDir() { + if (objDir == null) { + objDir = root.resolve("objects"); //$NON-NLS-1$ + } + return objDir; + } + + /** + * Get the media file which stores the original content + * + * @param id + * the id of the mediafile + * @return the file which stores the original content. Its path will look + * like + * {@code "/.git/lfs/objects//"} + */ + public Path getMediaFile(AnyLongObjectId id) { + String idStr = id.name(); + return getLfsObjDir().resolve(idStr.substring(0, 2)) + .resolve(idStr.substring(2, 4)).resolve(idStr); + } + + /** + * Create a new temp file in the LFS directory + * + * @return a new temporary file in the LFS directory + * @throws java.io.IOException + * when the temp file could not be created + */ + public Path createTmpFile() throws IOException { + return Files.createTempFile(getLfsTmpDir(), null, null); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Locale; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; + +/** + * Represents an LFS pointer file + * + * @since 4.6 + */ +public class LfsPointer implements Comparable { + /** + * The version of the LfsPointer file format + */ + public static final String VERSION = "https://git-lfs.github.com/spec/v1"; //$NON-NLS-1$ + + /** + * The version of the LfsPointer file format using legacy URL + * @since 4.7 + */ + public static final String VERSION_LEGACY = "https://hawser.github.com/spec/v1"; //$NON-NLS-1$ + + /** + * Don't inspect files that are larger than this threshold to avoid + * excessive reading. No pointer file should be larger than this. + * @since 4.11 + */ + public static final int SIZE_THRESHOLD = 200; + + /** + * The name of the hash function as used in the pointer files. This will + * evaluate to "sha256" + */ + public static final String HASH_FUNCTION_NAME = Constants.LONG_HASH_FUNCTION + .toLowerCase(Locale.ROOT).replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private AnyLongObjectId oid; + + private long size; + + /** + *

        Constructor for LfsPointer.

        + * + * @param oid + * the id of the content + * @param size + * the size of the content + */ + public LfsPointer(AnyLongObjectId oid, long size) { + this.oid = oid; + this.size = size; + } + + /** + *

        Getter for the field oid.

        + * + * @return the id of the content + */ + public AnyLongObjectId getOid() { + return oid; + } + + /** + *

        Getter for the field size.

        + * + * @return the size of the content + */ + public long getSize() { + return size; + } + + /** + * Encode this object into the LFS format defined by {@link #VERSION} + * + * @param out + * the {@link java.io.OutputStream} into which the encoded data should be + * written + */ + public void encode(OutputStream out) { + try (PrintStream ps = new PrintStream(out, false, + UTF_8.name())) { + ps.print("version "); //$NON-NLS-1$ + ps.print(VERSION + "\n"); //$NON-NLS-1$ + ps.print("oid " + HASH_FUNCTION_NAME + ":"); //$NON-NLS-1$ //$NON-NLS-2$ + ps.print(oid.name() + "\n"); //$NON-NLS-1$ + ps.print("size "); //$NON-NLS-1$ + ps.print(size + "\n"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // should not happen, we are using a standard charset + throw new UnsupportedCharsetException(UTF_8.name()); + } + } + + /** + * Try to parse the data provided by an InputStream to the format defined by + * {@link #VERSION} + * + * @param in + * the {@link java.io.InputStream} from where to read the data + * @return an {@link org.eclipse.jgit.lfs.LfsPointer} or null + * if the stream was not parseable as LfsPointer + * @throws java.io.IOException + */ + @Nullable + public static LfsPointer parseLfsPointer(InputStream in) + throws IOException { + boolean versionLine = false; + LongObjectId id = null; + long sz = -1; + + try (BufferedReader br = new BufferedReader( + new InputStreamReader(in, UTF_8))) { + for (String s = br.readLine(); s != null; s = br.readLine()) { + if (s.startsWith("#") || s.length() == 0) { //$NON-NLS-1$ + continue; + } else if (s.startsWith("version") && s.length() > 8 //$NON-NLS-1$ + && (s.substring(8).trim().equals(VERSION) || + s.substring(8).trim().equals(VERSION_LEGACY))) { + versionLine = true; + } else if (s.startsWith("oid sha256:")) { //$NON-NLS-1$ + id = LongObjectId.fromString(s.substring(11).trim()); + } else if (s.startsWith("size") && s.length() > 5) { //$NON-NLS-1$ + sz = Long.parseLong(s.substring(5).trim()); + } + } + if (versionLine && id != null && sz > -1) { + return new LfsPointer(id, sz); + } + } + return null; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "LfsPointer: oid=" + oid.name() + ", size=" //$NON-NLS-1$ //$NON-NLS-2$ + + size; + } + + /** + * @since 4.11 + */ + @Override + public int compareTo(LfsPointer o) { + int x = getOid().compareTo(o.getOid()); + if (x != 0) { + return x; + } + + return (int) (getSize() - o.getSize()); + } +} + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2017, Markus Duft + * 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.lfs; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lfs.Protocol.OPERATION_UPLOAD; +import static org.eclipse.jgit.lfs.internal.LfsConnectionFactory.toRequest; +import static org.eclipse.jgit.transport.http.HttpConnection.HTTP_OK; +import static org.eclipse.jgit.util.HttpSupport.METHOD_POST; +import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.lfs.Protocol.ObjectInfo; +import org.eclipse.jgit.lfs.errors.CorruptMediaFile; +import org.eclipse.jgit.lfs.internal.LfsConnectionFactory; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.ObjectWalk; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.http.HttpConnection; + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; + +/** + * Pre-push hook that handles uploading LFS artefacts. + * + * @since 4.11 + */ +public class LfsPrePushHook extends PrePushHook { + + private static final String EMPTY = ""; //$NON-NLS-1$ + private Collection refs; + + /** + * @param repo + * the repository + * @param outputStream + * not used by this implementation + */ + public LfsPrePushHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + @Override + public void setRefs(Collection toRefs) { + this.refs = toRefs; + } + + @Override + public String call() throws IOException, AbortedByHookException { + Set toPush = findObjectsToPush(); + if (toPush.isEmpty()) { + return EMPTY; + } + HttpConnection api = LfsConnectionFactory.getLfsConnection( + getRepository(), METHOD_POST, OPERATION_UPLOAD); + Map oid2ptr = requestBatchUpload(api, toPush); + uploadContents(api, oid2ptr); + return EMPTY; + + } + + private Set findObjectsToPush() throws IOException, + MissingObjectException, IncorrectObjectTypeException { + Set toPush = new TreeSet<>(); + + try (ObjectWalk walk = new ObjectWalk(getRepository())) { + for (RemoteRefUpdate up : refs) { + walk.setRewriteParents(false); + excludeRemoteRefs(walk); + walk.markStart(walk.parseCommit(up.getNewObjectId())); + while (walk.next() != null) { + // walk all commits to populate objects + } + findLfsPointers(toPush, walk); + } + } + return toPush; + } + + private static void findLfsPointers(Set toPush, ObjectWalk walk) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + RevObject obj; + ObjectReader r = walk.getObjectReader(); + while ((obj = walk.nextObject()) != null) { + if (obj.getType() == Constants.OBJ_BLOB + && getObjectSize(r, obj) < LfsPointer.SIZE_THRESHOLD) { + LfsPointer ptr = loadLfsPointer(r, obj); + if (ptr != null) { + toPush.add(ptr); + } + } + } + } + + private static long getObjectSize(ObjectReader r, RevObject obj) + throws IOException { + return r.getObjectSize(obj.getId(), Constants.OBJ_BLOB); + } + + private static LfsPointer loadLfsPointer(ObjectReader r, AnyObjectId obj) + throws IOException { + try (InputStream is = r.open(obj, Constants.OBJ_BLOB).openStream()) { + return LfsPointer.parseLfsPointer(is); + } + } + + private void excludeRemoteRefs(ObjectWalk walk) throws IOException { + RefDatabase refDatabase = getRepository().getRefDatabase(); + Map remoteRefs = refDatabase.getRefs(remote()); + for (Ref r : remoteRefs.values()) { + ObjectId oid = r.getPeeledObjectId(); + if (oid == null) { + oid = r.getObjectId(); + } + if (oid == null) { + // ignore (e.g. symbolic, ...) + continue; + } + RevObject o = walk.parseAny(oid); + if (o.getType() == Constants.OBJ_COMMIT + || o.getType() == Constants.OBJ_TAG) { + walk.markUninteresting(o); + } + } + } + + private String remote() { + String remoteName = getRemoteName() == null + ? Constants.DEFAULT_REMOTE_NAME + : getRemoteName(); + return Constants.R_REMOTES + remoteName; + } + + private Map requestBatchUpload(HttpConnection api, + Set toPush) throws IOException { + LfsPointer[] res = toPush.toArray(new LfsPointer[toPush.size()]); + Map oidStr2ptr = new HashMap<>(); + for (LfsPointer p : res) { + oidStr2ptr.put(p.getOid().name(), p); + } + Gson gson = Protocol.gson(); + api.getOutputStream().write( + gson.toJson(toRequest(OPERATION_UPLOAD, res)).getBytes(UTF_8)); + int responseCode = api.getResponseCode(); + if (responseCode != HTTP_OK) { + throw new IOException( + MessageFormat.format(LfsText.get().serverFailure, + api.getURL(), Integer.valueOf(responseCode))); + } + return oidStr2ptr; + } + + private void uploadContents(HttpConnection api, + Map oid2ptr) throws IOException { + try (JsonReader reader = new JsonReader( + new InputStreamReader(api.getInputStream()))) { + for (Protocol.ObjectInfo o : parseObjects(reader)) { + if (o.actions == null) { + continue; + } + LfsPointer ptr = oid2ptr.get(o.oid); + if (ptr == null) { + // received an object we didn't request + continue; + } + Protocol.Action uploadAction = o.actions.get(OPERATION_UPLOAD); + if (uploadAction == null || uploadAction.href == null) { + continue; + } + + Lfs lfs = new Lfs(getRepository()); + Path path = lfs.getMediaFile(ptr.getOid()); + if (!Files.exists(path)) { + throw new IOException(MessageFormat + .format(LfsText.get().missingLocalObject, path)); + } + uploadFile(o, uploadAction, path); + } + } + } + + private List parseObjects(JsonReader reader) { + Gson gson = new Gson(); + Protocol.Response resp = gson.fromJson(reader, Protocol.Response.class); + return resp.objects; + } + + private void uploadFile(Protocol.ObjectInfo o, + Protocol.Action uploadAction, Path path) + throws IOException, CorruptMediaFile { + HttpConnection contentServer = LfsConnectionFactory + .getLfsContentConnection(getRepository(), uploadAction, + METHOD_PUT); + contentServer.setDoOutput(true); + try (OutputStream out = contentServer + .getOutputStream()) { + long size = Files.copy(path, out); + if (size != o.size) { + throw new CorruptMediaFile(path, o.size, size); + } + } + int responseCode = contentServer.getResponseCode(); + if (responseCode != HTTP_OK) { + throw new IOException(MessageFormat.format( + LfsText.get().serverFailure, contentServer.getURL(), + Integer.valueOf(responseCode))); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import java.io.Serializable; +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A prefix abbreviation of an {@link org.eclipse.jgit.lfs.lib.LongObjectId}. + *

        + * Enable abbreviating SHA-256 strings used by Git LFS, using sufficient leading + * digits from the LongObjectId name to still be unique within the repository + * the string was generated from. These ids are likely to be unique for a useful + * period of time, especially if they contain at least 6-10 hex digits. + *

        + * This class converts the hex string into a binary form, to make it more + * efficient for matching against an object. + * + * Ported to SHA-256 from {@link org.eclipse.jgit.lib.AbbreviatedObjectId} + * + * @since 4.3 + */ +public final class AbbreviatedLongObjectId implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Test a string of characters to verify it is a hex format. + *

        + * If true the string can be parsed with {@link #fromString(String)}. + * + * @param id + * the string to test. + * @return true if the string can converted into an AbbreviatedObjectId. + */ + public static final boolean isId(final String id) { + if (id.length() < 2 + || Constants.LONG_OBJECT_ID_STRING_LENGTH < id.length()) + return false; + try { + for (int i = 0; i < id.length(); i++) + RawParseUtils.parseHexInt4((byte) id.charAt(i)); + return true; + } catch (ArrayIndexOutOfBoundsException e) { + return false; + } + } + + /** + * Convert an AbbreviatedObjectId from hex characters (US-ASCII). + * + * @param buf + * the US-ASCII buffer to read from. + * @param offset + * position to read the first character from. + * @param end + * one past the last position to read (end-offset is + * the length of the string). + * @return the converted object id. + */ + public static final AbbreviatedLongObjectId fromString(final byte[] buf, + final int offset, final int end) { + if (end - offset > Constants.LONG_OBJECT_ID_STRING_LENGTH) + throw new IllegalArgumentException(MessageFormat.format( + LfsText.get().invalidLongIdLength, + Integer.valueOf(end - offset), + Integer.valueOf(Constants.LONG_OBJECT_ID_STRING_LENGTH))); + return fromHexString(buf, offset, end); + } + + /** + * Convert an AbbreviatedObjectId from an + * {@link org.eclipse.jgit.lib.AnyObjectId}. + *

        + * This method copies over all bits of the Id, and is therefore complete + * (see {@link #isComplete()}). + * + * @param id + * the {@link org.eclipse.jgit.lib.ObjectId} to convert from. + * @return the converted object id. + */ + public static final AbbreviatedLongObjectId fromLongObjectId( + AnyLongObjectId id) { + return new AbbreviatedLongObjectId( + Constants.LONG_OBJECT_ID_STRING_LENGTH, id.w1, id.w2, id.w3, + id.w4); + } + + /** + * Convert an AbbreviatedLongObjectId from hex characters. + * + * @param str + * the string to read from. Must be <= 64 characters. + * @return the converted object id. + */ + public static final AbbreviatedLongObjectId fromString(final String str) { + if (str.length() > Constants.LONG_OBJECT_ID_STRING_LENGTH) + throw new IllegalArgumentException( + MessageFormat.format(LfsText.get().invalidLongId, str)); + final byte[] b = org.eclipse.jgit.lib.Constants.encodeASCII(str); + return fromHexString(b, 0, b.length); + } + + private static final AbbreviatedLongObjectId fromHexString(final byte[] bs, + int ptr, final int end) { + try { + final long a = hexUInt64(bs, ptr, end); + final long b = hexUInt64(bs, ptr + 16, end); + final long c = hexUInt64(bs, ptr + 32, end); + final long d = hexUInt64(bs, ptr + 48, end); + return new AbbreviatedLongObjectId(end - ptr, a, b, c, d); + } catch (ArrayIndexOutOfBoundsException e1) { + throw new InvalidLongObjectIdException(bs, ptr, end - ptr); + } + } + + private static final long hexUInt64(final byte[] bs, int p, final int end) { + if (16 <= end - p) + return RawParseUtils.parseHexInt64(bs, p); + + long r = 0; + int n = 0; + while (n < 16 && p < end) { + r <<= 4; + r |= RawParseUtils.parseHexInt4(bs[p++]); + n++; + } + return r << (16 - n) * 4; + } + + static long mask(final int nibbles, final long word, final long v) { + final long b = (word - 1) * 16; + if (b + 16 <= nibbles) { + // We have all of the bits required for this word. + // + return v; + } + + if (nibbles <= b) { + // We have none of the bits required for this word. + // + return 0; + } + + final long s = 64 - (nibbles - b) * 4; + return (v >>> s) << s; + } + + /** Number of half-bytes used by this id. */ + final int nibbles; + + final long w1; + + final long w2; + + final long w3; + + final long w4; + + AbbreviatedLongObjectId(final int n, final long new_1, final long new_2, + final long new_3, final long new_4) { + nibbles = n; + w1 = new_1; + w2 = new_2; + w3 = new_3; + w4 = new_4; + } + + /** + * Get length + * + * @return number of hex digits appearing in this id. + */ + public int length() { + return nibbles; + } + + /** + * Check if this id is complete + * + * @return true if this ObjectId is actually a complete id. + */ + public boolean isComplete() { + return length() == Constants.LONG_OBJECT_ID_STRING_LENGTH; + } + + /** + * Convert to LongObjectId + * + * @return a complete ObjectId; null if {@link #isComplete()} is false. + */ + public LongObjectId toLongObjectId() { + return isComplete() ? new LongObjectId(w1, w2, w3, w4) : null; + } + + /** + * Compares this abbreviation to a full object id. + * + * @param other + * the other object id. + * @return <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + public final int prefixCompare(final AnyLongObjectId other) { + int cmp; + + cmp = NB.compareUInt64(w1, mask(1, other.w1)); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, mask(2, other.w2)); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, mask(3, other.w3)); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, mask(4, other.w4)); + } + + /** + * Compare this abbreviation to a network-byte-order LongObjectId. + * + * @param bs + * array containing the other LongObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least + * 32 bytes, starting at this position are required. + * @return <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + public final int prefixCompare(final byte[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt64(w1, mask(1, NB.decodeInt64(bs, p))); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, mask(2, NB.decodeInt64(bs, p + 8))); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, mask(3, NB.decodeInt64(bs, p + 16))); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, mask(4, NB.decodeInt64(bs, p + 24))); + } + + /** + * Compare this abbreviation to a network-byte-order LongObjectId. + * + * @param bs + * array containing the other LongObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least 4 + * longs, starting at this position are required. + * @return <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + public final int prefixCompare(final long[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt64(w1, mask(1, bs[p])); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, mask(2, bs[p + 1])); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, mask(3, bs[p + 2])); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, mask(4, bs[p + 3])); + } + + /** + * Get the first byte of this id + * + * @return value for a fan-out style map, only valid of length >= 2. + */ + public final int getFirstByte() { + return (int) (w1 >>> 56); + } + + private long mask(final long word, final long v) { + return mask(nibbles, word, v); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (int) (w1 >> 32); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object o) { + if (o instanceof AbbreviatedLongObjectId) { + final AbbreviatedLongObjectId b = (AbbreviatedLongObjectId) o; + return nibbles == b.nibbles && w1 == b.w1 && w2 == b.w2 + && w3 == b.w3 && w4 == b.w4; + } + return false; + } + + /** + *

        name.

        + * + * @return string form of the abbreviation, in lower case hexadecimal. + */ + public final String name() { + final char[] b = new char[Constants.LONG_OBJECT_ID_STRING_LENGTH]; + + AnyLongObjectId.formatHexChar(b, 0, w1); + if (nibbles <= 16) + return new String(b, 0, nibbles); + + AnyLongObjectId.formatHexChar(b, 16, w2); + if (nibbles <= 32) + return new String(b, 0, nibbles); + + AnyLongObjectId.formatHexChar(b, 32, w3); + if (nibbles <= 48) + return new String(b, 0, nibbles); + + AnyLongObjectId.formatHexChar(b, 48, w4); + return new String(b, 0, nibbles); + } + + /** {@inheritDoc} */ + @SuppressWarnings("nls") + @Override + public String toString() { + return "AbbreviatedLongObjectId[" + name() + "]"; //$NON-NLS-1$ + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.ByteBuffer; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.util.NB; + +/** + * A (possibly mutable) SHA-256 abstraction. + *

        + * If this is an instance of + * {@link org.eclipse.jgit.lfs.lib.MutableLongObjectId} the concept of equality + * with this instance can alter at any time, if this instance is modified to + * represent a different object name. + * + * Ported to SHA-256 from {@link org.eclipse.jgit.lib.AnyObjectId} + * + * @since 4.3 + */ +public abstract class AnyLongObjectId implements Comparable { + + /** + * Compare two object identifier byte sequences for equality. + * + * @param firstObjectId + * the first identifier to compare. Must not be null. + * @param secondObjectId + * the second identifier to compare. Must not be null. + * @return true if the two identifiers are the same. + */ + public static boolean equals(final AnyLongObjectId firstObjectId, + final AnyLongObjectId secondObjectId) { + if (firstObjectId == secondObjectId) + return true; + + // We test word 2 first as odds are someone already used our + // word 1 as a hash code, and applying that came up with these + // two instances we are comparing for equality. Therefore the + // first two words are very likely to be identical. We want to + // break away from collisions as quickly as possible. + // + return firstObjectId.w2 == secondObjectId.w2 + && firstObjectId.w3 == secondObjectId.w3 + && firstObjectId.w4 == secondObjectId.w4 + && firstObjectId.w1 == secondObjectId.w1; + } + + long w1; + + long w2; + + long w3; + + long w4; + + /** + * Get the first 8 bits of the LongObjectId. + * + * This is a faster version of {@code getByte(0)}. + * + * @return a discriminator usable for a fan-out style map. Returned values + * are unsigned and thus are in the range [0,255] rather than the + * signed byte range of [-128, 127]. + */ + public final int getFirstByte() { + return (int) (w1 >>> 56); + } + + /** + * Get the second 8 bits of the LongObjectId. + * + * @return a discriminator usable for a fan-out style map. Returned values + * are unsigned and thus are in the range [0,255] rather than the + * signed byte range of [-128, 127]. + */ + public final int getSecondByte() { + return (int) ((w1 >>> 48) & 0xff); + } + + /** + * Get any byte from the LongObjectId. + * + * Callers hard-coding {@code getByte(0)} should instead use the much faster + * special case variant {@link #getFirstByte()}. + * + * @param index + * index of the byte to obtain from the raw form of the + * LongObjectId. Must be in range [0, + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}). + * @return the value of the requested byte at {@code index}. Returned values + * are unsigned and thus are in the range [0,255] rather than the + * signed byte range of [-128, 127]. + * @throws java.lang.ArrayIndexOutOfBoundsException + * {@code index} is less than 0, equal to + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}, + * or greater than + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}. + */ + public final int getByte(int index) { + long w; + switch (index >> 3) { + case 0: + w = w1; + break; + case 1: + w = w2; + break; + case 2: + w = w3; + break; + case 3: + w = w4; + break; + default: + throw new ArrayIndexOutOfBoundsException(index); + } + + return (int) ((w >>> (8 * (15 - (index & 15)))) & 0xff); + } + + /** + * {@inheritDoc} + * + * Compare this LongObjectId to another and obtain a sort ordering. + */ + @Override + public final int compareTo(final AnyLongObjectId other) { + if (this == other) + return 0; + + int cmp; + + cmp = NB.compareUInt64(w1, other.w1); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, other.w2); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, other.w3); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, other.w4); + } + + /** + * Compare this LongObjectId to a network-byte-order LongObjectId. + * + * @param bs + * array containing the other LongObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least + * 32 bytes, starting at this position are required. + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public final int compareTo(final byte[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt64(w1, NB.decodeInt64(bs, p)); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, NB.decodeInt64(bs, p + 8)); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, NB.decodeInt64(bs, p + 16)); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, NB.decodeInt64(bs, p + 24)); + } + + /** + * Compare this LongObjectId to a network-byte-order LongObjectId. + * + * @param bs + * array containing the other LongObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least 4 + * longs, starting at this position are required. + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public final int compareTo(final long[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt64(w1, bs[p]); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w2, bs[p + 1]); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt64(w3, bs[p + 2]); + if (cmp != 0) + return cmp; + + return NB.compareUInt64(w4, bs[p + 3]); + } + + /** + * Tests if this LongObjectId starts with the given abbreviation. + * + * @param abbr + * the abbreviation. + * @return true if this LongObjectId begins with the abbreviation; else + * false. + */ + public boolean startsWith(final AbbreviatedLongObjectId abbr) { + return abbr.prefixCompare(this) == 0; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (int) (w1 >> 32); + } + + /** + * Determine if this LongObjectId has exactly the same value as another. + * + * @param other + * the other id to compare to. May be null. + * @return true only if both LongObjectIds have identical bits. + */ + public final boolean equals(final AnyLongObjectId other) { + return other != null ? equals(this, other) : false; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + if (o instanceof AnyLongObjectId) + return equals((AnyLongObjectId) o); + else + return false; + } + + /** + * Copy this LongObjectId to an output writer in raw binary. + * + * @param w + * the buffer to copy to. Must be in big endian order. + */ + public void copyRawTo(final ByteBuffer w) { + w.putLong(w1); + w.putLong(w2); + w.putLong(w3); + w.putLong(w4); + } + + /** + * Copy this LongObjectId to a byte array. + * + * @param b + * the buffer to copy to. + * @param o + * the offset within b to write at. + */ + public void copyRawTo(final byte[] b, final int o) { + NB.encodeInt64(b, o, w1); + NB.encodeInt64(b, o + 8, w2); + NB.encodeInt64(b, o + 16, w3); + NB.encodeInt64(b, o + 24, w4); + } + + /** + * Copy this LongObjectId to an long array. + * + * @param b + * the buffer to copy to. + * @param o + * the offset within b to write at. + */ + public void copyRawTo(final long[] b, final int o) { + b[o] = w1; + b[o + 1] = w2; + b[o + 2] = w3; + b[o + 3] = w4; + } + + /** + * Copy this LongObjectId to an output writer in raw binary. + * + * @param w + * the stream to write to. + * @throws java.io.IOException + * the stream writing failed. + */ + public void copyRawTo(final OutputStream w) throws IOException { + writeRawLong(w, w1); + writeRawLong(w, w2); + writeRawLong(w, w3); + writeRawLong(w, w4); + } + + private static void writeRawLong(final OutputStream w, long v) + throws IOException { + w.write((int) (v >>> 56)); + w.write((int) (v >>> 48)); + w.write((int) (v >>> 40)); + w.write((int) (v >>> 32)); + w.write((int) (v >>> 24)); + w.write((int) (v >>> 16)); + w.write((int) (v >>> 8)); + w.write((int) v); + } + + /** + * Copy this LongObjectId to an output writer in hex format. + * + * @param w + * the stream to copy to. + * @throws java.io.IOException + * the stream writing failed. + */ + public void copyTo(final OutputStream w) throws IOException { + w.write(toHexByteArray()); + } + + /** + * Copy this LongObjectId to a byte array in hex format. + * + * @param b + * the buffer to copy to. + * @param o + * the offset within b to write at. + */ + public void copyTo(byte[] b, int o) { + formatHexByte(b, o + 0, w1); + formatHexByte(b, o + 16, w2); + formatHexByte(b, o + 32, w3); + formatHexByte(b, o + 48, w4); + } + + /** + * Copy this LongObjectId to a ByteBuffer in hex format. + * + * @param b + * the buffer to copy to. + */ + public void copyTo(ByteBuffer b) { + b.put(toHexByteArray()); + } + + private byte[] toHexByteArray() { + final byte[] dst = new byte[Constants.LONG_OBJECT_ID_STRING_LENGTH]; + formatHexByte(dst, 0, w1); + formatHexByte(dst, 16, w2); + formatHexByte(dst, 32, w3); + formatHexByte(dst, 48, w4); + return dst; + } + + private static final byte[] hexbyte = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + private static void formatHexByte(final byte[] dst, final int p, long w) { + int o = p + 15; + while (o >= p && w != 0) { + dst[o--] = hexbyte[(int) (w & 0xf)]; + w >>>= 4; + } + while (o >= p) + dst[o--] = '0'; + } + + /** + * Copy this LongObjectId to an output writer in hex format. + * + * @param w + * the stream to copy to. + * @throws java.io.IOException + * the stream writing failed. + */ + public void copyTo(final Writer w) throws IOException { + w.write(toHexCharArray()); + } + + /** + * Copy this LongObjectId to an output writer in hex format. + * + * @param tmp + * temporary char array to buffer construct into before writing. + * Must be at least large enough to hold 2 digits for each byte + * of object id (64 characters or larger). + * @param w + * the stream to copy to. + * @throws java.io.IOException + * the stream writing failed. + */ + public void copyTo(final char[] tmp, final Writer w) throws IOException { + toHexCharArray(tmp); + w.write(tmp, 0, Constants.LONG_OBJECT_ID_STRING_LENGTH); + } + + /** + * Copy this LongObjectId to a StringBuilder in hex format. + * + * @param tmp + * temporary char array to buffer construct into before writing. + * Must be at least large enough to hold 2 digits for each byte + * of object id (64 characters or larger). + * @param w + * the string to append onto. + */ + public void copyTo(final char[] tmp, final StringBuilder w) { + toHexCharArray(tmp); + w.append(tmp, 0, Constants.LONG_OBJECT_ID_STRING_LENGTH); + } + + char[] toHexCharArray() { + final char[] dst = new char[Constants.LONG_OBJECT_ID_STRING_LENGTH]; + toHexCharArray(dst); + return dst; + } + + private void toHexCharArray(final char[] dst) { + formatHexChar(dst, 0, w1); + formatHexChar(dst, 16, w2); + formatHexChar(dst, 32, w3); + formatHexChar(dst, 48, w4); + } + + private static final char[] hexchar = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + static void formatHexChar(final char[] dst, final int p, long w) { + int o = p + 15; + while (o >= p && w != 0) { + dst[o--] = hexchar[(int) (w & 0xf)]; + w >>>= 4; + } + while (o >= p) + dst[o--] = '0'; + } + + /** {@inheritDoc} */ + @SuppressWarnings("nls") + @Override + public String toString() { + return "AnyLongObjectId[" + name() + "]"; + } + + /** + * Get string form of the SHA-256 + * + * @return string form of the SHA-256, in lower case hexadecimal. + */ + public final String name() { + return new String(toHexCharArray()); + } + + /** + * Get string form of the SHA-256 + * + * @return string form of the SHA-256, in lower case hexadecimal. + */ + public final String getName() { + return name(); + } + + /** + * Return an abbreviation (prefix) of this object SHA-256. + *

        + * This implementation does not guarantee uniqueness. Callers should instead + * use + * {@link org.eclipse.jgit.lib.ObjectReader#abbreviate(AnyObjectId, int)} to + * obtain a unique abbreviation within the scope of a particular object + * database. + * + * @param len + * length of the abbreviated string. + * @return SHA-256 abbreviation. + */ + public AbbreviatedLongObjectId abbreviate(final int len) { + final long a = AbbreviatedLongObjectId.mask(len, 1, w1); + final long b = AbbreviatedLongObjectId.mask(len, 2, w2); + final long c = AbbreviatedLongObjectId.mask(len, 3, w3); + final long d = AbbreviatedLongObjectId.mask(len, 4, w4); + return new AbbreviatedLongObjectId(len, a, b, c, d); + } + + /** + * Obtain an immutable copy of this current object. + *

        + * Only returns this if this instance is an unsubclassed + * instance of {@link org.eclipse.jgit.lfs.lib.LongObjectId}; otherwise a + * new instance is returned holding the same value. + *

        + * This method is useful to shed any additional memory that may be tied to + * the subclass, yet retain the unique identity of the object id for future + * lookups within maps and repositories. + * + * @return an immutable copy, using the smallest memory footprint possible. + */ + public final LongObjectId copy() { + if (getClass() == LongObjectId.class) + return (LongObjectId) this; + return new LongObjectId(this); + } + + /** + * Obtain an immutable copy of this current object. + *

        + * See {@link #copy()} if this is a possibly subclassed (but + * immutable) identity and the application needs a lightweight identity + * only reference. + * + * @return an immutable copy. May be this if this is already an + * immutable instance. + */ + public abstract LongObjectId toObjectId(); +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.internal.LfsText; + +/** + * Misc. constants used throughout JGit LFS extension. + * + * @since 4.3 + */ +@SuppressWarnings("nls") +public final class Constants { + /** + * lfs folder/section/filter name + * + * @since 4.6 + */ + public static final String LFS = "lfs"; + + /** + * Hash function used natively by Git LFS extension for large objects. + * + * @since 4.6 + */ + public static final String LONG_HASH_FUNCTION = "SHA-256"; + + /** + * A Git LFS large object hash is 256 bits, i.e. 32 bytes. + *

        + * Changing this assumption is not going to be as easy as changing this + * declaration. + */ + public static final int LONG_OBJECT_ID_LENGTH = 32; + + /** + * A Git LFS large object can be expressed as a 64 character string of + * hexadecimal digits. + * + * @see #LONG_OBJECT_ID_LENGTH + */ + public static final int LONG_OBJECT_ID_STRING_LENGTH = LONG_OBJECT_ID_LENGTH + * 2; + + /** + * LFS upload operation. + * + * @since 4.7 + */ + public static final String UPLOAD = "upload"; + + /** + * LFS download operation. + * + * @since 4.7 + */ + public static final String DOWNLOAD = "download"; + + /** + * LFS verify operation. + * + * @since 4.7 + */ + public static final String VERIFY = "verify"; + + /** + * Prefix for all LFS related filters. + * + * @since 4.11 + */ + public static final String ATTR_FILTER_DRIVER_PREFIX = "lfs/"; + + /** + * Create a new digest function for objects. + * + * @return a new digest object. + * @throws java.lang.RuntimeException + * this Java virtual machine does not support the required hash + * function. Very unlikely given that JGit uses a hash function + * that is in the Java reference specification. + */ + public static MessageDigest newMessageDigest() { + try { + return MessageDigest.getInstance(LONG_HASH_FUNCTION); + } catch (NoSuchAlgorithmException nsae) { + throw new RuntimeException(MessageFormat.format( + LfsText.get().requiredHashFunctionNotAvailable, + LONG_HASH_FUNCTION), nsae); + } + } + + static { + if (LONG_OBJECT_ID_LENGTH != newMessageDigest().getDigestLength()) + throw new LinkageError( + LfsText.get().incorrectLONG_OBJECT_ID_LENGTH); + } + + /** + * Content type used by LFS REST API as defined in + * https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md + */ + public static final String CONTENT_TYPE_GIT_LFS_JSON = "application/vnd.git-lfs+json"; + + /** + * "Arbitrary binary data" as defined in + * RFC 2046 + */ + public static final String HDR_APPLICATION_OCTET_STREAM = "application/octet-stream"; +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015, 2017, Dariusz Luksza + * 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.lfs.lib; + +import java.io.IOException; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lfs.LfsPointer; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * Detects Large File pointers, as described in [1] in Git repository. + * + * [1] https://github.com/github/git-lfs/blob/master/docs/spec.md + * + * @since 4.7 + */ +public class LfsPointerFilter extends TreeFilter { + + private LfsPointer pointer; + + /** + * Get the field pointer. + * + * @return {@link org.eclipse.jgit.lfs.LfsPointer} or {@code null} + */ + public LfsPointer getPointer() { + return pointer; + } + + /** {@inheritDoc} */ + @Override + public boolean include(TreeWalk walk) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + pointer = null; + if (walk.isSubtree()) { + return walk.isRecursive(); + } + ObjectId objectId = walk.getObjectId(0); + ObjectLoader object = walk.getObjectReader().open(objectId); + if (object.getSize() > 1024) { + return false; + } + + try (ObjectStream stream = object.openStream()) { + pointer = LfsPointer.parseLfsPointer(stream); + return pointer != null; + } + } + + /** {@inheritDoc} */ + @Override + public boolean shouldBeRecursive() { + return false; + } + + /** {@inheritDoc} */ + @Override + public TreeFilter clone() { + return new LfsPointerFilter(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LongObjectId.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LongObjectId.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LongObjectId.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LongObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A SHA-256 abstraction. + * + * Ported to SHA-256 from {@link org.eclipse.jgit.lib.ObjectId} + * + * @since 4.3 + */ +public class LongObjectId extends AnyLongObjectId implements Serializable { + private static final long serialVersionUID = 1L; + + private static final LongObjectId ZEROID; + + private static final String ZEROID_STR; + + static { + ZEROID = new LongObjectId(0L, 0L, 0L, 0L); + ZEROID_STR = ZEROID.name(); + } + + /** + * Get the special all-zero LongObjectId. + * + * @return the all-zero LongObjectId, often used to stand-in for no object. + */ + public static final LongObjectId zeroId() { + return ZEROID; + } + + /** + * Test a string of characters to verify that it can be interpreted as + * LongObjectId. + *

        + * If true the string can be parsed with {@link #fromString(String)}. + * + * @param id + * the string to test. + * @return true if the string can converted into an LongObjectId. + */ + public static final boolean isId(final String id) { + if (id.length() != Constants.LONG_OBJECT_ID_STRING_LENGTH) + return false; + try { + for (int i = 0; i < Constants.LONG_OBJECT_ID_STRING_LENGTH; i++) { + RawParseUtils.parseHexInt4((byte) id.charAt(i)); + } + return true; + } catch (ArrayIndexOutOfBoundsException e) { + return false; + } + } + + /** + * Convert a LongObjectId into a hex string representation. + * + * @param i + * the id to convert. May be null. + * @return the hex string conversion of this id's content. + */ + public static final String toString(final LongObjectId i) { + return i != null ? i.name() : ZEROID_STR; + } + + /** + * Compare two object identifier byte sequences for equality. + * + * @param firstBuffer + * the first buffer to compare against. Must have at least 32 + * 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 32 + * 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. + */ + public static boolean equals(final byte[] firstBuffer, final int fi, + final byte[] secondBuffer, final int si) { + return firstBuffer[fi] == secondBuffer[si] + && firstBuffer[fi + 1] == secondBuffer[si + 1] + && firstBuffer[fi + 2] == secondBuffer[si + 2] + && firstBuffer[fi + 3] == secondBuffer[si + 3] + && firstBuffer[fi + 4] == secondBuffer[si + 4] + && firstBuffer[fi + 5] == secondBuffer[si + 5] + && firstBuffer[fi + 6] == secondBuffer[si + 6] + && firstBuffer[fi + 7] == secondBuffer[si + 7] + && firstBuffer[fi + 8] == secondBuffer[si + 8] + && firstBuffer[fi + 9] == secondBuffer[si + 9] + && firstBuffer[fi + 10] == secondBuffer[si + 10] + && firstBuffer[fi + 11] == secondBuffer[si + 11] + && firstBuffer[fi + 12] == secondBuffer[si + 12] + && firstBuffer[fi + 13] == secondBuffer[si + 13] + && firstBuffer[fi + 14] == secondBuffer[si + 14] + && firstBuffer[fi + 15] == secondBuffer[si + 15] + && firstBuffer[fi + 16] == secondBuffer[si + 16] + && firstBuffer[fi + 17] == secondBuffer[si + 17] + && firstBuffer[fi + 18] == secondBuffer[si + 18] + && firstBuffer[fi + 19] == secondBuffer[si + 19] + && firstBuffer[fi + 20] == secondBuffer[si + 20] + && firstBuffer[fi + 21] == secondBuffer[si + 21] + && firstBuffer[fi + 22] == secondBuffer[si + 22] + && firstBuffer[fi + 23] == secondBuffer[si + 23] + && firstBuffer[fi + 24] == secondBuffer[si + 24] + && firstBuffer[fi + 25] == secondBuffer[si + 25] + && firstBuffer[fi + 26] == secondBuffer[si + 26] + && firstBuffer[fi + 27] == secondBuffer[si + 27] + && firstBuffer[fi + 28] == secondBuffer[si + 28] + && firstBuffer[fi + 29] == secondBuffer[si + 29] + && firstBuffer[fi + 30] == secondBuffer[si + 30] + && firstBuffer[fi + 31] == secondBuffer[si + 31]; + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param bs + * the raw byte buffer to read from. At least 32 bytes must be + * available within this byte array. + * @return the converted object id. + */ + public static final LongObjectId fromRaw(final byte[] bs) { + return fromRaw(bs, 0); + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param bs + * the raw byte buffer to read from. At least 32 bytes after p + * must be available within this byte array. + * @param p + * position to read the first byte of data from. + * @return the converted object id. + */ + public static final LongObjectId fromRaw(final byte[] bs, final int p) { + final long a = NB.decodeInt64(bs, p); + final long b = NB.decodeInt64(bs, p + 8); + final long c = NB.decodeInt64(bs, p + 16); + final long d = NB.decodeInt64(bs, p + 24); + return new LongObjectId(a, b, c, d); + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param is + * the raw long buffer to read from. At least 4 longs must be + * available within this long array. + * @return the converted object id. + */ + public static final LongObjectId fromRaw(final long[] is) { + return fromRaw(is, 0); + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param is + * the raw long buffer to read from. At least 4 longs after p + * must be available within this long array. + * @param p + * position to read the first long of data from. + * @return the converted object id. + */ + public static final LongObjectId fromRaw(final long[] is, final int p) { + return new LongObjectId(is[p], is[p + 1], is[p + 2], is[p + 3]); + } + + /** + * Convert a LongObjectId from hex characters (US-ASCII). + * + * @param buf + * the US-ASCII buffer to read from. At least 64 bytes after + * offset must be available within this byte array. + * @param offset + * position to read the first character from. + * @return the converted object id. + */ + public static final LongObjectId fromString(final byte[] buf, final int offset) { + return fromHexString(buf, offset); + } + + /** + * Convert a LongObjectId from hex characters. + * + * @param str + * the string to read from. Must be 64 characters long. + * @return the converted object id. + */ + public static LongObjectId fromString(final String str) { + if (str.length() != Constants.LONG_OBJECT_ID_STRING_LENGTH) + throw new InvalidLongObjectIdException(str); + return fromHexString(org.eclipse.jgit.lib.Constants.encodeASCII(str), + 0); + } + + private static final LongObjectId fromHexString(final byte[] bs, int p) { + try { + final long a = RawParseUtils.parseHexInt64(bs, p); + final long b = RawParseUtils.parseHexInt64(bs, p + 16); + final long c = RawParseUtils.parseHexInt64(bs, p + 32); + final long d = RawParseUtils.parseHexInt64(bs, p + 48); + return new LongObjectId(a, b, c, d); + } catch (ArrayIndexOutOfBoundsException e1) { + throw new InvalidLongObjectIdException(bs, p, + Constants.LONG_OBJECT_ID_STRING_LENGTH); + } + } + + LongObjectId(final long new_1, final long new_2, final long new_3, + final long new_4) { + w1 = new_1; + w2 = new_2; + w3 = new_3; + w4 = new_4; + } + + /** + * Initialize this instance by copying another existing LongObjectId. + *

        + * This constructor is mostly useful for subclasses which want to extend a + * LongObjectId with more properties, but initialize from an existing + * LongObjectId instance acquired by other means. + * + * @param src + * another already parsed LongObjectId to copy the value out of. + */ + protected LongObjectId(final AnyLongObjectId src) { + w1 = src.w1; + w2 = src.w2; + w3 = src.w3; + w4 = src.w4; + } + + /** {@inheritDoc} */ + @Override + public LongObjectId toObjectId() { + return this; + } + + private void writeObject(ObjectOutputStream os) throws IOException { + os.writeLong(w1); + os.writeLong(w2); + os.writeLong(w3); + os.writeLong(w4); + } + + private void readObject(ObjectInputStream ois) throws IOException { + w1 = ois.readLong(); + w2 = ois.readLong(); + w3 = ois.readLong(); + w4 = ois.readLong(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/MutableLongObjectId.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/MutableLongObjectId.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/MutableLongObjectId.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/MutableLongObjectId.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import java.text.MessageFormat; + +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A mutable SHA-256 abstraction. + * + * Ported to SHA-256 from {@link org.eclipse.jgit.lib.MutableObjectId} + * + * @since 4.3 + */ +public class MutableLongObjectId extends AnyLongObjectId { + /** + * Empty constructor. Initialize object with default (zeros) value. + */ + public MutableLongObjectId() { + super(); + } + + /** + * Copying constructor. + * + * @param src + * original entry, to copy id from + */ + MutableLongObjectId(MutableLongObjectId src) { + fromObjectId(src); + } + + /** + * Set any byte in the id. + * + * @param index + * index of the byte to set in the raw form of the ObjectId. Must + * be in range [0, + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}). + * @param value + * the value of the specified byte at {@code index}. Values are + * unsigned and thus are in the range [0,255] rather than the + * signed byte range of [-128, 127]. + * @throws java.lang.ArrayIndexOutOfBoundsException + * {@code index} is less than 0, equal to + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}, + * or greater than + * {@link org.eclipse.jgit.lfs.lib.Constants#LONG_OBJECT_ID_LENGTH}. + */ + public void setByte(int index, int value) { + switch (index >> 3) { + case 0: + w1 = set(w1, index & 7, value); + break; + case 1: + w2 = set(w2, index & 7, value); + break; + case 2: + w3 = set(w3, index & 7, value); + break; + case 3: + w4 = set(w4, index & 7, value); + break; + default: + throw new ArrayIndexOutOfBoundsException(index); + } + } + + private static long set(long w, int index, long value) { + value &= 0xff; + + switch (index) { + case 0: + return (w & 0x00ffffffffffffffL) | (value << 56); + case 1: + return (w & 0xff00ffffffffffffL) | (value << 48); + case 2: + return (w & 0xffff00ffffffffffL) | (value << 40); + case 3: + return (w & 0xffffff00ffffffffL) | (value << 32); + case 4: + return (w & 0xffffffff00ffffffL) | (value << 24); + case 5: + return (w & 0xffffffffff00ffffL) | (value << 16); + case 6: + return (w & 0xffffffffffff00ffL) | (value << 8); + case 7: + return (w & 0xffffffffffffff00L) | value; + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Make this id match + * {@link org.eclipse.jgit.lfs.lib.LongObjectId#zeroId()}. + */ + public void clear() { + w1 = 0; + w2 = 0; + w3 = 0; + w4 = 0; + } + + /** + * Copy a LongObjectId into this mutable buffer. + * + * @param src + * the source id to copy from. + */ + public void fromObjectId(AnyLongObjectId src) { + this.w1 = src.w1; + this.w2 = src.w2; + this.w3 = src.w3; + this.w4 = src.w4; + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param bs + * the raw byte buffer to read from. At least 32 bytes must be + * available within this byte array. + */ + public void fromRaw(final byte[] bs) { + fromRaw(bs, 0); + } + + /** + * Convert a LongObjectId from raw binary representation. + * + * @param bs + * the raw byte buffer to read from. At least 32 bytes after p + * must be available within this byte array. + * @param p + * position to read the first byte of data from. + */ + public void fromRaw(final byte[] bs, final int p) { + w1 = NB.decodeInt64(bs, p); + w2 = NB.decodeInt64(bs, p + 8); + w3 = NB.decodeInt64(bs, p + 16); + w4 = NB.decodeInt64(bs, p + 24); + } + + /** + * Convert a LongObjectId from binary representation expressed in integers. + * + * @param longs + * the raw long buffer to read from. At least 4 longs must be + * available within this longs array. + */ + public void fromRaw(final long[] longs) { + fromRaw(longs, 0); + } + + /** + * Convert a LongObjectId from binary representation expressed in longs. + * + * @param longs + * the raw int buffer to read from. At least 4 longs after p must + * be available within this longs array. + * @param p + * position to read the first integer of data from. + */ + public void fromRaw(final long[] longs, final int p) { + w1 = longs[p]; + w2 = longs[p + 1]; + w3 = longs[p + 2]; + w4 = longs[p + 3]; + } + + /** + * Convert a LongObjectId from hex characters (US-ASCII). + * + * @param buf + * the US-ASCII buffer to read from. At least 32 bytes after + * offset must be available within this byte array. + * @param offset + * position to read the first character from. + */ + public void fromString(final byte[] buf, final int offset) { + fromHexString(buf, offset); + } + + /** + * Convert a LongObjectId from hex characters. + * + * @param str + * the string to read from. Must be 64 characters long. + */ + public void fromString(final String str) { + if (str.length() != Constants.LONG_OBJECT_ID_STRING_LENGTH) + throw new IllegalArgumentException( + MessageFormat.format(LfsText.get().invalidLongId, str)); + fromHexString(org.eclipse.jgit.lib.Constants.encodeASCII(str), 0); + } + + private void fromHexString(final byte[] bs, int p) { + try { + w1 = RawParseUtils.parseHexInt64(bs, p); + w2 = RawParseUtils.parseHexInt64(bs, p + 16); + w3 = RawParseUtils.parseHexInt64(bs, p + 32); + w4 = RawParseUtils.parseHexInt64(bs, p + 48); + } catch (ArrayIndexOutOfBoundsException e1) { + throw new InvalidLongObjectIdException(bs, p, + Constants.LONG_OBJECT_ID_STRING_LENGTH); + } + } + + /** {@inheritDoc} */ + @Override + public LongObjectId toObjectId() { + return new LongObjectId(this); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Protocol.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Protocol.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Protocol.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/Protocol.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs; + +import java.util.List; +import java.util.Map; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * This interface describes the network protocol used between lfs client and lfs + * server + * + * @since 4.11 + */ +public interface Protocol { + /** A request sent to an LFS server */ + class Request { + /** The operation of this request */ + public String operation; + + /** The objects of this request */ + public List objects; + } + + /** A response received from an LFS server */ + class Response { + public List objects; + } + + /** + * MetaData of an LFS object. Needs to be specified when requesting objects + * from the LFS server and is also returned in the response + */ + class ObjectSpec { + public String oid; // the objectid + + public long size; // the size of the object + } + + /** + * Describes in a response all actions the LFS server offers for a single + * object + */ + class ObjectInfo extends ObjectSpec { + public Map actions; // Maps operation to action + + public Error error; + } + + /** + * Describes in a Response a single action the client can execute on a + * single object + */ + class Action { + public String href; + + public Map header; + } + + /** + * An action with an additional expiration timestamp + * + * @since 4.11 + */ + class ExpiringAction extends Action { + /** + * Absolute date/time in format "yyyy-MM-dd'T'HH:mm:ss.SSSX" + */ + public String expiresAt; + + /** + * Validity time in milliseconds (preferred over expiresAt as specified: + * https://github.com/git-lfs/git-lfs/blob/master/docs/api/authentication.md) + */ + public String expiresIn; + } + + /** Describes an error to be returned by the LFS batch API */ + class Error { + public int code; + + public String message; + } + + /** + * The "download" operation + */ + String OPERATION_DOWNLOAD = "download"; //$NON-NLS-1$ + + /** + * The "upload" operation + */ + String OPERATION_UPLOAD = "upload"; //$NON-NLS-1$ + + /** + * The contenttype used in LFS requests + */ + String CONTENTTYPE_VND_GIT_LFS_JSON = "application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$ + + /** + * Authorization header when auto-discovering via SSH. + */ + String HDR_AUTH = "Authorization"; //$NON-NLS-1$ + + /** + * Prefix of authentication token obtained through SSH. + */ + String HDR_AUTH_SSH_PREFIX = "Ssh: "; //$NON-NLS-1$ + + /** + * Path to the LFS info servlet. + */ + String INFO_LFS_ENDPOINT = "/info/lfs"; //$NON-NLS-1$ + + /** + * Path to the LFS objects servlet. + */ + String OBJECTS_LFS_ENDPOINT = "/objects/batch"; //$NON-NLS-1$ + + /** + * @return a {@link Gson} instance suitable for handling this + * {@link Protocol} + * + * @since 4.11 + */ + public static Gson gson() { + return new GsonBuilder() + .setFieldNamingPolicy( + FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .disableHtmlEscaping().create(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java --- jgit-4.1.2/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandFactory; +import org.eclipse.jgit.attributes.FilterCommandRegistry; +import org.eclipse.jgit.lfs.internal.LfsConnectionFactory; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.util.HttpSupport; + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; + +/** + * Built-in LFS smudge filter + * + * When content is read from git's object-database and written to the filesystem + * and this filter is configured for that content, then this filter will replace + * the content of LFS pointer files with the original content. This happens e.g. + * when a checkout needs to update a working tree file which is under LFS + * control. + * + * @since 4.6 + */ +public class SmudgeFilter extends FilterCommand { + + /** + * Max number of bytes to copy in a single {@link #run()} call. + */ + private static final int MAX_COPY_BYTES = 1024 * 1024 * 256; + + /** + * The factory is responsible for creating instances of + * {@link org.eclipse.jgit.lfs.SmudgeFilter} + */ + public final static FilterCommandFactory FACTORY = new FilterCommandFactory() { + @Override + public FilterCommand create(Repository db, InputStream in, + OutputStream out) throws IOException { + return new SmudgeFilter(db, in, out); + } + }; + + /** + * Register this filter in JGit + */ + static void register() { + FilterCommandRegistry + .register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX + + Constants.ATTR_FILTER_DRIVER_PREFIX + + org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE, + FACTORY); + } + + /** + * Constructor for SmudgeFilter. + * + * @param db + * a {@link org.eclipse.jgit.lib.Repository} object. + * @param in + * a {@link java.io.InputStream} object. The stream is closed in + * any case. + * @param out + * a {@link java.io.OutputStream} object. + * @throws java.io.IOException + * in case of an error + */ + public SmudgeFilter(Repository db, InputStream in, OutputStream out) + throws IOException { + super(in, out); + try { + Lfs lfs = new Lfs(db); + LfsPointer res = LfsPointer.parseLfsPointer(in); + if (res != null) { + AnyLongObjectId oid = res.getOid(); + Path mediaFile = lfs.getMediaFile(oid); + if (!Files.exists(mediaFile)) { + downloadLfsResource(lfs, db, res); + } + this.in = Files.newInputStream(mediaFile); + } + } finally { + in.close(); // make sure the swapped stream is closed properly. + } + } + + /** + * Download content which is hosted on a LFS server + * + * @param lfs + * local {@link Lfs} storage. + * @param db + * the repository to work with + * @param res + * the objects to download + * @return the paths of all mediafiles which have been downloaded + * @throws IOException + * @since 4.11 + */ + public static Collection downloadLfsResource(Lfs lfs, Repository db, + LfsPointer... res) throws IOException { + Collection downloadedPaths = new ArrayList<>(); + Map oidStr2ptr = new HashMap<>(); + for (LfsPointer p : res) { + oidStr2ptr.put(p.getOid().name(), p); + } + HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db, + HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD); + Gson gson = Protocol.gson(); + lfsServerConn.getOutputStream() + .write(gson + .toJson(LfsConnectionFactory + .toRequest(Protocol.OPERATION_DOWNLOAD, res)) + .getBytes(StandardCharsets.UTF_8)); + int responseCode = lfsServerConn.getResponseCode(); + if (responseCode != HttpConnection.HTTP_OK) { + throw new IOException( + MessageFormat.format(LfsText.get().serverFailure, + lfsServerConn.getURL(), + Integer.valueOf(responseCode))); + } + try (JsonReader reader = new JsonReader( + new InputStreamReader(lfsServerConn.getInputStream()))) { + Protocol.Response resp = gson.fromJson(reader, + Protocol.Response.class); + for (Protocol.ObjectInfo o : resp.objects) { + if (o.error != null) { + throw new IOException( + MessageFormat.format(LfsText.get().protocolError, + Integer.valueOf(o.error.code), + o.error.message)); + } + if (o.actions == null) { + continue; + } + LfsPointer ptr = oidStr2ptr.get(o.oid); + if (ptr == null) { + // received an object we didn't request + continue; + } + if (ptr.getSize() != o.size) { + throw new IOException(MessageFormat.format( + LfsText.get().inconsistentContentLength, + lfsServerConn.getURL(), Long.valueOf(ptr.getSize()), + Long.valueOf(o.size))); + } + Protocol.Action downloadAction = o.actions + .get(Protocol.OPERATION_DOWNLOAD); + if (downloadAction == null || downloadAction.href == null) { + continue; + } + + HttpConnection contentServerConn = LfsConnectionFactory + .getLfsContentConnection(db, downloadAction, + HttpSupport.METHOD_GET); + + responseCode = contentServerConn.getResponseCode(); + if (responseCode != HttpConnection.HTTP_OK) { + throw new IOException( + MessageFormat.format(LfsText.get().serverFailure, + contentServerConn.getURL(), + Integer.valueOf(responseCode))); + } + Path path = lfs.getMediaFile(ptr.getOid()); + path.getParent().toFile().mkdirs(); + try (InputStream contentIn = contentServerConn + .getInputStream()) { + long bytesCopied = Files.copy(contentIn, path); + if (bytesCopied != o.size) { + throw new IOException(MessageFormat.format( + LfsText.get().wrongAmoutOfDataReceived, + contentServerConn.getURL(), + Long.valueOf(bytesCopied), + Long.valueOf(o.size))); + } + downloadedPaths.add(path); + } + } + } + return downloadedPaths; + } + + /** {@inheritDoc} */ + @Override + public int run() throws IOException { + try { + int totalRead = 0; + int length = 0; + if (in != null) { + byte[] buf = new byte[8192]; + while ((length = in.read(buf)) != -1) { + out.write(buf, 0, length); + totalRead += length; + + // when threshold reached, loop back to the caller. + // otherwise we could only support files up to 2GB (int + // return type) properly. we will be called again as long as + // we don't return -1 here. + if (totalRead >= MAX_COPY_BYTES) { + // leave streams open - we need them in the next call. + return totalRead; + } + } + } + + if (totalRead == 0 && length == -1) { + // we're totally done :) cleanup all streams + in.close(); + out.close(); + return length; + } + + return totalRead; + } catch (IOException e) { + in.close(); // clean up - we swapped this stream. + out.close(); + throw e; + } + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/about.html jgit-4.11.9/org.eclipse.jgit.lfs.server/about.html --- jgit-4.1.2/org.eclipse.jgit.lfs.server/about.html 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/about.html 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,59 @@ + + + + + + +Eclipse Distribution License - Version 1.0 + + + + + + +

        Eclipse Distribution License - v 1.0

        + +

        Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.

        + +

        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.

        + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/BUILD jgit-4.11.9/org.eclipse.jgit.lfs.server/BUILD --- jgit-4.1.2/org.eclipse.jgit.lfs.server/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,17 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "jgit-lfs-server", + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.lfs.server/resources", + resources = glob(["resources/**"]), + deps = [ + "//lib:gson", + "//lib:httpcore", + "//lib:servlet-api", + "//lib:slf4j-api", + "//org.eclipse.jgit.http.apache:http-apache", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.lfs:jgit-lfs", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/build.properties jgit-4.11.9/org.eclipse.jgit.lfs.server/build.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.server/build.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,7 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + about.html diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.classpath jgit-4.11.9/org.eclipse.jgit.lfs.server/.classpath --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.classpath 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.fbprefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.fbprefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.fbprefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.fbprefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,125 @@ +#FindBugs User Preferences +#Mon May 04 16:24:13 PDT 2009 +detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true +detectorBadAppletConstructor=BadAppletConstructor|false +detectorBadResultSetAccess=BadResultSetAccess|true +detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true +detectorBadUseOfReturnValue=BadUseOfReturnValue|true +detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true +detectorBooleanReturnNull=BooleanReturnNull|true +detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true +detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true +detectorCheckTypeQualifiers=CheckTypeQualifiers|true +detectorCloneIdiom=CloneIdiom|false +detectorComparatorIdiom=ComparatorIdiom|true +detectorConfusedInheritance=ConfusedInheritance|true +detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true +detectorCrossSiteScripting=CrossSiteScripting|true +detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true +detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true +detectorDontUseEnum=DontUseEnum|true +detectorDroppedException=DroppedException|true +detectorDumbMethodInvocations=DumbMethodInvocations|true +detectorDumbMethods=DumbMethods|true +detectorDuplicateBranches=DuplicateBranches|true +detectorEmptyZipFileEntry=EmptyZipFileEntry|true +detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true +detectorFinalizerNullsFields=FinalizerNullsFields|true +detectorFindBadCast2=FindBadCast2|true +detectorFindBadForLoop=FindBadForLoop|true +detectorFindCircularDependencies=FindCircularDependencies|false +detectorFindDeadLocalStores=FindDeadLocalStores|true +detectorFindDoubleCheck=FindDoubleCheck|true +detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true +detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true +detectorFindFinalizeInvocations=FindFinalizeInvocations|true +detectorFindFloatEquality=FindFloatEquality|true +detectorFindHEmismatch=FindHEmismatch|true +detectorFindInconsistentSync2=FindInconsistentSync2|true +detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true +detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true +detectorFindMaskedFields=FindMaskedFields|true +detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true +detectorFindNakedNotify=FindNakedNotify|true +detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true +detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true +detectorFindNonShortCircuit=FindNonShortCircuit|true +detectorFindNullDeref=FindNullDeref|true +detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true +detectorFindOpenStream=FindOpenStream|true +detectorFindPuzzlers=FindPuzzlers|true +detectorFindRefComparison=FindRefComparison|true +detectorFindReturnRef=FindReturnRef|true +detectorFindRunInvocations=FindRunInvocations|true +detectorFindSelfComparison=FindSelfComparison|true +detectorFindSelfComparison2=FindSelfComparison2|true +detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true +detectorFindSpinLoop=FindSpinLoop|true +detectorFindSqlInjection=FindSqlInjection|true +detectorFindTwoLockWait=FindTwoLockWait|true +detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true +detectorFindUnconditionalWait=FindUnconditionalWait|true +detectorFindUninitializedGet=FindUninitializedGet|true +detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true +detectorFindUnreleasedLock=FindUnreleasedLock|true +detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true +detectorFindUnsyncGet=FindUnsyncGet|true +detectorFindUselessControlFlow=FindUselessControlFlow|true +detectorFormatStringChecker=FormatStringChecker|true +detectorHugeSharedStringConstants=HugeSharedStringConstants|true +detectorIDivResultCastToDouble=IDivResultCastToDouble|true +detectorIncompatMask=IncompatMask|true +detectorInconsistentAnnotations=InconsistentAnnotations|true +detectorInefficientMemberAccess=InefficientMemberAccess|false +detectorInefficientToArray=InefficientToArray|true +detectorInfiniteLoop=InfiniteLoop|true +detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true +detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false +detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true +detectorInitializationChain=InitializationChain|true +detectorInstantiateStaticClass=InstantiateStaticClass|true +detectorInvalidJUnitTest=InvalidJUnitTest|true +detectorIteratorIdioms=IteratorIdioms|true +detectorLazyInit=LazyInit|true +detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true +detectorMethodReturnCheck=MethodReturnCheck|true +detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true +detectorMutableLock=MutableLock|true +detectorMutableStaticFields=MutableStaticFields|true +detectorNaming=Naming|true +detectorNumberConstructor=NumberConstructor|true +detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true +detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true +detectorPublicSemaphores=PublicSemaphores|false +detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true +detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true +detectorRedundantInterfaces=RedundantInterfaces|true +detectorRepeatedConditionals=RepeatedConditionals|true +detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true +detectorSerializableIdiom=SerializableIdiom|true +detectorStartInConstructor=StartInConstructor|true +detectorStaticCalendarDetector=StaticCalendarDetector|true +detectorStringConcatenation=StringConcatenation|true +detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true +detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true +detectorSwitchFallthrough=SwitchFallthrough|true +detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true +detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true +detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true +detectorURLProblems=URLProblems|true +detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true +detectorUnnecessaryMath=UnnecessaryMath|true +detectorUnreadFields=UnreadFields|true +detectorUseObjectEquals=UseObjectEquals|false +detectorUselessSubclassMethod=UselessSubclassMethod|false +detectorVarArgsProblems=VarArgsProblems|true +detectorVolatileUsage=VolatileUsage|true +detectorWaitInLoop=WaitInLoop|true +detectorWrongMapIterator=WrongMapIterator|true +detectorXMLFactoryBypass=XMLFactoryBypass|true +detector_threshold=2 +effort=default +excludefilter0=findBugs/FindBugsExcludeFilter.xml +filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false +filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| +run_at_full_build=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.gitignore jgit-4.11.9/org.eclipse.jgit.lfs.server/.gitignore --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +/bin +/target diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,38 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.lfs.server +Bundle-SymbolicName: org.eclipse.jgit.lfs.server +Bundle-Version: 4.11.9.201909030838-r +Bundle-Localization: plugin +Bundle-Vendor: %provider_name +Export-Package: org.eclipse.jgit.lfs.server;version="4.11.9"; + uses:="javax.servlet.http, + org.eclipse.jgit.lfs.lib", + org.eclipse.jgit.lfs.server.fs;version="4.11.9"; + uses:="javax.servlet, + javax.servlet.http, + org.eclipse.jgit.lfs.server, + org.eclipse.jgit.lfs.lib", + org.eclipse.jgit.lfs.server.internal;version="4.11.9";x-internal:=true, + org.eclipse.jgit.lfs.server.s3;version="4.11.9"; + uses:="org.eclipse.jgit.lfs.server, + org.eclipse.jgit.lfs.lib" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: com.google.gson;version="[2.8.0,3.0.0)", + javax.servlet;version="[3.1.0,4.0.0)", + javax.servlet.annotation;version="[3.1.0,4.0.0)", + javax.servlet.http;version="[3.1.0,4.0.0)", + org.apache.http;version="[4.3.0,5.0.0)", + org.apache.http.client;version="[4.3.0,5.0.0)", + org.eclipse.jgit.annotations;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.internal;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport.http.apache;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.slf4j;version="[1.7.0,2.0.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/plugin.properties jgit-4.11.9/org.eclipse.jgit.lfs.server/plugin.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.server/plugin.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/plugin.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +plugin_name=JGit Large File Storage Server +provider_name=Eclipse JGit diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/pom.xml jgit-4.11.9/org.eclipse.jgit.lfs.server/pom.xml --- jgit-4.1.2/org.eclipse.jgit.lfs.server/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,145 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 4.11.9.201909030838-r + + + org.eclipse.jgit.lfs.server + JGit - Large File Storage Server + + + JGit Large File Storage Server implementation. + + + + + + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.lfs + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.http.apache + ${project.version} + + + + javax.servlet + javax.servlet-api + provided + + + + com.google.code.gson + gson + + + + + src/ + + + + . + + plugin.properties + about.html + + + + resources/ + + + + + + org.apache.maven.plugins + maven-source-plugin + true + + + attach-sources + process-classes + + jar + + + + ${source-bundle-manifest} + + + + + + + + maven-jar-plugin + + + ${bundle-manifest} + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.project jgit-4.11.9/org.eclipse.jgit.lfs.server/.project --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.project 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.project 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,34 @@ + + + org.eclipse.jgit.lfs.server + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.eclipse.pde.api.tools.apiAnalysisNature + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties jgit-4.11.9/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,11 @@ +failedToCalcSignature=Failed to calculate a request signature: {0} +invalidPathInfo=Invalid pathInfo: ''{0}'' does not match ''/'{'SHA-256'}''' +objectNotFound=Object ''{0}'' not found +undefinedS3AccessKey=S3 configuration: 'accessKey' is undefined +undefinedS3Bucket=S3 configuration: 'bucket' is undefined +undefinedS3Region=S3 configuration: 'region' is undefined +undefinedS3SecretKey=S3 configuration: 'secretKey' is undefined +undefinedS3StorageClass=S3 configuration: 'storageClass' is undefined +unparsableEndpoint=Unable to parse service endpoint: {0} +unsupportedOperation=Operation ''{0}'' is not supported +unsupportedUtf8=UTF-8 encoding is not supported. diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.resources.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.resources.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.resources.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.resources.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.runtime.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.runtime.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.runtime.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.core.runtime.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,399 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +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.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +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.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.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.tasks.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.tasks.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.tasks.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.tasks.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +resolve.requirebundle=false diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collections; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lfs.internal.AtomicObjectOutputStream; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.Response; +import org.eclipse.jgit.lfs.server.Response.Action; + +/** + * Repository storing large objects in the file system + * + * @since 4.3 + */ +public class FileLfsRepository implements LargeFileRepository { + + private String url; + private final Path dir; + + /** + *

        Constructor for FileLfsRepository.

        + * + * @param url + * external URL of this repository + * @param dir + * storage directory + * @throws java.io.IOException + */ + public FileLfsRepository(String url, Path dir) throws IOException { + this.url = url; + this.dir = dir; + Files.createDirectories(dir); + } + + /** {@inheritDoc} */ + @Override + public Response.Action getDownloadAction(AnyLongObjectId id) { + return getAction(id); + } + + /** {@inheritDoc} */ + @Override + public Action getUploadAction(AnyLongObjectId id, long size) { + return getAction(id); + } + + /** {@inheritDoc} */ + @Override + public @Nullable Action getVerifyAction(AnyLongObjectId id) { + return null; + } + + /** {@inheritDoc} */ + @Override + public long getSize(AnyLongObjectId id) throws IOException { + Path p = getPath(id); + if (Files.exists(p)) { + return Files.size(p); + } else { + return -1; + } + } + + /** + * Get the storage directory + * + * @return the path of the storage directory + */ + public Path getDir() { + return dir; + } + + /** + * Get the path where the given object is stored + * + * @param id + * id of a large object + * @return path the object's storage path + */ + protected Path getPath(AnyLongObjectId id) { + StringBuilder s = new StringBuilder( + Constants.LONG_OBJECT_ID_STRING_LENGTH + 6); + s.append(toHexCharArray(id.getFirstByte())).append('/'); + s.append(toHexCharArray(id.getSecondByte())).append('/'); + s.append(id.name()); + return dir.resolve(s.toString()); + } + + private Response.Action getAction(AnyLongObjectId id) { + Response.Action a = new Response.Action(); + a.href = url + id.getName(); + a.header = Collections.singletonMap(HDR_AUTHORIZATION, "not:required"); //$NON-NLS-1$ + return a; + } + + ReadableByteChannel getReadChannel(AnyLongObjectId id) + throws IOException { + return FileChannel.open(getPath(id), StandardOpenOption.READ); + } + + AtomicObjectOutputStream getOutputStream(AnyLongObjectId id) + throws IOException { + Path path = getPath(id); + Path parent = path.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + return new AtomicObjectOutputStream(path, id); + } + + private static char[] toHexCharArray(int b) { + final char[] dst = new char[2]; + formatHexChar(dst, 0, b); + return dst; + } + + private static final char[] hexchar = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + private static void formatHexChar(final char[] dst, final int p, int b) { + int o = p + 1; + while (o >= p && b != 0) { + dst[o--] = hexchar[b & 0xf]; + b >>>= 4; + } + while (o >= p) + dst[o--] = '0'; + } + + /** + * @return the url of the content server + * @since 4.11 + */ + public String getUrl() { + return url; + } + + /** + * @param url + * the url of the content server + * @since 4.11 + */ + public void setUrl(String url) { + this.url = url; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import java.io.IOException; +import java.io.PrintWriter; +import java.text.MessageFormat; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.HttpStatus; +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lfs.server.internal.LfsGson; +import org.eclipse.jgit.lfs.server.internal.LfsServerText; + +/** + * Servlet supporting upload and download of large objects as defined by the + * GitHub Large File Storage extension API extending git to allow separate + * storage of large files + * (https://github.com/github/git-lfs/tree/master/docs/api). + * + * @since 4.3 + */ +@WebServlet(asyncSupported = true) +public class FileLfsServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final FileLfsRepository repository; + + private final long timeout; + + /** + *

        Constructor for FileLfsServlet.

        + * + * @param repository + * the repository storing the large objects + * @param timeout + * timeout for object upload / download in milliseconds + */ + public FileLfsServlet(FileLfsRepository repository, long timeout) { + this.repository = repository; + this.timeout = timeout; + } + + /** + * {@inheritDoc} + * + * Handle object downloads + */ + @Override + protected void doGet(HttpServletRequest req, + HttpServletResponse rsp) throws ServletException, IOException { + AnyLongObjectId obj = getObjectToTransfer(req, rsp); + if (obj != null) { + if (repository.getSize(obj) == -1) { + sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat + .format(LfsServerText.get().objectNotFound, + obj.getName())); + return; + } + AsyncContext context = req.startAsync(); + context.setTimeout(timeout); + rsp.getOutputStream() + .setWriteListener(new ObjectDownloadListener(repository, + context, rsp, obj)); + } + } + + /** + * Retrieve object id from request + * + * @param req + * servlet request + * @param rsp + * servlet response + * @return object id, or null if the object id could not be + * retrieved + * @throws java.io.IOException + * if an I/O error occurs + * @since 4.6 + */ + protected AnyLongObjectId getObjectToTransfer(HttpServletRequest req, + HttpServletResponse rsp) throws IOException { + String info = req.getPathInfo(); + int length = 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH; + if (info.length() != length) { + sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, MessageFormat + .format(LfsServerText.get().invalidPathInfo, info)); + return null; + } + try { + return LongObjectId.fromString(info.substring(1, length)); + } catch (InvalidLongObjectIdException e) { + sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, e.getMessage()); + return null; + } + } + + /** + * {@inheritDoc} + * + * Handle object uploads + */ + @Override + protected void doPut(HttpServletRequest req, + HttpServletResponse rsp) throws ServletException, IOException { + AnyLongObjectId id = getObjectToTransfer(req, rsp); + if (id != null) { + AsyncContext context = req.startAsync(); + context.setTimeout(timeout); + req.getInputStream().setReadListener(new ObjectUploadListener( + repository, context, req, rsp, id)); + } + } + + /** + * Send an error response. + * + * @param rsp + * the servlet response + * @param status + * HTTP status code + * @param message + * error message + * @throws java.io.IOException + * on failure to send the response + * @since 4.6 + */ + protected static void sendError(HttpServletResponse rsp, int status, String message) + throws IOException { + if (rsp.isCommitted()) { + rsp.getOutputStream().close(); + return; + } + rsp.reset(); + rsp.setStatus(status); + try (PrintWriter writer = rsp.getWriter()) { + LfsGson.toJson(message, writer); + writer.flush(); + } + rsp.flushBuffer(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.HttpStatus; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.util.HttpSupport; + +/** + * Handle asynchronous large object download. + * + * @since 4.7 + */ +public class ObjectDownloadListener implements WriteListener { + + private static Logger LOG = Logger + .getLogger(ObjectDownloadListener.class.getName()); + + private final AsyncContext context; + + private final HttpServletResponse response; + + private final ServletOutputStream out; + + private final ReadableByteChannel in; + + private final WritableByteChannel outChannel; + + private ByteBuffer buffer = ByteBuffer.allocateDirect(8192); + + /** + *

        Constructor for ObjectDownloadListener.

        + * + * @param repository + * the repository storing large objects + * @param context + * the servlet asynchronous context + * @param response + * the servlet response + * @param id + * id of the object to be downloaded + * @throws java.io.IOException + */ + public ObjectDownloadListener(FileLfsRepository repository, + AsyncContext context, HttpServletResponse response, + AnyLongObjectId id) throws IOException { + this.context = context; + this.response = response; + this.in = repository.getReadChannel(id); + this.out = response.getOutputStream(); + this.outChannel = Channels.newChannel(out); + + response.addHeader(HttpSupport.HDR_CONTENT_LENGTH, + String.valueOf(repository.getSize(id))); + response.setContentType(Constants.HDR_APPLICATION_OCTET_STREAM); + } + + /** + * {@inheritDoc} + * + * Write file content + */ + @Override + public void onWritePossible() throws IOException { + while (out.isReady()) { + try { + buffer.clear(); + if (in.read(buffer) < 0) { + buffer = null; + } else { + buffer.flip(); + } + } catch (Throwable t) { + LOG.log(Level.SEVERE, t.getMessage(), t); + buffer = null; + } finally { + if (buffer != null) { + outChannel.write(buffer); + } else { + try { + in.close(); + } catch (IOException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + } + try { + out.close(); + } finally { + context.complete(); + } + // This is need to avoid endless loop in recent Jetty versions. + // That's because out.isReady() is returning true for already + // closed streams and because out.close() doesn't throw any + // exception any more when trying to close already closed stream. + return; + } + } + } + } + + /** + * {@inheritDoc} + * + * Handle errors + */ + @Override + public void onError(Throwable e) { + try { + FileLfsServlet.sendError(response, + HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + context.complete(); + in.close(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.AsyncContext; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.HttpStatus; +import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; +import org.eclipse.jgit.lfs.internal.AtomicObjectOutputStream; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; + +/** + * Handle asynchronous object upload. + * + * @since 4.6 + */ +public class ObjectUploadListener implements ReadListener { + + private static Logger LOG = Logger + .getLogger(ObjectUploadListener.class.getName()); + + private final AsyncContext context; + + private final HttpServletResponse response; + + private final ServletInputStream in; + + private final ReadableByteChannel inChannel; + + private final AtomicObjectOutputStream out; + + private WritableByteChannel channel; + + private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192); + + /** + * Constructor for ObjectUploadListener. + * + * @param repository + * the repository storing large objects + * @param context + * a {@link javax.servlet.AsyncContext} object. + * @param request + * a {@link javax.servlet.http.HttpServletRequest} object. + * @param response + * a {@link javax.servlet.http.HttpServletResponse} object. + * @param id + * a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object. + * @throws java.io.FileNotFoundException + * @throws java.io.IOException + */ + public ObjectUploadListener(FileLfsRepository repository, + AsyncContext context, HttpServletRequest request, + HttpServletResponse response, AnyLongObjectId id) + throws FileNotFoundException, IOException { + this.context = context; + this.response = response; + this.in = request.getInputStream(); + this.inChannel = Channels.newChannel(in); + this.out = repository.getOutputStream(id); + this.channel = Channels.newChannel(out); + response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON); + } + + /** + * {@inheritDoc} + * + * Writes all the received data to the output channel + */ + @Override + public void onDataAvailable() throws IOException { + while (in.isReady()) { + if (inChannel.read(buffer) > 0) { + buffer.flip(); + channel.write(buffer); + buffer.compact(); + } else { + buffer.flip(); + while (buffer.hasRemaining()) { + channel.write(buffer); + } + close(); + return; + } + } + } + + /** {@inheritDoc} */ + @Override + public void onAllDataRead() throws IOException { + close(); + } + + /** + * Close resources held by this listener + * + * @throws java.io.IOException + */ + protected void close() throws IOException { + try { + inChannel.close(); + channel.close(); + // TODO check if status 200 is ok for PUT request, HTTP foresees 204 + // for successful PUT without response body + if (!response.isCommitted()) { + response.setStatus(HttpServletResponse.SC_OK); + } + } finally { + context.complete(); + } + } + + /** {@inheritDoc} */ + @Override + public void onError(Throwable e) { + try { + out.abort(); + inChannel.close(); + channel.close(); + int status; + if (e instanceof CorruptLongObjectException) { + status = HttpStatus.SC_BAD_REQUEST; + LOG.log(Level.WARNING, e.getMessage(), e); + } else { + status = HttpStatus.SC_INTERNAL_SERVER_ERROR; + LOG.log(Level.SEVERE, e.getMessage(), e); + } + FileLfsServlet.sendError(response, status, e.getMessage()); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsGson.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsGson.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsGson.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsGson.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017, David Pursehouse + * 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.lfs.server.internal; + +import java.io.Reader; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +/** + * Wrapper for {@link com.google.gson.Gson} used by LFS servlets. + * + * @since 4.10.0 + */ +public class LfsGson { + private static final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .disableHtmlEscaping() + .create(); + + /** + * Wrapper class only used for serialization of error messages. + */ + static class Error { + String message; + + Error(String m) { + this.message = m; + } + } + + /** + * Serializes the specified object into its equivalent Json representation. + * + * @param src + * the object for which Json representation is to be created. If + * this is a String, it is wrapped in an instance of + * {@link org.eclipse.jgit.lfs.server.internal.LfsGson.Error}. + * @param writer + * Writer to which the Json representation needs to be written + * @throws com.google.gson.JsonIOException + * if there was a problem writing to the writer + * @see Gson#toJson(Object, Appendable) + */ + public static void toJson(Object src, Appendable writer) + throws JsonIOException { + if (src instanceof String) { + gson.toJson(new Error((String) src), writer); + } else { + gson.toJson(src, writer); + } + } + + /** + * Deserializes the Json read from the specified reader into an object of + * the specified type. + * + * @param json + * reader producing json from which the object is to be + * deserialized + * @param classOfT + * specified type to deserialize + * @return an Object of type T + * @throws com.google.gson.JsonIOException + * if there was a problem reading from the Reader + * @throws com.google.gson.JsonSyntaxException + * if json is not a valid representation for an object of type + * @see Gson#fromJson(Reader, java.lang.reflect.Type) + * @param + * a T object. + */ + public static T fromJson(Reader json, Class classOfT) + throws JsonSyntaxException, JsonIOException { + return gson.fromJson(json, classOfT); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.internal; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Translation bundle for JGit LFS server + */ +public class LfsServerText extends TranslationBundle { + + /** + * Get an instance of this translation bundle + * + * @return an instance of this translation bundle + */ + public static LfsServerText get() { + return NLS.getBundleFor(LfsServerText.class); + } + + // @formatter:off + /***/ public String failedToCalcSignature; + /***/ public String invalidPathInfo; + /***/ public String objectNotFound; + /***/ public String undefinedS3AccessKey; + /***/ public String undefinedS3Bucket; + /***/ public String undefinedS3Region; + /***/ public String undefinedS3SecretKey; + /***/ public String undefinedS3StorageClass; + /***/ public String unparsableEndpoint; + /***/ public String unsupportedOperation; + /***/ public String unsupportedUtf8; +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; + +/** + * Abstraction of a repository for storing large objects + * + * @since 4.3 + */ +public interface LargeFileRepository { + + /** + * Get download action + * + * @param id + * id of the object to download + * @return Action for downloading the object + */ + public Response.Action getDownloadAction(AnyLongObjectId id); + + /** + * Get upload action + * + * @param id + * id of the object to upload + * @param size + * size of the object to be uploaded + * @return Action for uploading the object + */ + public Response.Action getUploadAction(AnyLongObjectId id, long size); + + /** + * Get verify action + * + * @param id + * id of the object to be verified + * @return Action for verifying the object, or {@code null} if the server + * doesn't support or require verification + */ + public @Nullable Response.Action getVerifyAction(AnyLongObjectId id); + + /** + * Get size of an object + * + * @param id + * id of the object + * @return length of the object content in bytes, -1 if the object doesn't + * exist + * @throws java.io.IOException + */ + public long getSize(AnyLongObjectId id) throws IOException; +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server; + +/** + * LFS object. + * + * @since 4.5 + */ +public class LfsObject { + String oid; + long size; + + /** + * Get the oid of this object. + * + * @return the object ID. + */ + public String getOid() { + return oid; + } + + /** + * Get the size of this object. + * + * @return the object size. + */ + public long getSize() { + return size; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.http.HttpStatus.SC_FORBIDDEN; +import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE; +import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.apache.http.HttpStatus.SC_NOT_FOUND; +import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; +import static org.eclipse.jgit.lfs.lib.Constants.DOWNLOAD; +import static org.eclipse.jgit.lfs.lib.Constants.UPLOAD; +import static org.eclipse.jgit.lfs.lib.Constants.VERIFY; +import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.text.MessageFormat; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded; +import org.eclipse.jgit.lfs.errors.LfsException; +import org.eclipse.jgit.lfs.errors.LfsInsufficientStorage; +import org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded; +import org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound; +import org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly; +import org.eclipse.jgit.lfs.errors.LfsUnauthorized; +import org.eclipse.jgit.lfs.errors.LfsUnavailable; +import org.eclipse.jgit.lfs.errors.LfsValidationError; +import org.eclipse.jgit.lfs.internal.LfsText; +import org.eclipse.jgit.lfs.server.internal.LfsGson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * LFS protocol handler implementing the LFS batch API [1] + * + * [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md + * + * @since 4.3 + */ +public abstract class LfsProtocolServlet extends HttpServlet { + private static Logger LOG = LoggerFactory + .getLogger(LfsProtocolServlet.class); + + private static final long serialVersionUID = 1L; + + private static final String CONTENTTYPE_VND_GIT_LFS_JSON = + "application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$ + + private static final int SC_RATE_LIMIT_EXCEEDED = 429; + + private static final int SC_BANDWIDTH_LIMIT_EXCEEDED = 509; + + /** + * Get the large file repository for the given request and path. + * + * @param request + * the request + * @param path + * the path + * @return the large file repository storing large files. + * @throws org.eclipse.jgit.lfs.errors.LfsException + * implementations should throw more specific exceptions to + * signal which type of error occurred: + *
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsValidationError}
        + *
        when there is a validation error with one or more of the + * objects in the request
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}
        + *
        when the repository does not exist for the user
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}
        + *
        when the user has read, but not write access. Only + * applicable when the operation in the request is "upload"
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}
        + *
        when the user has hit a rate limit with the server
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}
        + *
        when the bandwidth limit for the user or repository has + * been exceeded
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}
        + *
        when there is insufficient storage on the server
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsUnavailable}
        + *
        when LFS is not available
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsException}
        + *
        when an unexpected internal server error occurred
        + *
        + * @since 4.5 + * @deprecated use + * {@link #getLargeFileRepository(LfsRequest, String, String)} + */ + @Deprecated + protected LargeFileRepository getLargeFileRepository(LfsRequest request, + String path) throws LfsException { + return getLargeFileRepository(request, path, null); + } + + /** + * Get the large file repository for the given request and path. + * + * @param request + * the request + * @param path + * the path + * @param auth + * the Authorization HTTP header + * @return the large file repository storing large files. + * @throws org.eclipse.jgit.lfs.errors.LfsException + * implementations should throw more specific exceptions to + * signal which type of error occurred: + *
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsValidationError}
        + *
        when there is a validation error with one or more of the + * objects in the request
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}
        + *
        when the repository does not exist for the user
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}
        + *
        when the user has read, but not write access. Only + * applicable when the operation in the request is "upload"
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}
        + *
        when the user has hit a rate limit with the server
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}
        + *
        when the bandwidth limit for the user or repository has + * been exceeded
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}
        + *
        when there is insufficient storage on the server
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsUnavailable}
        + *
        when LFS is not available
        + *
        {@link org.eclipse.jgit.lfs.errors.LfsException}
        + *
        when an unexpected internal server error occurred
        + *
        + * @since 4.7 + */ + protected abstract LargeFileRepository getLargeFileRepository( + LfsRequest request, String path, String auth) throws LfsException; + + /** + * LFS request. + * + * @since 4.5 + */ + protected static class LfsRequest { + private String operation; + + private List objects; + + /** + * Get the LFS operation. + * + * @return the operation + */ + public String getOperation() { + return operation; + } + + /** + * Get the LFS objects. + * + * @return the objects + */ + public List getObjects() { + return objects; + } + + /** + * @return true if the operation is upload. + * @since 4.7 + */ + public boolean isUpload() { + return operation.equals(UPLOAD); + } + + /** + * @return true if the operation is download. + * @since 4.7 + */ + public boolean isDownload() { + return operation.equals(DOWNLOAD); + } + + /** + * @return true if the operation is verify. + * @since 4.7 + */ + public boolean isVerify() { + return operation.equals(VERIFY); + } + } + + /** {@inheritDoc} */ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + Writer w = new BufferedWriter( + new OutputStreamWriter(res.getOutputStream(), UTF_8)); + + Reader r = new BufferedReader( + new InputStreamReader(req.getInputStream(), UTF_8)); + LfsRequest request = LfsGson.fromJson(r, LfsRequest.class); + String path = req.getPathInfo(); + + res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON); + LargeFileRepository repo = null; + try { + repo = getLargeFileRepository(request, path, + req.getHeader(HDR_AUTHORIZATION)); + if (repo == null) { + String error = MessageFormat + .format(LfsText.get().lfsFailedToGetRepository, path); + LOG.error(error); + throw new LfsException(error); + } + res.setStatus(SC_OK); + TransferHandler handler = TransferHandler + .forOperation(request.operation, repo, request.objects); + LfsGson.toJson(handler.process(), w); + } catch (LfsValidationError e) { + sendError(res, w, SC_UNPROCESSABLE_ENTITY, e.getMessage()); + } catch (LfsRepositoryNotFound e) { + sendError(res, w, SC_NOT_FOUND, e.getMessage()); + } catch (LfsRepositoryReadOnly e) { + sendError(res, w, SC_FORBIDDEN, e.getMessage()); + } catch (LfsRateLimitExceeded e) { + sendError(res, w, SC_RATE_LIMIT_EXCEEDED, e.getMessage()); + } catch (LfsBandwidthLimitExceeded e) { + sendError(res, w, SC_BANDWIDTH_LIMIT_EXCEEDED, e.getMessage()); + } catch (LfsInsufficientStorage e) { + sendError(res, w, SC_INSUFFICIENT_STORAGE, e.getMessage()); + } catch (LfsUnavailable e) { + sendError(res, w, SC_SERVICE_UNAVAILABLE, e.getMessage()); + } catch (LfsUnauthorized e) { + sendError(res, w, SC_UNAUTHORIZED, e.getMessage()); + } catch (LfsException e) { + sendError(res, w, SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } finally { + w.flush(); + } + } + + private void sendError(HttpServletResponse rsp, Writer writer, int status, + String message) { + rsp.setStatus(status); + LfsGson.toJson(message, writer); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server; + +import java.util.List; +import java.util.Map; + +/** + * POJOs for Gson serialization/de-serialization. + * + * See the LFS + * API specification + * + * @since 4.3 + */ +public interface Response { + /** Describes an action the client can execute on a single object */ + class Action { + public String href; + public Map header; + } + + /** Describes an error to be returned by the LFS batch API */ + class Error { + public int code; + public String message; + } + + /** Describes the actions the LFS server offers for a single object */ + class ObjectInfo { + public String oid; + public long size; + public Map actions; + public Error error; + } + + /** Describes the body of a LFS batch API response */ + class Body { + public List objects; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Config.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Config.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Config.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Config.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server.s3; + +/** + * Configuration for an Amazon AWS S3 bucket + * + * @since 4.3 + */ +public class S3Config { + private final String region; + private final String bucket; + private final String storageClass; + private final String accessKey; + private final String secretKey; + private final int expirationSeconds; + private final boolean disableSslVerify; + + /** + *

        Constructor for S3Config.

        + * + * @param region + * AWS region + * @param bucket + * S3 storage bucket + * @param storageClass + * S3 storage class + * @param accessKey + * access key for authenticating to AWS + * @param secretKey + * secret key for authenticating to AWS + * @param expirationSeconds + * period in seconds after which requests signed for this bucket + * will expire + * @param disableSslVerify + * if {@code true} disable Amazon server certificate and hostname + * verification + */ + public S3Config(String region, String bucket, String storageClass, + String accessKey, String secretKey, int expirationSeconds, + boolean disableSslVerify) { + this.region = region; + this.bucket = bucket; + this.storageClass = storageClass; + this.accessKey = accessKey; + this.secretKey = secretKey; + this.expirationSeconds = expirationSeconds; + this.disableSslVerify = disableSslVerify; + } + + /** + * Get the region. + * + * @return Get name of AWS region this bucket resides in + */ + public String getRegion() { + return region; + } + + /** + * Get the bucket. + * + * @return Get S3 storage bucket name + */ + public String getBucket() { + return bucket; + } + + /** + * Get the storageClass. + * + * @return S3 storage class to use for objects stored in this bucket + */ + public String getStorageClass() { + return storageClass; + } + + /** + * Get the accessKey. + * + * @return access key for authenticating to AWS + */ + public String getAccessKey() { + return accessKey; + } + + /** + * Get the secretKey. + * + * @return secret key for authenticating to AWS + */ + public String getSecretKey() { + return secretKey; + } + + /** + * Get the expirationSeconds. + * + * @return period in seconds after which requests signed for this bucket + * will expire + */ + public int getExpirationSeconds() { + return expirationSeconds; + } + + /** + * @return {@code true} if Amazon server certificate and hostname + * verification is disabled + */ + boolean isDisableSslVerify() { + return disableSslVerify; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server.s3; + +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.eclipse.jgit.lfs.server.s3.SignerV4.UNSIGNED_PAYLOAD; +import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_CONTENT_SHA256; +import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_EXPIRES; +import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_STORAGE_CLASS; +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH; +import static org.eclipse.jgit.util.HttpSupport.METHOD_GET; +import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD; +import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URL; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.Response; +import org.eclipse.jgit.lfs.server.Response.Action; +import org.eclipse.jgit.lfs.server.internal.LfsServerText; +import org.eclipse.jgit.transport.http.HttpConnection; +import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; +import org.eclipse.jgit.util.HttpSupport; + +/** + * Repository storing LFS objects in Amazon S3 + * + * @since 4.3 + */ +public class S3Repository implements LargeFileRepository { + + private S3Config s3Config; + + /** + * Construct a LFS repository storing large objects in Amazon S3 + * + * @param config + * AWS S3 storage bucket configuration + */ + public S3Repository(S3Config config) { + validateConfig(config); + this.s3Config = config; + } + + /** {@inheritDoc} */ + @Override + public Response.Action getDownloadAction(AnyLongObjectId oid) { + URL endpointUrl = getObjectUrl(oid); + Map queryParams = new HashMap<>(); + queryParams.put(X_AMZ_EXPIRES, + Integer.toString(s3Config.getExpirationSeconds())); + Map headers = new HashMap<>(); + String authorizationQueryParameters = SignerV4.createAuthorizationQuery( + s3Config, endpointUrl, METHOD_GET, headers, queryParams, + UNSIGNED_PAYLOAD); + + Response.Action a = new Response.Action(); + a.href = endpointUrl.toString() + "?" + authorizationQueryParameters; //$NON-NLS-1$ + return a; + } + + /** {@inheritDoc} */ + @Override + public Response.Action getUploadAction(AnyLongObjectId oid, long size) { + cacheObjectMetaData(oid, size); + URL objectUrl = getObjectUrl(oid); + Map headers = new HashMap<>(); + headers.put(X_AMZ_CONTENT_SHA256, oid.getName()); + headers.put(HDR_CONTENT_LENGTH, Long.toString(size)); + headers.put(X_AMZ_STORAGE_CLASS, s3Config.getStorageClass()); + headers.put(HttpSupport.HDR_CONTENT_TYPE, "application/octet-stream"); //$NON-NLS-1$ + headers = SignerV4.createHeaderAuthorization(s3Config, objectUrl, + METHOD_PUT, headers, oid.getName()); + + Response.Action a = new Response.Action(); + a.href = objectUrl.toString(); + a.header = new HashMap<>(); + a.header.putAll(headers); + return a; + } + + /** {@inheritDoc} */ + @Override + public Action getVerifyAction(AnyLongObjectId id) { + return null; // TODO(ms) implement this + } + + /** {@inheritDoc} */ + @Override + public long getSize(AnyLongObjectId oid) throws IOException { + URL endpointUrl = getObjectUrl(oid); + Map queryParams = new HashMap<>(); + queryParams.put(X_AMZ_EXPIRES, + Integer.toString(s3Config.getExpirationSeconds())); + Map headers = new HashMap<>(); + + String authorizationQueryParameters = SignerV4.createAuthorizationQuery( + s3Config, endpointUrl, METHOD_HEAD, headers, queryParams, + UNSIGNED_PAYLOAD); + String href = endpointUrl.toString() + "?" //$NON-NLS-1$ + + authorizationQueryParameters; + + Proxy proxy = HttpSupport.proxyFor(ProxySelector.getDefault(), + endpointUrl); + HttpClientConnectionFactory f = new HttpClientConnectionFactory(); + HttpConnection conn = f.create(new URL(href), proxy); + if (s3Config.isDisableSslVerify()) { + HttpSupport.disableSslVerify(conn); + } + conn.setRequestMethod(METHOD_HEAD); + conn.connect(); + int status = conn.getResponseCode(); + if (status == SC_OK) { + String contentLengthHeader = conn + .getHeaderField(HDR_CONTENT_LENGTH); + if (contentLengthHeader != null) { + return Integer.parseInt(contentLengthHeader); + } + } + return -1; + } + + /** + * Cache metadata (size) for an object to avoid extra roundtrip to S3 in + * order to retrieve this metadata for a given object. Subclasses can + * implement a local cache and override {{@link #getSize(AnyLongObjectId)} + * to retrieve the object size from the local cache to eliminate the need + * for another roundtrip to S3 + * + * @param oid + * the object id identifying the object to be cached + * @param size + * the object's size (in bytes) + */ + protected void cacheObjectMetaData(AnyLongObjectId oid, long size) { + // no caching + } + + private void validateConfig(S3Config config) { + assertNotEmpty(LfsServerText.get().undefinedS3AccessKey, + config.getAccessKey()); + assertNotEmpty(LfsServerText.get().undefinedS3Bucket, + config.getBucket()); + assertNotEmpty(LfsServerText.get().undefinedS3Region, + config.getRegion()); + assertNotEmpty(LfsServerText.get().undefinedS3SecretKey, + config.getSecretKey()); + assertNotEmpty(LfsServerText.get().undefinedS3StorageClass, + config.getStorageClass()); + } + + private void assertNotEmpty(String message, String value) { + if (value == null || value.trim().length() == 0) { + throw new IllegalArgumentException(message); + } + } + + private URL getObjectUrl(AnyLongObjectId oid) { + try { + return new URL(String.format("https://s3-%s.amazonaws.com/%s/%s", //$NON-NLS-1$ + s3Config.getRegion(), s3Config.getBucket(), + getPath(oid))); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(MessageFormat.format( + LfsServerText.get().unparsableEndpoint, e.getMessage())); + } + } + + private String getPath(AnyLongObjectId oid) { + return oid.getName(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server.s3; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; + +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.server.internal.LfsServerText; + +/** + * Signing support for Amazon AWS signing V4 + *

        + * See + * http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html + */ +class SignerV4 { + static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; //$NON-NLS-1$ + + private static final String ALGORITHM = "HMAC-SHA256"; //$NON-NLS-1$ + private static final String DATE_STRING_FORMAT = "yyyyMMdd"; //$NON-NLS-1$ + private static final String HEX = "0123456789abcdef"; //$NON-NLS-1$ + private static final String HMACSHA256 = "HmacSHA256"; //$NON-NLS-1$ + private static final String ISO8601_BASIC_FORMAT = "yyyyMMdd'T'HHmmss'Z'"; //$NON-NLS-1$ + private static final String S3 = "s3"; //$NON-NLS-1$ + private static final String SCHEME = "AWS4"; //$NON-NLS-1$ + private static final String TERMINATOR = "aws4_request"; //$NON-NLS-1$ + private static final String UTC = "UTC"; //$NON-NLS-1$ + private static final String X_AMZ_ALGORITHM = "X-Amz-Algorithm"; //$NON-NLS-1$ + private static final String X_AMZ_CREDENTIAL = "X-Amz-Credential"; //$NON-NLS-1$ + private static final String X_AMZ_DATE = "X-Amz-Date"; //$NON-NLS-1$ + private static final String X_AMZ_SIGNATURE = "X-Amz-Signature"; //$NON-NLS-1$ + private static final String X_AMZ_SIGNED_HEADERS = "X-Amz-SignedHeaders"; //$NON-NLS-1$ + + static final String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; //$NON-NLS-1$ + static final String X_AMZ_EXPIRES = "X-Amz-Expires"; //$NON-NLS-1$ + static final String X_AMZ_STORAGE_CLASS = "x-amz-storage-class"; //$NON-NLS-1$ + + /** + * Create an AWSV4 authorization for a request, suitable for embedding in + * query parameters. + * + * @param bucketConfig + * configuration of S3 storage bucket this request should be + * signed for + * @param url + * HTTP request URL + * @param httpMethod + * HTTP method + * @param headers + * The HTTP request headers; 'Host' and 'X-Amz-Date' will be + * added to this set. + * @param queryParameters + * Any query parameters that will be added to the endpoint. The + * parameters should be specified in canonical format. + * @param bodyHash + * Pre-computed SHA256 hash of the request body content; this + * value should also be set as the header 'X-Amz-Content-SHA256' + * for non-streaming uploads. + * @return The computed authorization string for the request. This value + * needs to be set as the header 'Authorization' on the subsequent + * HTTP request. + */ + static String createAuthorizationQuery(S3Config bucketConfig, URL url, + String httpMethod, Map headers, + Map queryParameters, String bodyHash) { + addHostHeader(url, headers); + + queryParameters.put(X_AMZ_ALGORITHM, SCHEME + "-" + ALGORITHM); //$NON-NLS-1$ + + Date now = new Date(); + String dateStamp = dateStamp(now); + String scope = scope(bucketConfig.getRegion(), dateStamp); + queryParameters.put(X_AMZ_CREDENTIAL, + bucketConfig.getAccessKey() + "/" + scope); //$NON-NLS-1$ + + String dateTimeStampISO8601 = dateTimeStampISO8601(now); + queryParameters.put(X_AMZ_DATE, dateTimeStampISO8601); + + String canonicalizedHeaderNames = canonicalizeHeaderNames(headers); + queryParameters.put(X_AMZ_SIGNED_HEADERS, canonicalizedHeaderNames); + + String canonicalizedQueryParameters = canonicalizeQueryString( + queryParameters); + String canonicalizedHeaders = canonicalizeHeaderString(headers); + String canonicalRequest = canonicalRequest(url, httpMethod, + canonicalizedQueryParameters, canonicalizedHeaderNames, + canonicalizedHeaders, bodyHash); + byte[] signature = createSignature(bucketConfig, dateTimeStampISO8601, + dateStamp, scope, canonicalRequest); + queryParameters.put(X_AMZ_SIGNATURE, toHex(signature)); + + return formatAuthorizationQuery(queryParameters); + } + + private static String formatAuthorizationQuery( + Map queryParameters) { + StringBuilder s = new StringBuilder(); + for (String key : queryParameters.keySet()) { + appendQuery(s, key, queryParameters.get(key)); + } + return s.toString(); + } + + private static void appendQuery(StringBuilder s, String key, + String value) { + if (s.length() != 0) { + s.append("&"); //$NON-NLS-1$ + } + s.append(key).append("=").append(value); //$NON-NLS-1$ + } + + /** + * Sign headers for given bucket, url and HTTP method and add signature in + * Authorization header. + * + * @param bucketConfig + * configuration of S3 storage bucket this request should be + * signed for + * @param url + * HTTP request URL + * @param httpMethod + * HTTP method + * @param headers + * HTTP headers to sign + * @param bodyHash + * Pre-computed SHA256 hash of the request body content; this + * value should also be set as the header 'X-Amz-Content-SHA256' + * for non-streaming uploads. + * @return HTTP headers signd by an Authorization header added to the + * headers + */ + static Map createHeaderAuthorization( + S3Config bucketConfig, URL url, String httpMethod, + Map headers, String bodyHash) { + addHostHeader(url, headers); + + Date now = new Date(); + String dateTimeStamp = dateTimeStampISO8601(now); + headers.put(X_AMZ_DATE, dateTimeStamp); + + String canonicalizedHeaderNames = canonicalizeHeaderNames(headers); + String canonicalizedHeaders = canonicalizeHeaderString(headers); + String canonicalRequest = canonicalRequest(url, httpMethod, "", //$NON-NLS-1$ + canonicalizedHeaderNames, canonicalizedHeaders, bodyHash); + String dateStamp = dateStamp(now); + String scope = scope(bucketConfig.getRegion(), dateStamp); + + byte[] signature = createSignature(bucketConfig, dateTimeStamp, + dateStamp, scope, canonicalRequest); + + headers.put(HDR_AUTHORIZATION, formatAuthorizationHeader(bucketConfig, + canonicalizedHeaderNames, scope, signature)); // $NON-NLS-1$ + + return headers; + } + + private static String formatAuthorizationHeader( + S3Config bucketConfig, String canonicalizedHeaderNames, + String scope, byte[] signature) { + StringBuilder s = new StringBuilder(); + s.append(SCHEME).append("-").append(ALGORITHM).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ + s.append("Credential=").append(bucketConfig.getAccessKey()).append("/") //$NON-NLS-1$//$NON-NLS-2$ + .append(scope).append(","); //$NON-NLS-1$ + s.append("SignedHeaders=").append(canonicalizedHeaderNames).append(","); //$NON-NLS-1$ //$NON-NLS-2$ + s.append("Signature=").append(toHex(signature)); //$NON-NLS-1$ + return s.toString(); + } + + private static void addHostHeader(URL url, + Map headers) { + StringBuilder hostHeader = new StringBuilder(url.getHost()); + int port = url.getPort(); + if (port > -1) { + hostHeader.append(":").append(port); //$NON-NLS-1$ + } + headers.put("Host", hostHeader.toString()); //$NON-NLS-1$ + } + + private static String canonicalizeHeaderNames( + Map headers) { + List sortedHeaders = new ArrayList<>(); + sortedHeaders.addAll(headers.keySet()); + Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER); + + StringBuilder buffer = new StringBuilder(); + for (String header : sortedHeaders) { + if (buffer.length() > 0) + buffer.append(";"); //$NON-NLS-1$ + buffer.append(header.toLowerCase(Locale.ROOT)); + } + + return buffer.toString(); + } + + private static String canonicalizeHeaderString( + Map headers) { + if (headers == null || headers.isEmpty()) { + return ""; //$NON-NLS-1$ + } + + List sortedHeaders = new ArrayList<>(); + sortedHeaders.addAll(headers.keySet()); + Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER); + + StringBuilder buffer = new StringBuilder(); + for (String key : sortedHeaders) { + buffer.append( + key.toLowerCase(Locale.ROOT).replaceAll("\\s+", " ") + ":" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + headers.get(key).replaceAll("\\s+", " ")); //$NON-NLS-1$//$NON-NLS-2$ + buffer.append("\n"); //$NON-NLS-1$ + } + + return buffer.toString(); + } + + private static String dateStamp(Date now) { + // TODO(ms) cache and reuse DateFormat instances + SimpleDateFormat dateStampFormat = new SimpleDateFormat( + DATE_STRING_FORMAT); + dateStampFormat.setTimeZone(new SimpleTimeZone(0, UTC)); + String dateStamp = dateStampFormat.format(now); + return dateStamp; + } + + private static String dateTimeStampISO8601(Date now) { + // TODO(ms) cache and reuse DateFormat instances + SimpleDateFormat dateTimeFormat = new SimpleDateFormat( + ISO8601_BASIC_FORMAT); + dateTimeFormat.setTimeZone(new SimpleTimeZone(0, UTC)); + String dateTimeStamp = dateTimeFormat.format(now); + return dateTimeStamp; + } + + private static String scope(String region, String dateStamp) { + String scope = String.format("%s/%s/%s/%s", dateStamp, region, S3, //$NON-NLS-1$ + TERMINATOR); + return scope; + } + + private static String canonicalizeQueryString( + Map parameters) { + if (parameters == null || parameters.isEmpty()) { + return ""; //$NON-NLS-1$ + } + + SortedMap sorted = new TreeMap<>(); + + Iterator> pairs = parameters.entrySet() + .iterator(); + while (pairs.hasNext()) { + Map.Entry pair = pairs.next(); + String key = pair.getKey(); + String value = pair.getValue(); + sorted.put(urlEncode(key, false), urlEncode(value, false)); + } + + StringBuilder builder = new StringBuilder(); + pairs = sorted.entrySet().iterator(); + while (pairs.hasNext()) { + Map.Entry pair = pairs.next(); + builder.append(pair.getKey()); + builder.append("="); //$NON-NLS-1$ + builder.append(pair.getValue()); + if (pairs.hasNext()) { + builder.append("&"); //$NON-NLS-1$ + } + } + + return builder.toString(); + } + + private static String canonicalRequest(URL endpoint, String httpMethod, + String queryParameters, String canonicalizedHeaderNames, + String canonicalizedHeaders, String bodyHash) { + return String.format("%s\n%s\n%s\n%s\n%s\n%s", //$NON-NLS-1$ + httpMethod, canonicalizeResourcePath(endpoint), + queryParameters, canonicalizedHeaders, canonicalizedHeaderNames, + bodyHash); + } + + private static String canonicalizeResourcePath(URL endpoint) { + if (endpoint == null) { + return "/"; //$NON-NLS-1$ + } + String path = endpoint.getPath(); + if (path == null || path.isEmpty()) { + return "/"; //$NON-NLS-1$ + } + + String encodedPath = urlEncode(path, true); + if (encodedPath.startsWith("/")) { //$NON-NLS-1$ + return encodedPath; + } else { + return "/" + encodedPath; //$NON-NLS-1$ + } + } + + private static byte[] hash(String s) { + MessageDigest md = Constants.newMessageDigest(); + md.update(s.getBytes(UTF_8)); + return md.digest(); + } + + private static byte[] sign(String stringData, byte[] key) { + try { + byte[] data = stringData.getBytes(UTF_8); + Mac mac = Mac.getInstance(HMACSHA256); + mac.init(new SecretKeySpec(key, HMACSHA256)); + return mac.doFinal(data); + } catch (Exception e) { + throw new RuntimeException(MessageFormat.format( + LfsServerText.get().failedToCalcSignature, e.getMessage()), + e); + } + } + + private static String stringToSign(String scheme, String algorithm, + String dateTime, String scope, String canonicalRequest) { + return String.format("%s-%s\n%s\n%s\n%s", //$NON-NLS-1$ + scheme, algorithm, dateTime, scope, + toHex(hash(canonicalRequest))); + } + + private static String toHex(byte[] bytes) { + StringBuilder builder = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + builder.append(HEX.charAt((b & 0xF0) >> 4)); + builder.append(HEX.charAt(b & 0xF)); + } + return builder.toString(); + } + + private static String urlEncode(String url, boolean keepPathSlash) { + String encoded; + try { + encoded = URLEncoder.encode(url, UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(LfsServerText.get().unsupportedUtf8, e); + } + if (keepPathSlash) { + encoded = encoded.replace("%2F", "/"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return encoded; + } + + private static byte[] createSignature(S3Config bucketConfig, + String dateTimeStamp, String dateStamp, + String scope, String canonicalRequest) { + String stringToSign = stringToSign(SCHEME, ALGORITHM, dateTimeStamp, + scope, canonicalRequest); + + byte[] signature = (SCHEME + bucketConfig.getSecretKey()).getBytes(); + signature = sign(dateStamp, signature); + signature = sign(bucketConfig.getRegion(), signature); + signature = sign(S3, signature); + signature = sign(TERMINATOR, signature); + signature = sign(stringToSign, signature); + return signature; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015, Sasa Zivkov + * 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.lfs.server; + +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static org.eclipse.jgit.lfs.lib.Constants.DOWNLOAD; +import static org.eclipse.jgit.lfs.lib.Constants.UPLOAD; +import static org.eclipse.jgit.lfs.lib.Constants.VERIFY; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lfs.server.Response.Action; +import org.eclipse.jgit.lfs.server.Response.Body; +import org.eclipse.jgit.lfs.server.internal.LfsServerText; + +abstract class TransferHandler { + + static TransferHandler forOperation(String operation, + LargeFileRepository repository, List objects) { + switch (operation) { + case UPLOAD: + return new Upload(repository, objects); + case DOWNLOAD: + return new Download(repository, objects); + case VERIFY: + default: + throw new UnsupportedOperationException(MessageFormat.format( + LfsServerText.get().unsupportedOperation, operation)); + } + } + + private static class Upload extends TransferHandler { + Upload(LargeFileRepository repository, + List objects) { + super(repository, objects); + } + + @Override + Body process() throws IOException { + Response.Body body = new Response.Body(); + if (objects.size() > 0) { + body.objects = new ArrayList<>(); + for (LfsObject o : objects) { + addObjectInfo(body, o); + } + } + return body; + } + + private void addObjectInfo(Response.Body body, LfsObject o) + throws IOException { + Response.ObjectInfo info = new Response.ObjectInfo(); + body.objects.add(info); + info.oid = o.oid; + info.size = o.size; + + LongObjectId oid = LongObjectId.fromString(o.oid); + if (repository.getSize(oid) == -1) { + info.actions = new HashMap<>(); + info.actions.put(UPLOAD, + repository.getUploadAction(oid, o.size)); + Action verify = repository.getVerifyAction(oid); + if (verify != null) { + info.actions.put(VERIFY, verify); + } + } + } + } + + private static class Download extends TransferHandler { + Download(LargeFileRepository repository, + List objects) { + super(repository, objects); + } + + @Override + Body process() throws IOException { + Response.Body body = new Response.Body(); + if (objects.size() > 0) { + body.objects = new ArrayList<>(); + for (LfsObject o : objects) { + addObjectInfo(body, o); + } + } + return body; + } + + private void addObjectInfo(Response.Body body, LfsObject o) + throws IOException { + Response.ObjectInfo info = new Response.ObjectInfo(); + body.objects.add(info); + info.oid = o.oid; + info.size = o.size; + + LongObjectId oid = LongObjectId.fromString(o.oid); + if (repository.getSize(oid) >= 0) { + info.actions = new HashMap<>(); + info.actions.put(DOWNLOAD, + repository.getDownloadAction(oid)); + } else { + info.error = new Response.Error(); + info.error.code = SC_NOT_FOUND; + info.error.message = MessageFormat.format( + LfsServerText.get().objectNotFound, + oid.getName()); + } + } + } + + final LargeFileRepository repository; + + final List objects; + + TransferHandler(LargeFileRepository repository, + List objects) { + this.repository = repository; + this.objects = objects; + } + + abstract Response.Body process() throws IOException; +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/BUILD jgit-4.11.9/org.eclipse.jgit.lfs.server.test/BUILD --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,49 @@ +load( + "@com_googlesource_gerrit_bazlets//tools:junit.bzl", + "junit_tests", +) + +TEST_BASE = ["tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java"] + +DEPS = [ + "//org.eclipse.jgit.lfs.test:helpers", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit:junit", + "//org.eclipse.jgit.junit.http:junit-http", + "//org.eclipse.jgit.lfs:jgit-lfs", + "//org.eclipse.jgit.lfs.server:jgit-lfs-server", + "//lib:commons-logging", + "//lib:httpcore", + "//lib:httpclient", + "//lib:junit", + "//lib:jetty-http", + "//lib:jetty-io", + "//lib:jetty-server", + "//lib:jetty-servlet", + "//lib:jetty-security", + "//lib:jetty-util", + "//lib:servlet-api", +] + +junit_tests( + name = "lfs_server", + srcs = glob( + ["tst/**/*.java"], + exclude = TEST_BASE, + ), + jvm_flags = [ + "-Xmx256m", + "-Dfile.encoding=UTF-8", + ], + tags = ["lfs-server"], + deps = DEPS + [ + ":helpers", + ], +) + +java_library( + name = "helpers", + testonly = 1, + srcs = TEST_BASE, + deps = DEPS, +) diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/build.properties jgit-4.11.9/org.eclipse.jgit.lfs.server.test/build.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/build.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,5 @@ +source.. = tst/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.classpath jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.classpath --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.classpath 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,7 @@ + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.gitignore jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.gitignore --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +/target +/bin diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,53 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.lfs.server.test +Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test +Bundle-Version: 4.11.9.201909030838-r +Bundle-Vendor: %provider_name +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: javax.servlet;version="[3.1.0,4.0.0)", + javax.servlet.http;version="[3.1.0,4.0.0)", + org.apache.http;version="[4.3.0,5.0.0)", + org.apache.http.client;version="[4.3.0,5.0.0)", + org.apache.http.client.methods;version="[4.3.0,5.0.0)", + org.apache.http.entity;version="[4.3.0,5.0.0)", + org.apache.http.impl.client;version="[4.3.0,5.0.0)", + org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)", + org.eclipse.jetty.http;version="[9.4.5,10.0.0)", + org.eclipse.jetty.io;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)", + org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)", + org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)", + org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)", + org.eclipse.jgit.api;version="[4.11.9,4.12.0)", + org.eclipse.jgit.api.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.internal.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit.http;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.server;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.server.fs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.test;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.storage.file;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk.filter;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.hamcrest.core;version="[1.1.0,2.0.0)", + org.junit;version="[4.12,5.0.0)", + org.junit.rules;version="[4.12,5.0.0)", + org.junit.runner;version="[4.12,5.0.0)", + org.junit.runners;version="[4.12,5.0.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/plugin.properties jgit-4.11.9/org.eclipse.jgit.lfs.server.test/plugin.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/plugin.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/plugin.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +plugin_name=JGit LFS Server Tests +provider_name=Eclipse JGit diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/pom.xml jgit-4.11.9/org.eclipse.jgit.lfs.server.test/pom.xml --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,146 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 4.11.9.201909030838-r + + + org.eclipse.jgit.lfs.server.test + JGit - LFS Server Tests + + + Tests for the LFS server. + + + + + junit + junit + test + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.lfs.server + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.lfs.test + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.junit.http + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.junit + ${project.version} + test + + + + org.eclipse.jetty + jetty-servlet + test + + + + org.apache.httpcomponents + httpclient + 4.3.6 + + + + + tst/ + + + + tst-rsrc/ + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + maven-surefire-plugin + + -Djava.io.tmpdir=${project.build.directory} -Xmx300m + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.project jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.project --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.project 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.project 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,34 @@ + + + org.eclipse.jgit.lfs.server.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.api.tools.apiAnalysisNature + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.resources.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.resources.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.resources.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.resources.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.runtime.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.runtime.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.runtime.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.core.runtime.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,399 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +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.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +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.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.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.tasks.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.tasks.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.tasks.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.tasks.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +resolve.requirebundle=false diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/CheckoutTest.java jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/CheckoutTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/CheckoutTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/CheckoutTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs.server.fs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lfs.BuiltinLFS; +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.util.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class CheckoutTest extends LfsServerTest { + + Git git; + private TestRepository tdb; + + @Override + @Before + public void setup() throws Exception { + super.setup(); + + BuiltinLFS.register(); + + Path tmp = Files.createTempDirectory("jgit_test_"); + Repository db = FileRepositoryBuilder + .create(tmp.resolve(".git").toFile()); + db.create(); + StoredConfig cfg = db.getConfig(); + cfg.setBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, true); + cfg.setBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_REQUIRED, false); + cfg.setString(ConfigConstants.CONFIG_SECTION_LFS, null, "url", + server.getURI().toString() + "/lfs"); + cfg.save(); + + tdb = new TestRepository<>(db); + tdb.branch("test").commit() + .add(".gitattributes", + "*.bin filter=lfs diff=lfs merge=lfs -text ") + .add("a.bin", + "version https://git-lfs.github.com/spec/v1\noid sha256:8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414\nsize 7\n") + .create(); + git = Git.wrap(db); + tdb.branch("test2").commit().add(".gitattributes", + "*.bin filter=lfs diff=lfs merge=lfs -text ").create(); + } + + @After + public void cleanup() throws Exception { + tdb.getRepository().close(); + FileUtils.delete(tdb.getRepository().getWorkTree(), + FileUtils.RECURSIVE); + } + + @Test + public void testUnknownContent() throws Exception { + git.checkout().setName("test").call(); + // unknown content. We will see the pointer file + assertEquals( + "version https://git-lfs.github.com/spec/v1\noid sha256:8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414\nsize 7\n", + JGitTestUtil.read(git.getRepository(), "a.bin")); + assertEquals("[POST /lfs/objects/batch 200]", + server.getRequests().toString()); + } + + @Test(expected = JGitInternalException.class) + public void testUnknownContentRequired() throws Exception { + StoredConfig cfg = tdb.getRepository().getConfig(); + cfg.setBoolean(ConfigConstants.CONFIG_FILTER_SECTION, + ConfigConstants.CONFIG_SECTION_LFS, + ConfigConstants.CONFIG_KEY_REQUIRED, true); + cfg.save(); + + // must throw + git.checkout().setName("test").call(); + } + + @Test + public void testKnownContent() throws Exception { + putContent( + LongObjectId.fromString( + "8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414"), + "1234567"); + git.checkout().setName("test").call(); + // known content. we will see the actual content of the LFS blob. + assertEquals( + "1234567", + JGitTestUtil.read(git.getRepository(), "a.bin")); + assertEquals( + "[PUT /lfs/objects/8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 200" + + ", POST /lfs/objects/batch 200" + + ", GET /lfs/objects/8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 200]", + server.getRequests().toString()); + + git.checkout().setName("test2").call(); + assertFalse(JGitTestUtil.check(git.getRepository(), "a.bin")); + git.checkout().setName("test").call(); + // unknown content. We will see the pointer file + assertEquals("1234567", + JGitTestUtil.read(git.getRepository(), "a.bin")); + assertEquals(3, server.getRequests().size()); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import static org.apache.http.HttpStatus.SC_NOT_FOUND; +import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; + +import org.apache.http.client.ClientProtocolException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class DownloadTest extends LfsServerTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void testDownload() throws Exception { + String TEXT = "test"; + AnyLongObjectId id = putContent(TEXT); + Path f = Paths.get(getTempDirectory().toString(), "download"); + long len = getContent(id, f); + assertEquals(TEXT.length(), len); + FileUtils.delete(f.toFile(), FileUtils.RETRY); + } + + @Test + public void testDownloadInvalidPathInfo() + throws ClientProtocolException, IOException { + String TEXT = "test"; + String id = putContent(TEXT).name().substring(0, 60); + Path f = Paths.get(getTempDirectory().toString(), "download"); + String error = String.format( + "Invalid pathInfo: '/%s' does not match '/{SHA-256}'", id); + exception.expect(RuntimeException.class); + exception.expectMessage( + formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error)); + getContent(id, f); + } + + @Test + public void testDownloadInvalidId() + throws ClientProtocolException, IOException { + String TEXT = "test"; + String id = putContent(TEXT).name().replace('f', 'z'); + Path f = Paths.get(getTempDirectory().toString(), "download"); + String error = String.format("Invalid id: %s", id); + exception.expect(RuntimeException.class); + exception.expectMessage( + formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error)); + getContent(id, f); + } + + @Test + public void testDownloadNotFound() + throws ClientProtocolException, IOException { + String TEXT = "test"; + AnyLongObjectId id = LongObjectIdTestUtils.hash(TEXT); + Path f = Paths.get(getTempDirectory().toString(), "download"); + String error = String.format("Object '%s' not found", id.getName()); + exception.expect(RuntimeException.class); + exception.expectMessage(formatErrorMessage(SC_NOT_FOUND, error)); + getContent(id, f); + } + + @SuppressWarnings("boxing") + @Test + public void testLargeFileDownload() throws Exception { + Path f = Paths.get(getTempDirectory().toString(), "largeRandomFile"); + long expectedLen = createPseudoRandomContentFile(f, 5 * MiB); + AnyLongObjectId id = putContent(f); + Path f2 = Paths.get(getTempDirectory().toString(), "download"); + long start = System.nanoTime(); + long len = getContent(id, f2); + System.out.println( + MessageFormat.format("downloaded 10 MiB random data in {0}ms", + (System.nanoTime() - start) / 1e6)); + assertEquals(expectedLen, len); + FileUtils.delete(f.toFile(), FileUtils.RETRY); + + } + + @SuppressWarnings("boxing") + private String formatErrorMessage(int status, String message) { + return String.format("Status: %d {\"message\":\"%s\"}", status, + message); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.DigestInputStream; +import java.security.SecureRandom; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.junit.http.AppServer; +import org.eclipse.jgit.lfs.errors.LfsException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.LfsProtocolServlet; +import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.junit.After; +import org.junit.Before; + +public abstract class LfsServerTest { + + private static final long timeout = /* 10 sec */ 10 * 1000; + + protected static final int MiB = 1024 * 1024; + + /** In-memory application server; subclass must start. */ + protected AppServer server; + + private Path tmp; + + private Path dir; + + protected FileLfsRepository repository; + + protected FileLfsServlet servlet; + + public LfsServerTest() { + super(); + } + + public Path getTempDirectory() { + return tmp; + } + + public Path getDir() { + return dir; + } + + @Before + public void setup() throws Exception { + tmp = Files.createTempDirectory("jgit_test_"); + server = new AppServer(); + ServletContextHandler app = server.addContext("/lfs"); + dir = Paths.get(tmp.toString(), "lfs"); + this.repository = new FileLfsRepository(null, dir); + servlet = new FileLfsServlet(repository, timeout); + app.addServlet(new ServletHolder(servlet), "/objects/*"); + + LfsProtocolServlet protocol = new LfsProtocolServlet() { + private static final long serialVersionUID = 1L; + + @Override + protected LargeFileRepository getLargeFileRepository( + LfsRequest request, String path) { + return repository; + } + + @Override + protected LargeFileRepository getLargeFileRepository( + LfsRequest request, String path, String auth) + throws LfsException { + return repository; + } + }; + app.addServlet(new ServletHolder(protocol), "/objects/batch"); + + server.setUp(); + this.repository.setUrl(server.getURI() + "/lfs/objects/"); + } + + @After + public void tearDown() throws Exception { + server.tearDown(); + FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY); + } + + protected AnyLongObjectId putContent(String s) + throws IOException, ClientProtocolException { + AnyLongObjectId id = LongObjectIdTestUtils.hash(s); + return putContent(id, s); + } + + protected AnyLongObjectId putContent(AnyLongObjectId id, String s) + throws ClientProtocolException, IOException { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + HttpEntity entity = new StringEntity(s, + ContentType.APPLICATION_OCTET_STREAM); + String hexId = id.name(); + HttpPut request = new HttpPut( + server.getURI() + "/lfs/objects/" + hexId); + request.setEntity(entity); + try (CloseableHttpResponse response = client.execute(request)) { + StatusLine statusLine = response.getStatusLine(); + int status = statusLine.getStatusCode(); + if (status >= 400) { + throw new RuntimeException("Status: " + status + ". " + + statusLine.getReasonPhrase()); + } + } + return id; + } + } + + protected LongObjectId putContent(Path f) + throws FileNotFoundException, IOException { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + LongObjectId id1, id2; + String hexId1, hexId2; + try (DigestInputStream in = new DigestInputStream( + new BufferedInputStream(Files.newInputStream(f)), + Constants.newMessageDigest())) { + InputStreamEntity entity = new InputStreamEntity(in, + Files.size(f), ContentType.APPLICATION_OCTET_STREAM); + id1 = LongObjectIdTestUtils.hash(f); + hexId1 = id1.name(); + HttpPut request = new HttpPut( + server.getURI() + "/lfs/objects/" + hexId1); + request.setEntity(entity); + HttpResponse response = client.execute(request); + checkResponseStatus(response); + id2 = LongObjectId.fromRaw(in.getMessageDigest().digest()); + hexId2 = id2.name(); + assertEquals(hexId1, hexId2); + } + return id1; + } + } + + private void checkResponseStatus(HttpResponse response) { + StatusLine statusLine = response.getStatusLine(); + int status = statusLine.getStatusCode(); + if (statusLine.getStatusCode() >= 400) { + String error; + try { + ByteBuffer buf = IO.readWholeStream(new BufferedInputStream( + response.getEntity().getContent()), 1024); + if (buf.hasArray()) { + error = new String(buf.array(), + buf.arrayOffset() + buf.position(), buf.remaining(), + UTF_8); + } else { + final byte[] b = new byte[buf.remaining()]; + buf.duplicate().get(b); + error = new String(b, UTF_8); + } + } catch (IOException e) { + error = statusLine.getReasonPhrase(); + } + throw new RuntimeException("Status: " + status + " " + error); + } + assertEquals(200, status); + } + + protected long getContent(AnyLongObjectId id, Path f) throws IOException { + String hexId = id.name(); + return getContent(hexId, f); + } + + protected long getContent(String hexId, Path f) throws IOException { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + HttpGet request = new HttpGet( + server.getURI() + "/lfs/objects/" + hexId); + HttpResponse response = client.execute(request); + checkResponseStatus(response); + HttpEntity entity = response.getEntity(); + long pos = 0; + try (InputStream in = entity.getContent(); + ReadableByteChannel inChannel = Channels.newChannel(in); + FileChannel outChannel = FileChannel.open(f, + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE)) { + long transferred; + do { + transferred = outChannel.transferFrom(inChannel, pos, MiB); + pos += transferred; + } while (transferred > 0); + } + return pos; + } + } + + /** + * Creates a file with random content, repeatedly writing a random string of + * 4k length to the file until the file has at least the specified length. + * + * @param f + * file to fill + * @param size + * size of the file to generate + * @return length of the generated file in bytes + * @throws IOException + */ + protected long createPseudoRandomContentFile(Path f, long size) + throws IOException { + SecureRandom rnd = new SecureRandom(); + byte[] buf = new byte[4096]; + rnd.nextBytes(buf); + ByteBuffer bytebuf = ByteBuffer.wrap(buf); + try (FileChannel outChannel = FileChannel.open(f, + StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + long len = 0; + do { + len += outChannel.write(bytebuf); + if (bytebuf.position() == 4096) { + bytebuf.rewind(); + } + } while (len < size); + } + return Files.size(f); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018, Markus Duft + * 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.lfs.server.fs; + +import static org.junit.Assert.assertEquals; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RemoteAddCommand; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lfs.BuiltinLFS; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class PushTest extends LfsServerTest { + + Git git; + + private TestRepository localDb; + + private Repository remoteDb; + + @Override + @Before + public void setup() throws Exception { + super.setup(); + + BuiltinLFS.register(); + + Path rtmp = Files.createTempDirectory("jgit_test_"); + remoteDb = FileRepositoryBuilder.create(rtmp.toFile()); + remoteDb.create(true); + + Path tmp = Files.createTempDirectory("jgit_test_"); + Repository db = FileRepositoryBuilder + .create(tmp.resolve(".git").toFile()); + db.create(false); + StoredConfig cfg = db.getConfig(); + cfg.setString("filter", "lfs", "usejgitbuiltin", "true"); + cfg.setString("lfs", null, "url", server.getURI().toString() + "/lfs"); + cfg.save(); + + localDb = new TestRepository<>(db); + localDb.branch("master").commit().add(".gitattributes", + "*.bin filter=lfs diff=lfs merge=lfs -text ").create(); + git = Git.wrap(db); + + URIish uri = new URIish( + "file://" + remoteDb.getDirectory()); + RemoteAddCommand radd = git.remoteAdd(); + radd.setUri(uri); + radd.setName(Constants.DEFAULT_REMOTE_NAME); + radd.call(); + + git.checkout().setName("master").call(); + git.push().call(); + } + + @After + public void cleanup() throws Exception { + remoteDb.close(); + localDb.getRepository().close(); + FileUtils.delete(localDb.getRepository().getWorkTree(), + FileUtils.RECURSIVE); + FileUtils.delete(remoteDb.getDirectory(), FileUtils.RECURSIVE); + } + + @Test + public void testPushSimple() throws Exception { + JGitTestUtil.writeTrashFile(localDb.getRepository(), "a.bin", + "1234567"); + git.add().addFilepattern("a.bin").call(); + RevCommit commit = git.commit().setMessage("add lfs blob").call(); + git.push().call(); + + // check object in remote db, should be LFS pointer + ObjectId id = commit.getId(); + try (RevWalk walk = new RevWalk(remoteDb)) { + RevCommit rc = walk.parseCommit(id); + try (TreeWalk tw = new TreeWalk(walk.getObjectReader())) { + tw.addTree(rc.getTree()); + tw.setFilter(PathFilter.create("a.bin")); + tw.next(); + + assertEquals(tw.getPathString(), "a.bin"); + ObjectLoader ldr = walk.getObjectReader() + .open(tw.getObjectId(0), Constants.OBJ_BLOB); + try(InputStream is = ldr.openStream()) { + assertEquals( + "version https://git-lfs.github.com/spec/v1\noid sha256:8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414\nsize 7\n", + new String(IO + .readWholeStream(is, + (int) ldr.getSize()) + .array())); + } + } + + } + + assertEquals( + "[POST /lfs/objects/batch 200, PUT /lfs/objects/8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 200]", + server.getRequests().toString()); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.server.fs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; +import org.junit.Test; + +public class UploadTest extends LfsServerTest { + + @Test + public void testUpload() throws Exception { + String TEXT = "test"; + AnyLongObjectId id = putContent(TEXT); + assertTrue("expect object " + id.name() + " to exist", + repository.getSize(id) >= 0); + assertEquals("expected object length " + TEXT.length(), TEXT.length(), + repository.getSize(id)); + } + + @Test + public void testCorruptUpload() throws Exception { + String TEXT = "test"; + AnyLongObjectId id = LongObjectIdTestUtils.hash("wrongHash"); + try { + putContent(id, TEXT); + fail("expected RuntimeException(\"Status 400\")"); + } catch (RuntimeException e) { + assertEquals("Status: 400. Bad Request", e.getMessage()); + } + assertFalse("expect object " + id.name() + " not to exist", + repository.getSize(id) >= 0); + } + + @SuppressWarnings("boxing") + @Test + public void testLargeFileUpload() throws Exception { + Path f = Paths.get(getTempDirectory().toString(), "largeRandomFile"); + createPseudoRandomContentFile(f, 5 * MiB); + long start = System.nanoTime(); + LongObjectId id = putContent(f); + System.out.println( + MessageFormat.format("uploaded 10 MiB random data in {0}ms", + (System.nanoTime() - start) / 1e6)); + assertTrue("expect object " + id.name() + " to exist", + repository.getSize(id) >= 0); + assertEquals("expected object length " + Files.size(f), Files.size(f), + repository.getSize(id)); + } + + @Test + public void testParallelUploads() throws Exception { + int count = 10; + List paths = new ArrayList<>(count); + + for (int i = 0; i < count; i++) { + Path f = Paths.get(getTempDirectory().toString(), + "largeRandomFile_" + i); + createPseudoRandomContentFile(f, 1 * MiB); + paths.add(f); + } + + final CyclicBarrier barrier = new CyclicBarrier(count); + + ExecutorService e = Executors.newFixedThreadPool(count); + try { + for (final Path p : paths) { + e.submit(new Callable() { + @Override + public Void call() throws Exception { + barrier.await(); + putContent(p); + return null; + } + }); + } + } finally { + e.shutdown(); + e.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/BUILD jgit-4.11.9/org.eclipse.jgit.lfs.test/BUILD --- jgit-4.1.2/org.eclipse.jgit.lfs.test/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,31 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@com_googlesource_gerrit_bazlets//tools:junit.bzl", + "junit_tests", +) + +junit_tests( + name = "lfs", + srcs = glob(["tst/**/*.java"]), + tags = ["lfs"], + deps = [ + ":helpers", + "//lib:junit", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit:junit", + "//org.eclipse.jgit.lfs:jgit-lfs", + ], +) + +java_library( + name = "helpers", + testonly = 1, + srcs = glob(["src/**/*.java"]), + deps = [ + "//lib:junit", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit:junit", + "//org.eclipse.jgit.lfs:jgit-lfs", + ], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/build.properties jgit-4.11.9/org.eclipse.jgit.lfs.test/build.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.test/build.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,5 @@ +source.. = tst/, src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.classpath jgit-4.11.9/org.eclipse.jgit.lfs.test/.classpath --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.classpath 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.gitignore jgit-4.11.9/org.eclipse.jgit.lfs.test/.gitignore --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +/target +/bin diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.lfs.test +Bundle-SymbolicName: org.eclipse.jgit.lfs.test +Bundle-Version: 4.11.9.201909030838-r +Bundle-Vendor: %provider_name +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.junit;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lfs.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.treewalk.filter;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)", + org.hamcrest.core;version="[1.1.0,2.0.0)", + org.junit;version="[4.12,5.0.0)", + org.junit.runner;version="[4.12,5.0.0)", + org.junit.runners;version="[4.12,5.0.0)" +Export-Package: org.eclipse.jgit.lfs.test;version="4.11.9";x-friends:="org.eclipse.jgit.lfs.server.test" + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/plugin.properties jgit-4.11.9/org.eclipse.jgit.lfs.test/plugin.properties --- jgit-4.1.2/org.eclipse.jgit.lfs.test/plugin.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/plugin.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +plugin_name=JGit LFS Tests +provider_name=Eclipse JGit diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/pom.xml jgit-4.11.9/org.eclipse.jgit.lfs.test/pom.xml --- jgit-4.1.2/org.eclipse.jgit.lfs.test/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,119 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 4.11.9.201909030838-r + + + org.eclipse.jgit.lfs.test + JGit - Large File Storage Tests + + + Tests for the Large File Extension (LFS). + + + + + junit + junit + test + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.lfs + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.junit + ${project.version} + test + + + + + tst/ + src/ + + + + tst-rsrc/ + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + maven-surefire-plugin + + -Djava.io.tmpdir=${project.build.directory} -Xmx300m + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.project jgit-4.11.9/org.eclipse.jgit.lfs.test/.project --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.project 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.project 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,34 @@ + + + org.eclipse.jgit.lfs.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.api.tools.apiAnalysisNature + + diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.resources.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.resources.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.resources.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.resources.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.runtime.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.runtime.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.runtime.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.core.runtime.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,399 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +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.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +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.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.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.tasks.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.tasks.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.tasks.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.tasks.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.core.prefs jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.core.prefs --- jgit-4.1.2/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.core.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +resolve.requirebundle=false diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java jgit-4.11.9/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.test; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; + +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; + +public class LongObjectIdTestUtils { + + /** + * Create id as hash of the given string. + * + * @param s + * the string to hash + * @return id calculated by hashing string + */ + public static LongObjectId hash(String s) { + MessageDigest md = Constants.newMessageDigest(); + md.update(s.getBytes(UTF_8)); + return LongObjectId.fromRaw(md.digest()); + } + + /** + * Create id as hash of a file content + * + * @param file + * the file to hash + * @return id calculated by hashing file content + * @throws FileNotFoundException + * if file doesn't exist + * @throws IOException + */ + public static LongObjectId hash(Path file) + throws FileNotFoundException, IOException { + MessageDigest md = Constants.newMessageDigest(); + try (InputStream is = new BufferedInputStream( + Files.newInputStream(file))) { + final byte[] buffer = new byte[4096]; + for (int read = 0; (read = is.read(buffer)) != -1;) { + md.update(buffer, 0, read); + } + } + return LongObjectId.fromRaw(md.digest()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectIdTest.java jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectIdTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectIdTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectIdTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +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.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; +import org.junit.Test; + +/* + * Ported to SHA-256 from org.eclipse.jgit.lib.AbbreviatedObjectIdTest + */ +public class AbbreviatedLongObjectIdTest { + @Test + public void testEmpty_FromByteArray() { + final AbbreviatedLongObjectId i; + i = AbbreviatedLongObjectId.fromString(new byte[] {}, 0, 0); + assertNotNull(i); + assertEquals(0, i.length()); + assertFalse(i.isComplete()); + assertEquals("", i.name()); + } + + @Test + public void testEmpty_FromString() { + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId + .fromString(""); + assertNotNull(i); + assertEquals(0, i.length()); + assertFalse(i.isComplete()); + assertEquals("", i.name()); + } + + @Test + public void testFull_FromByteArray() { + final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final byte[] b = org.eclipse.jgit.lib.Constants.encodeASCII(s); + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(b, + 0, b.length); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertTrue(i.isComplete()); + assertEquals(s, i.name()); + + final LongObjectId f = i.toLongObjectId(); + assertNotNull(f); + assertEquals(LongObjectId.fromString(s), f); + assertEquals(f.hashCode(), i.hashCode()); + } + + @Test + public void testFull_FromString() { + final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertTrue(i.isComplete()); + assertEquals(s, i.name()); + + final LongObjectId f = i.toLongObjectId(); + assertNotNull(f); + assertEquals(LongObjectId.fromString(s), f); + assertEquals(f.hashCode(), i.hashCode()); + } + + @Test + public void test1_FromString() { + final String s = "2"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test2_FromString() { + final String s = "27"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test3_FromString() { + final String s = "27e"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test4_FromString() { + final String s = "27e1"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test5_FromString() { + final String s = "27e15"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test6_FromString() { + final String s = "27e15b"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test7_FromString() { + final String s = "27e15b7"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test8_FromString() { + final String s = "27e15b72"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test9_FromString() { + final String s = "27e15b729"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test15_FromString() { + final String s = "27e15b72937fc8f"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test16_FromString() { + final String s = "27e15b72937fc8f5"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test17_FromString() { + final String s = "27e15b72937fc8f55"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void test33_FromString() { + final String s = "27e15b72937fc8f558da24ac3d50ec203"; + final AbbreviatedLongObjectId i = AbbreviatedLongObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toLongObjectId()); + } + + @Test + public void testEquals_Short() { + final String s = "27e15b72"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId.fromString(s); + final AbbreviatedLongObjectId b = AbbreviatedLongObjectId.fromString(s); + assertNotSame(a, b); + assertTrue(a.hashCode() == b.hashCode()); + assertEquals(b, a); + assertEquals(a, b); + } + + @Test + public void testEquals_Full() { + final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId.fromString(s); + final AbbreviatedLongObjectId b = AbbreviatedLongObjectId.fromString(s); + assertNotSame(a, b); + assertTrue(a.hashCode() == b.hashCode()); + assertEquals(b, a); + assertEquals(a, b); + } + + @Test + public void testNotEquals_SameLength() { + final String sa = "27e15b72"; + final String sb = "27e15b7f"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + final AbbreviatedLongObjectId b = AbbreviatedLongObjectId + .fromString(sb); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + + @Test + public void testNotEquals_DiffLength() { + final String sa = "27e15b72abcd"; + final String sb = "27e15b72"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + final AbbreviatedLongObjectId b = AbbreviatedLongObjectId + .fromString(sb); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + + @Test + public void testPrefixCompare_Full() { + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(s1); + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b11"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b0f"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testPrefixCompare_1() { + final String sa = "2"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "37e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "17e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testPrefixCompare_15() { + final String sa = "27e15b72937fc8f"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "27e15b72937fc90558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "27e15b72937fc8e558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testPrefixCompare_16() { + final String sa = "27e15b72937fc8f5"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "27e15b72937fc8f658da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "27e15b72937fc8f458da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testPrefixCompare_17() { + final String sa = "27e15b72937fc8f55"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "27e15b72937fc8f568da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "27e15b72937fc8f548da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testPrefixCompare_33() { + final String sa = "27e15b72937fc8f558da24ac3d50ec203"; + final AbbreviatedLongObjectId a = AbbreviatedLongObjectId + .fromString(sa); + + final String s1 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i1 = LongObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "27e15b72937fc8f558da24ac3d50ec20402a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i2 = LongObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "27e15b72937fc8f558da24ac3d50ec20202a4cf21e33b87ae8e4ce90e89c4b10"; + final LongObjectId i3 = LongObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + @Test + public void testIsId() { + // These are all too short. + assertFalse(AbbreviatedLongObjectId.isId("")); + assertFalse(AbbreviatedLongObjectId.isId("a")); + + // These are too long. + assertFalse(AbbreviatedLongObjectId.isId(LongObjectId + .fromString( + "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10") + .name() + "0")); + assertFalse(AbbreviatedLongObjectId.isId(LongObjectId + .fromString( + "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10") + .name() + "c0ffee")); + + // These contain non-hex characters. + assertFalse(AbbreviatedLongObjectId.isId("01notahexstring")); + + // These should all work. + assertTrue(AbbreviatedLongObjectId.isId("ab")); + assertTrue(AbbreviatedLongObjectId.isId("abc")); + assertTrue(AbbreviatedLongObjectId.isId("abcd")); + assertTrue(AbbreviatedLongObjectId.isId("abcd0")); + assertTrue(AbbreviatedLongObjectId.isId("abcd09")); + assertTrue(AbbreviatedLongObjectId.isId(LongObjectId + .fromString( + "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10") + .name())); + } + + @Test + public void testAbbreviate() { + AnyLongObjectId id = LongObjectIdTestUtils.hash("test"); + assertEquals( + "abbreviated id should match the id it was abbreviated from", 0, + id.abbreviate(10).prefixCompare(id)); + } + + @Test + public void testFromStringByteWrongLength() { + byte[] buf = new byte[65]; + try { + AbbreviatedLongObjectId.fromString(buf, 0, 65); + fail("expected IllegalArgumentException for too long AbbreviatedLongObjectId"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testFromStringWrongLength() { + AnyLongObjectId id = LongObjectIdTestUtils.hash("test"); + try { + AbbreviatedLongObjectId.fromString(id.name() + "c0ffee"); + fail("expected IllegalArgumentException for too long AbbreviatedLongObjectId"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testFromLongObjectId() { + AnyLongObjectId id = LongObjectIdTestUtils.hash("test"); + assertEquals(0, + AbbreviatedLongObjectId.fromLongObjectId(id).prefixCompare(id)); + } + + @Test + public void testPrefixCompareByte() { + AnyLongObjectId id = LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + byte[] buf = new byte[32]; + id.copyRawTo(buf, 0); + + AbbreviatedLongObjectId a = id.abbreviate(62); + assertEquals(0, a.prefixCompare(buf, 0)); + + a = LongObjectId + .fromString( + "0023456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .abbreviate(16); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef0023456789abcdef0123456789abcdef0123456789abcdef") + .abbreviate(32); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef0123456789abcdef0023456789abcdef0123456789abcdef") + .abbreviate(48); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0023456789abcdef") + .abbreviate(64); + assertEquals(-1, a.prefixCompare(buf, 0)); + + a = LongObjectId + .fromString( + "1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .abbreviate(16); + assertEquals(1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef1123456789abcdef0123456789abcdef0123456789abcdef") + .abbreviate(32); + assertEquals(1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef0123456789abcdef1123456789abcdef0123456789abcdef") + .abbreviate(48); + assertEquals(1, a.prefixCompare(buf, 0)); + a = LongObjectId + .fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef1123456789abcdef") + .abbreviate(64); + assertEquals(1, a.prefixCompare(buf, 0)); + } + + @Test + public void testPrefixCompareLong() { + AnyLongObjectId id = new LongObjectId(1L, 2L, 3L, 4L); + long[] buf = new long[4]; + id.copyRawTo(buf, 0); + + AbbreviatedLongObjectId a = id.abbreviate(62); + assertEquals(0, a.prefixCompare(buf, 0)); + + a = new LongObjectId(0L, 2L, 3L, 4L).abbreviate(16); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 1L, 3L, 4L).abbreviate(32); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 2L, 2L, 4L).abbreviate(48); + assertEquals(-1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 2L, 3L, 3L).abbreviate(64); + assertEquals(-1, a.prefixCompare(buf, 0)); + + a = new LongObjectId(2L, 2L, 3L, 4L).abbreviate(16); + assertEquals(1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 3L, 3L, 4L).abbreviate(32); + assertEquals(1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 2L, 4L, 4L).abbreviate(48); + assertEquals(1, a.prefixCompare(buf, 0)); + a = new LongObjectId(1L, 2L, 3L, 5L).abbreviate(64); + assertEquals(1, a.prefixCompare(buf, 0)); + } + + @Test + public void testGetFirstByte() { + AnyLongObjectId id = LongObjectId.fromString( + "f423456789abcdef0123456789abcdef0123456789abcdef1123456789abcdef"); + AbbreviatedLongObjectId a = id.abbreviate(10); + assertEquals(0xf4, a.getFirstByte()); + assertEquals(id.getFirstByte(), a.getFirstByte()); + } + + @SuppressWarnings("unlikely-arg-type") + @Test + public void testNotEquals() { + AbbreviatedLongObjectId a = new LongObjectId(1L, 2L, 3L, 4L) + .abbreviate(10); + assertFalse(a.equals("different")); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2015, Dariusz Luksza + * 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.lfs.lib; + +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.assertTrue; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.revwalk.ObjectWalk; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Test; + +public class LfsPointerFilterTest { + + private static final int SIZE = 12345; + + private static final String OID = "4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393"; + + private static final String[] NOT_VALID_LFS_FILES = { "", // empty file + // simulate java file + "package org.eclipse.jgit;", + // invalid LFS pointer, no oid and version + "version https://hawser.github.com/spec/v1\n", + // invalid LFS pointer, no version + "version https://hawser.github.com/spec/v1\n" + + "oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393\n", + // invalid LFS pointer, no id + "version https://hawser.github.com/spec/v1\n" + "size 12345\n", + // invalid LFS pointer, wrong order of oid and size + "version https://hawser.github.com/spec/v1\n" + "size 12345\n" + + "oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393\n" }; + + private static final String[] LFS_VERSION_DOMAINS = { + "hawser", "git-lfs" + }; + + private static final String[] VALID_LFS_FILES = { + // valid LFS pointer + "version https://%s.github.com/spec/v1\n" + + "oid sha256:" + OID + "\n" + + "size " + SIZE + "\n", + // valid LFS pointer with "custom" key + "version https://%s.github.com/spec/v1\n" + + "custom key with value\n" + + "oid sha256:" + OID + "\n" + + "size " + SIZE + "\n", + // valid LFS pointer with key with "." + "version https://%s.github.com/spec/v1\n" + + "oid sha256:" + OID + "\n" + + "r.key key with .\n" + + "size " + SIZE + "\n", + // valid LFS pointer with key with "-" + "version https://%s.github.com/spec/v1\n" + + "oid sha256:" + OID + "\n" + + "size " + SIZE + "\n" + + "valid-name another valid key\n" }; + + @Test + public void testRegularFilesInRepositoryRoot() throws Exception { + for (String file : NOT_VALID_LFS_FILES) { + assertLfs("file.bin", file).withRecursive(false).shouldBe(false); + } + } + + @Test + public void testNestedRegularFiles() throws Exception { + for (String file : NOT_VALID_LFS_FILES) { + assertLfs("a/file.bin", file).withRecursive(true).shouldBe(false); + } + } + + @Test + public void testValidPointersInRepositoryRoot() throws Exception { + for (String domain : LFS_VERSION_DOMAINS) { + for (String file : VALID_LFS_FILES) { + assertLfs("file.bin", String.format(file, domain)) + .withRecursive(true).shouldBe(true) + .check(); + } + } + } + + @Test + public void testValidNestedPointers() throws Exception { + for (String domain : LFS_VERSION_DOMAINS) { + for (String file : VALID_LFS_FILES) { + assertLfs("a/file.bin", String.format(file, domain)) + .withRecursive(true).shouldBe(true).check(); + } + } + } + + @Test + public void testValidNestedPointersWithoutRecurrence() throws Exception { + for (String domain : LFS_VERSION_DOMAINS) { + for (String file : VALID_LFS_FILES) { + assertLfs("file.bin", String.format(file, domain)) + .withRecursive(false).shouldBe(true).check(); + assertLfs("a/file.bin", String.format(file, domain)) + .withRecursive(false).shouldBe(false).check(); + } + } + } + + private static LfsTreeWalk assertLfs(String path, String content) { + return new LfsTreeWalk(path, content); + } + + private static class LfsTreeWalk { + private final String path; + + private final String content; + + private boolean state; + + private boolean recursive; + + private TestRepository tr; + + LfsTreeWalk(String path, String content) { + this.path = path; + this.content = content; + } + + LfsTreeWalk withRecursive(boolean shouldBeRecursive) { + this.recursive = shouldBeRecursive; + return this; + } + + LfsTreeWalk shouldBe(boolean shouldBeValid) { + this.state = shouldBeValid; + return this; + } + + void check() throws Exception { + tr = new TestRepository<>(new InMemoryRepository( + new DfsRepositoryDescription("test"))); + RevCommit commit = tr.branch("master").commit().add(path, content) + .message("initial commit").create(); + RevTree tree = parseCommit(commit); + LfsPointerFilter filter = new LfsPointerFilter(); + try (TreeWalk treeWalk = new TreeWalk(tr.getRepository())) { + treeWalk.addTree(tree); + treeWalk.setRecursive(recursive); + treeWalk.setFilter(filter); + + if (state) { + assertTrue(treeWalk.next()); + assertEquals(path, treeWalk.getPathString()); + assertNotNull(filter.getPointer()); + assertEquals(SIZE, filter.getPointer().getSize()); + assertEquals(OID, filter.getPointer().getOid().name()); + } else { + assertFalse(treeWalk.next()); + assertNull(filter.getPointer()); + } + } + } + + private RevTree parseCommit(RevCommit commit) throws Exception { + try (ObjectWalk ow = new ObjectWalk(tr.getRepository())) { + return ow.parseCommit(commit).getTree(); + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.lfs.lib; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.jgit.lfs.LfsPointer; +import org.junit.Test; + +/* + * Test LfsPointer file abstraction + */ +public class LFSPointerTest { + @Test + public void testEncoding() throws IOException { + final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; + AnyLongObjectId id = LongObjectId.fromString(s); + LfsPointer ptr = new LfsPointer(id, 4); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + ptr.encode(baos); + assertEquals( + "version https://git-lfs.github.com/spec/v1\noid sha256:" + + s + "\nsize 4\n", + baos.toString(UTF_8.name())); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; + +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/* + * Ported to SHA-256 from org.eclipse.jgit.lib.ObjectIdTest + */ +public class LongObjectIdTest { + private static Path tmp; + + @BeforeClass + public static void setup() throws IOException { + tmp = Files.createTempDirectory("jgit_test_"); + } + + @AfterClass + public static void tearDown() throws IOException { + FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY); + } + + @Test + public void test001_toString() { + final String x = "8367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2deb7ab0a"; + final LongObjectId oid = LongObjectId.fromString(x); + assertEquals(x, oid.name()); + } + + @Test + public void test002_toString() { + final String x = "140ce71d628cceb78e3709940ba52a651a0c4a9c1400f2e15e998a1a43887edf"; + final LongObjectId oid = LongObjectId.fromString(x); + assertEquals(x, oid.name()); + } + + @Test + public void test003_equals() { + final String x = "8367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2deb7ab0a"; + final LongObjectId a = LongObjectId.fromString(x); + final LongObjectId b = LongObjectId.fromString(x); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals("a and b should be equal", b, a); + } + + @Test + public void test004_isId() { + assertTrue("valid id", LongObjectId.isId( + "8367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2deb7ab0a")); + } + + @Test + public void test005_notIsId() { + assertFalse("bob is not an id", LongObjectId.isId("bob")); + } + + @Test + public void test006_notIsId() { + assertFalse("63 digits is not an id", LongObjectId.isId( + "8367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2deb7ab0")); + } + + @Test + public void test007_isId() { + assertTrue("uppercase is accepted", LongObjectId.isId( + "8367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2dEb7ab0A")); + } + + @Test + public void test008_notIsId() { + assertFalse("g is not a valid hex digit", LongObjectId.isId( + "g367b0edc81df80e6b42eb1b71f783111224e058cb3da37894d065d2deb7ab0a")); + } + + @Test + public void test009_toString() { + final String x = "140ce71d628cceb78e3709940ba52a651a0c4a9c1400f2e15e998a1a43887edf"; + final LongObjectId oid = LongObjectId.fromString(x); + assertEquals(x, LongObjectId.toString(oid)); + } + + @Test + public void test010_toString() { + final String x = "0000000000000000000000000000000000000000000000000000000000000000"; + assertEquals(x, LongObjectId.toString(null)); + } + + @Test + public void test011_toString() { + final String x = "0123456789ABCDEFabcdef01234567890123456789ABCDEFabcdef0123456789"; + final LongObjectId oid = LongObjectId.fromString(x); + assertEquals(x.toLowerCase(Locale.ROOT), oid.name()); + } + + @Test + public void testGetByte() { + byte[] raw = new byte[32]; + for (int i = 0; i < 32; i++) + raw[i] = (byte) (0xa0 + i); + LongObjectId id = LongObjectId.fromRaw(raw); + + assertEquals(raw[0] & 0xff, id.getFirstByte()); + assertEquals(raw[0] & 0xff, id.getByte(0)); + assertEquals(raw[1] & 0xff, id.getByte(1)); + assertEquals(raw[1] & 0xff, id.getSecondByte()); + + for (int i = 2; i < 32; i++) { + assertEquals("index " + i, raw[i] & 0xff, id.getByte(i)); + } + try { + id.getByte(32); + fail("LongObjectId has 32 byte only"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSetByte() { + byte[] exp = new byte[32]; + for (int i = 0; i < 32; i++) { + exp[i] = (byte) (0xa0 + i); + } + + MutableLongObjectId id = new MutableLongObjectId(); + id.fromRaw(exp); + assertEquals(LongObjectId.fromRaw(exp).name(), id.name()); + + id.setByte(0, 0x10); + assertEquals(0x10, id.getByte(0)); + exp[0] = 0x10; + assertEquals(LongObjectId.fromRaw(exp).name(), id.name()); + + for (int p = 1; p < 32; p++) { + id.setByte(p, 0x10 + p); + assertEquals(0x10 + p, id.getByte(p)); + exp[p] = (byte) (0x10 + p); + assertEquals(LongObjectId.fromRaw(exp).name(), id.name()); + } + + for (int p = 0; p < 32; p++) { + id.setByte(p, 0x80 + p); + assertEquals(0x80 + p, id.getByte(p)); + exp[p] = (byte) (0x80 + p); + assertEquals(LongObjectId.fromRaw(exp).name(), id.name()); + } + } + + @Test + public void testZeroId() { + AnyLongObjectId zero = new LongObjectId(0L, 0L, 0L, 0L); + assertEquals(zero, LongObjectId.zeroId()); + assertEquals( + "0000000000000000000000000000000000000000000000000000000000000000", + LongObjectId.zeroId().name()); + } + + @Test + public void testEquals() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + assertTrue("id should equal itself", id1.equals(id1)); + AnyLongObjectId id2 = new LongObjectId(id1); + assertEquals("objects should be equals", id1, id2); + + id2 = LongObjectIdTestUtils.hash("other"); + assertNotEquals("objects should be not equal", id1, id2); + } + + @Test + public void testCopyRawBytes() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + AnyLongObjectId id2 = new LongObjectId(id1); + + byte[] buf = new byte[64]; + id1.copyRawTo(buf, 0); + id2.copyRawTo(buf, 32); + assertTrue("objects should be equals", + LongObjectId.equals(buf, 0, buf, 32)); + } + + @Test + public void testCopyRawLongs() { + long[] a = new long[4]; + a[0] = 1L; + a[1] = 2L; + a[2] = 3L; + a[3] = 4L; + AnyLongObjectId id1 = new LongObjectId(a[0], a[1], a[2], a[3]); + AnyLongObjectId id2 = LongObjectId.fromRaw(a); + assertEquals("objects should be equals", id1, id2); + } + + @Test + public void testCopyFromStringInvalid() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + try { + LongObjectId.fromString(id1.name() + "01234"); + fail("expected InvalidLongObjectIdException"); + } catch (InvalidLongObjectIdException e) { + assertEquals("Invalid id: " + id1.name() + "01234", + e.getMessage()); + } + } + + @Test + public void testCopyFromStringByte() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + byte[] buf = new byte[64]; + Charset cs = US_ASCII; + cs.encode(id1.name()).get(buf); + AnyLongObjectId id2 = LongObjectId.fromString(buf, 0); + assertEquals("objects should be equals", id1, id2); + } + + @Test + public void testHashFile() throws IOException { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + Path f = tmp.resolve("test"); + JGitTestUtil.write(f.toFile(), "test"); + AnyLongObjectId id2 = LongObjectIdTestUtils.hash(f); + assertEquals("objects should be equals", id1, id2); + } + + @Test + public void testCompareTo() { + AnyLongObjectId id1 = LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + assertEquals(0, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))); + AnyLongObjectId self = id1; + assertEquals(0, id1.compareTo(self)); + + assertEquals(-1, id1.compareTo(LongObjectId.fromString( + "1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))); + assertEquals(-1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef1123456789abcdef0123456789abcdef0123456789abcdef"))); + assertEquals(-1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0123456789abcdef1123456789abcdef0123456789abcdef"))); + assertEquals(-1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef1123456789abcdef"))); + + assertEquals(1, id1.compareTo(LongObjectId.fromString( + "0023456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))); + assertEquals(1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0023456789abcdef0123456789abcdef0123456789abcdef"))); + assertEquals(1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0023456789abcdef0123456789abcdef"))); + assertEquals(1, id1.compareTo(LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0023456789abcdef"))); + } + + @Test + public void testCompareToByte() { + AnyLongObjectId id1 = LongObjectId.fromString( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + byte[] buf = new byte[32]; + id1.copyRawTo(buf, 0); + assertEquals(0, id1.compareTo(buf, 0)); + + LongObjectId + .fromString( + "1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .copyRawTo(buf, 0); + assertEquals(-1, id1.compareTo(buf, 0)); + + LongObjectId + .fromString( + "0023456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .copyRawTo(buf, 0); + assertEquals(1, id1.compareTo(buf, 0)); + } + + @Test + public void testCompareToLong() { + AnyLongObjectId id1 = new LongObjectId(1L, 2L, 3L, 4L); + long[] buf = new long[4]; + id1.copyRawTo(buf, 0); + assertEquals(0, id1.compareTo(buf, 0)); + + new LongObjectId(2L, 2L, 3L, 4L).copyRawTo(buf, 0); + assertEquals(-1, id1.compareTo(buf, 0)); + + new LongObjectId(0L, 2L, 3L, 4L).copyRawTo(buf, 0); + assertEquals(1, id1.compareTo(buf, 0)); + } + + @Test + public void testCopyToByte() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + byte[] buf = new byte[64]; + id1.copyTo(buf, 0); + assertEquals(id1, LongObjectId.fromString(buf, 0)); + } + + @Test + public void testCopyRawToByteBuffer() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteBuffer buf = ByteBuffer.allocate(32); + id1.copyRawTo(buf); + assertEquals(id1, LongObjectId.fromRaw(buf.array(), 0)); + } + + @Test + public void testCopyToByteBuffer() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteBuffer buf = ByteBuffer.allocate(64); + id1.copyTo(buf); + assertEquals(id1, LongObjectId.fromString(buf.array(), 0)); + } + + @Test + public void testCopyRawToOutputStream() throws IOException { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteArrayOutputStream os = new ByteArrayOutputStream(32); + id1.copyRawTo(os); + assertEquals(id1, LongObjectId.fromRaw(os.toByteArray(), 0)); + } + + @Test + public void testCopyToOutputStream() throws IOException { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteArrayOutputStream os = new ByteArrayOutputStream(64); + id1.copyTo(os); + assertEquals(id1, LongObjectId.fromString(os.toByteArray(), 0)); + } + + @Test + public void testCopyToWriter() throws IOException { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteArrayOutputStream os = new ByteArrayOutputStream(64); + try (OutputStreamWriter w = new OutputStreamWriter(os, + Constants.CHARSET)) { + id1.copyTo(w); + } + assertEquals(id1, LongObjectId.fromString(os.toByteArray(), 0)); + } + + @Test + public void testCopyToWriterWithBuf() throws IOException { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + ByteArrayOutputStream os = new ByteArrayOutputStream(64); + try (OutputStreamWriter w = new OutputStreamWriter(os, + Constants.CHARSET)) { + char[] buf = new char[64]; + id1.copyTo(buf, w); + } + assertEquals(id1, LongObjectId.fromString(os.toByteArray(), 0)); + } + + @Test + public void testCopyToStringBuilder() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + StringBuilder sb = new StringBuilder(); + char[] buf = new char[64]; + id1.copyTo(buf, sb); + assertEquals(id1, LongObjectId.fromString(sb.toString())); + } + + @Test + public void testCopy() { + AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test"); + assertEquals(id1.copy(), id1); + MutableLongObjectId id2 = new MutableLongObjectId(); + id2.fromObjectId(id1); + assertEquals(id1, id2.copy()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/MutableLongObjectIdTest.java jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/MutableLongObjectIdTest.java --- jgit-4.1.2/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/MutableLongObjectIdTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/MutableLongObjectIdTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015, Matthias Sohn + * 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.lfs.lib; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/* + * Ported to SHA-256 from org.eclipse.jgit.lib.MutableObjectIdTest + */ +public class MutableLongObjectIdTest { + + @Test + public void testFromRawLong() { + MutableLongObjectId m = new MutableLongObjectId(); + m.fromRaw(new long[] { 1L, 2L, 3L, 4L }); + assertEquals(new LongObjectId(1L, 2L, 3L, 4L), m); + } + + @Test + public void testFromString() { + AnyLongObjectId id = new LongObjectId(1L, 2L, 3L, 4L); + MutableLongObjectId m = new MutableLongObjectId(); + m.fromString(id.name()); + assertEquals(id, m); + } + + @Test + public void testFromStringByte() { + AnyLongObjectId id = new LongObjectId(1L, 2L, 3L, 4L); + MutableLongObjectId m = new MutableLongObjectId(); + byte[] buf = new byte[64]; + id.copyTo(buf, 0); + m.fromString(buf, 0); + assertEquals(id, m); + } + + @Test + public void testCopy() { + MutableLongObjectId m = new MutableLongObjectId(); + m.fromRaw(new long[] { 1L, 2L, 3L, 4L }); + assertEquals(m, new MutableLongObjectId(m)); + } + + @Test + public void testToObjectId() { + MutableLongObjectId m = new MutableLongObjectId(); + m.fromRaw(new long[] { 1L, 2L, 3L, 4L }); + assertEquals(m, m.toObjectId()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ @@ -77,6 +77,13 @@ download-size="0" install-size="0" version="0.0.0" + unpack="false"/> + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.properties jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.properties --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.properties 2019-09-03 12:37:49.000000000 +0000 @@ -9,14 +9,13 @@ # ############################################################################### -featureName=JUnit test support for Java implementation of Git +featureName=Java implementation of Git - JUnit test support providerName=Eclipse JGit updateSiteName=Eclipse JGit Update Site # description property - text of the "Feature Description" -description=\ -JUnit test support for JGit.\n +description=JUnit test support for JGit ################ end of description property ################################## # "copyright" property - text of the "Feature Update Copyright" diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/build.properties jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/build.properties --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/build.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/build.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,4 @@ +bin.includes = feature.xml,\ + edl-v10.html,\ + feature.properties,\ + license.html diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/edl-v10.html jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/edl-v10.html --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/edl-v10.html 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/edl-v10.html 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,59 @@ + + + + + + +Eclipse Distribution License - Version 1.0 + + + + + + +

        Eclipse Distribution License - v 1.0

        + +

        Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.

        + +

        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.

        + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.properties jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.properties --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,159 @@ +############################################################################### +# Copyright (c) 20015 Matthias Sohn and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +############################################################################### + +featureName=Java implementation of Git - optional LFS support +providerName=Eclipse JGit + +updateSiteName=Eclipse JGit Update Site + +# description property - text of the "Feature Description" +description=\ +Optional LFS support.\n +################ end of description property ################################## + +# "copyright" property - text of the "Feature Update Copyright" +copyright=\ +Copyright (c) 2015, Matthias Sohn et.al.\n\ +All rights reserved. This program and the accompanying materials\n\ +are made available under the terms of the Eclipse Distribution License v1.0\n\ +which accompanies this distribution, and is available at\n\ +http://www.eclipse.org/org/documents/edl-v10.html\n +################ end of copyright property #################################### + +# "licenseURL" property - URL of the "Feature License" +# do not translate value - just change to point to a locale-specific HTML page +licenseURL=license.html + +# "license" property - text of the "Feature Update License" +# should be plain text version of license agreement pointed to be "licenseURL" +license=\ +Eclipse Foundation Software User Agreement\n\ +April 9, 2014\n\ +\n\ +Usage Of Content\n\ +\n\ +THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR\n\ +OTHER MATERIALS FOR OPEN SOURCE PROJECTS (COLLECTIVELY "CONTENT").\n\ +USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS\n\ +AGREEMENT AND/OR THE TERMS AND CONDITIONS OF LICENSE AGREEMENTS OR\n\ +NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU\n\ +AGREE THAT YOUR USE OF THE CONTENT IS GOVERNED BY THIS AGREEMENT\n\ +AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS\n\ +OR NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE\n\ +TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND CONDITIONS\n\ +OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED\n\ +BELOW, THEN YOU MAY NOT USE THE CONTENT.\n\ +\n\ +Applicable Licenses\n\ +\n\ +Unless otherwise indicated, all Content made available by the\n\ +Eclipse Foundation is provided to you under the terms and conditions of\n\ +the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is\n\ +provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html.\n\ +For purposes of the EPL, "Program" will mean the Content.\n\ +\n\ +Content includes, but is not limited to, source code, object code,\n\ +documentation and other files maintained in the Eclipse Foundation source code\n\ +repository ("Repository") in software modules ("Modules") and made available\n\ +as downloadable archives ("Downloads").\n\ +\n\ + - Content may be structured and packaged into modules to facilitate delivering,\n\ + extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"),\n\ + plug-in fragments ("Fragments"), and features ("Features").\n\ + - Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java(TM) ARchive)\n\ + in a directory named "plugins".\n\ + - A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material.\n\ + Each Feature may be packaged as a sub-directory in a directory named "features".\n\ + Within a Feature, files named "feature.xml" may contain a list of the names and version\n\ + numbers of the Plug-ins and/or Fragments associated with that Feature.\n\ + - Features may also include other Features ("Included Features"). Within a Feature, files\n\ + named "feature.xml" may contain a list of the names and version numbers of Included Features.\n\ +\n\ +The terms and conditions governing Plug-ins and Fragments should be\n\ +contained in files named "about.html" ("Abouts"). The terms and\n\ +conditions governing Features and Included Features should be contained\n\ +in files named "license.html" ("Feature Licenses"). Abouts and Feature\n\ +Licenses may be located in any directory of a Download or Module\n\ +including, but not limited to the following locations:\n\ +\n\ + - The top-level (root) directory\n\ + - Plug-in and Fragment directories\n\ + - Inside Plug-ins and Fragments packaged as JARs\n\ + - Sub-directories of the directory named "src" of certain Plug-ins\n\ + - Feature directories\n\ +\n\ +Note: if a Feature made available by the Eclipse Foundation is installed using the\n\ +Provisioning Technology (as defined below), you must agree to a license ("Feature \n\ +Update License") during the installation process. If the Feature contains\n\ +Included Features, the Feature Update License should either provide you\n\ +with the terms and conditions governing the Included Features or inform\n\ +you where you can locate them. Feature Update Licenses may be found in\n\ +the "license" property of files named "feature.properties" found within a Feature.\n\ +Such Abouts, Feature Licenses, and Feature Update Licenses contain the\n\ +terms and conditions (or references to such terms and conditions) that\n\ +govern your use of the associated Content in that directory.\n\ +\n\ +THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER\n\ +TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS.\n\ +SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):\n\ +\n\ + - Eclipse Distribution License Version 1.0 (available at http://www.eclipse.org/licenses/edl-v1.0.html)\n\ + - Common Public License Version 1.0 (available at http://www.eclipse.org/legal/cpl-v10.html)\n\ + - Apache Software License 1.1 (available at http://www.apache.org/licenses/LICENSE)\n\ + - Apache Software License 2.0 (available at http://www.apache.org/licenses/LICENSE-2.0)\n\ + - Mozilla Public License Version 1.1 (available at http://www.mozilla.org/MPL/MPL-1.1.html)\n\ +\n\ +IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR\n\ +TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License\n\ +is provided, please contact the Eclipse Foundation to determine what terms and conditions\n\ +govern that particular Content.\n\ +\n\ +\n\Use of Provisioning Technology\n\ +\n\ +The Eclipse Foundation makes available provisioning software, examples of which include,\n\ +but are not limited to, p2 and the Eclipse Update Manager ("Provisioning Technology") for\n\ +the purpose of allowing users to install software, documentation, information and/or\n\ +other materials (collectively "Installable Software"). This capability is provided with\n\ +the intent of allowing such users to install, extend and update Eclipse-based products.\n\ +Information about packaging Installable Software is available at\n\ +http://eclipse.org/equinox/p2/repository_packaging.html ("Specification").\n\ +\n\ +You may use Provisioning Technology to allow other parties to install Installable Software.\n\ +You shall be responsible for enabling the applicable license agreements relating to the\n\ +Installable Software to be presented to, and accepted by, the users of the Provisioning Technology\n\ +in accordance with the Specification. By using Provisioning Technology in such a manner and\n\ +making it available in accordance with the Specification, you further acknowledge your\n\ +agreement to, and the acquisition of all necessary rights to permit the following:\n\ +\n\ + 1. A series of actions may occur ("Provisioning Process") in which a user may execute\n\ + the Provisioning Technology on a machine ("Target Machine") with the intent of installing,\n\ + extending or updating the functionality of an Eclipse-based product.\n\ + 2. During the Provisioning Process, the Provisioning Technology may cause third party\n\ + Installable Software or a portion thereof to be accessed and copied to the Target Machine.\n\ + 3. Pursuant to the Specification, you will provide to the user the terms and conditions that\n\ + govern the use of the Installable Software ("Installable Software Agreement") and such\n\ + Installable Software Agreement shall be accessed from the Target Machine in accordance\n\ + with the Specification. Such Installable Software Agreement must inform the user of the\n\ + terms and conditions that govern the Installable Software and must solicit acceptance by\n\ + the end user in the manner prescribed in such Installable Software Agreement. Upon such\n\ + indication of agreement by the user, the provisioning Technology will complete installation\n\ + of the Installable Software.\n\ +\n\ +Cryptography\n\ +\n\ +Content may contain encryption software. The country in which you are\n\ +currently may have restrictions on the import, possession, and use,\n\ +and/or re-export to another country, of encryption software. BEFORE\n\ +using any encryption software, please check the country's laws,\n\ +regulations and policies concerning the import, possession, or use, and\n\ +re-export of encryption software, to see if this is permitted.\n\ +\n\ +Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.\n +########### end of license property ########################################## diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,50 @@ + + + + + %description + + + + %copyright + + + + %license + + + + + + + + + + + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.gitignore jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.gitignore --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.gitignore 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +target/ diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/license.html jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/license.html --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/license.html 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/license.html 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,106 @@ + + + + +Eclipse Foundation Software User Agreement + + + +

        Eclipse Foundation Software User Agreement

        +

        April 9, 2014

        + +

        Usage Of Content

        + +

        THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.

        + +

        Applicable Licenses

        + +

        Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

        + +

        Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse Foundation source code + repository ("Repository") in software modules ("Modules") and made available as downloadable archives ("Downloads").

        + +
          +
        • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features").
        • +
        • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java ARchive) in a directory named "plugins".
        • +
        • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins + and/or Fragments associated with that Feature.
        • +
        • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of Included Features.
        • +
        + +

        The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and +Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module +including, but not limited to the following locations:

        + +
          +
        • The top-level (root) directory
        • +
        • Plug-in and Fragment directories
        • +
        • Inside Plug-ins and Fragments packaged as JARs
        • +
        • Sub-directories of the directory named "src" of certain Plug-ins
        • +
        • Feature directories
        • +
        + +

        Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined below), you must agree to a license ("Feature Update License") during the +installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature. +Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in +that directory.

        + +

        THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

        + + + +

        IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

        + + +

        Use of Provisioning Technology

        + +

        The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and the Eclipse + Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, documentation, information and/or + other materials (collectively "Installable Software"). This capability is provided with the intent of allowing such users to + install, extend and update Eclipse-based products. Information about packaging Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html + ("Specification").

        + +

        You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for enabling the + applicable license agreements relating to the Installable Software to be presented to, and accepted by, the users of the Provisioning Technology + in accordance with the Specification. By using Provisioning Technology in such a manner and making it available in accordance with the + Specification, you further acknowledge your agreement to, and the acquisition of all necessary rights to permit the following:

        + +
          +
        1. A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology + on a machine ("Target Machine") with the intent of installing, extending or updating the functionality of an Eclipse-based + product.
        2. +
        3. During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion thereof to be + accessed and copied to the Target Machine.
        4. +
        5. Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the Installable + Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed from the Target + Machine in accordance with the Specification. Such Installable Software Agreement must inform the user of the terms and conditions that govern + the Installable Software and must solicit acceptance by the end user in the manner prescribed in such Installable Software Agreement. Upon such + indication of agreement by the user, the provisioning Technology will complete installation of the Installable Software.
        6. +
        + +

        Cryptography

        + +

        Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.

        + +

        Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.

        + + + \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,82 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + jgit.tycho.parent + 4.11.9.201909030838-r + + + org.eclipse.jgit.feature + org.eclipse.jgit.lfs + eclipse-feature + + JGit - Optional LFS support + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.lfs + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.lfs.server + ${project.version} + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.project jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.project --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.project 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.project 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,17 @@ + + + org.eclipse.jgit.lfs.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.resources.prefs jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.resources.prefs --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.resources.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.resources.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +#Fri Jun 18 23:33:45 CEST 2010 +eclipse.preferences.version=1 +encoding/=UTF-8 diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.runtime.prefs jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.runtime.prefs --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.runtime.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.core.runtime.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +#Fri Jun 18 23:33:45 CEST 2010 +eclipse.preferences.version=1 +line.separator=\n diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,4 @@ +#Tue Jul 19 20:11:28 CEST 2011 +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +#Tue Jul 19 20:11:28 CEST 2011 +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.properties jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.properties --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.properties 2019-09-03 12:37:49.000000000 +0000 @@ -8,14 +8,14 @@ # ############################################################################### -featureName=Command Line Interface for Java implementation of Git +featureName=Java implementation of Git - Command Line Interface providerName=Eclipse JGit updateSiteName=Eclipse JGit Update Site # description property - text of the "Feature Description" description=\ -Command line interface for a pure Java implementation of the Git version control system.\n +Command line interface for a pure Java implementation of the Git version control system ################ end of description property ################################## # "copyright" property - text of the "Feature Update Copyright" diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ @@ -26,8 +26,13 @@ id="org.eclipse.jgit" version="0.0.0"/> + + - + + org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties 2019-09-03 12:37:49.000000000 +0000 @@ -8,7 +8,7 @@ # ############################################################################### -featureName=Command Line Interface for Java implementation of Git - Source Code +featureName=Java implementation of Git - Command Line Interface - Source Code providerName=Eclipse JGit updateSiteName=Eclipse JGit Update Site diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml 2019-09-03 12:37:49.000000000 +0000 @@ -21,9 +21,12 @@ - + + + + - JGit + Java implementation of Git diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.repository @@ -70,6 +70,16 @@ ${project.version} + org.eclipse.jgit + org.eclipse.jgit.lfs + ${project.version} + + + org.eclipse.jgit + org.eclipse.jgit.lfs.server + ${project.version} + + org.eclipse.jgit org.eclipse.jgit.pgm ${project.version} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml 2019-09-03 12:37:49.000000000 +0000 @@ -2,7 +2,7 @@ diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -50,7 +50,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.feature diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -target "jgit-4.3" with source configurePhase - -include "projects/jetty-9.2.13.tpd" -include "orbit/R20150124073747-Luna-SR2.tpd" - -location "http://download.eclipse.org/releases/kepler/" { - org.eclipse.osgi lazy -} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -target "jgit-4.4" with source configurePhase - -include "projects/jetty-9.2.13.tpd" -include "orbit/R20150124073747-Luna-SR2.tpd" - -location "http://download.eclipse.org/releases/luna/" { - org.eclipse.osgi lazy -} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target 2019-09-03 12:37:49.000000000 +0000 @@ -1,63 +1,71 @@ - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - + + + + + + - - - - + + + + - - - - - - + + + + + + + + - - - - + + + + + + - - + + - + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -1,7 +1,7 @@ target "jgit-4.5" with source configurePhase -include "projects/jetty-9.2.13.tpd" -include "orbit/R20150821153341-Mars.tpd" +include "projects/jetty-9.4.8.tpd" +include "orbit/R20180606145124-Photon.tpd" location "http://download.eclipse.org/releases/mars/" { org.eclipse.osgi lazy diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ +target "jgit-4.6" with source configurePhase + +include "projects/jetty-9.4.8.tpd" +include "orbit/R20180606145124-Photon.tpd" + +location "http://download.eclipse.org/releases/neon/" { + org.eclipse.osgi lazy +} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ +target "jgit-4.7" with source configurePhase + +include "projects/jetty-9.4.8.tpd" +include "orbit/R20180606145124-Photon.tpd" + +location "http://download.eclipse.org/releases/oxygen/" { + org.eclipse.osgi lazy +} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ +target "jgit-4.8" with source configurePhase + +include "projects/jetty-9.4.8.tpd" +include "orbit/R20180606145124-Photon.tpd" + +location "http://download.eclipse.org/releases/photon/" { + org.eclipse.osgi lazy +} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -2,4 +2,4 @@ Bundle-ManifestVersion: 2 Bundle-Name: JGit Target Platform Bundle Bundle-SymbolicName: org.eclipse.jgit.target -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150124073747-Luna-SR2.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150124073747-Luna-SR2.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150124073747-Luna-SR2.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150124073747-Luna-SR2.tpd 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -target "R20150124073747" with source configurePhase -// see http://download.eclipse.org/tools/orbit/downloads/ - -location "http://download.eclipse.org/tools/orbit/downloads/drops/R20150124073747/repository/" { - org.apache.ant [1.9.2.v201404171502,1.9.2.v201404171502] - org.apache.ant.source [1.9.2.v201404171502,1.9.2.v201404171502] - org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400] - org.apache.commons.compress.source [1.6.0.v201310281400,1.6.0.v201310281400] - org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721] - org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721] - org.apache.httpcomponents.httpcore [4.3.3.v201411290715,4.3.3.v201411290715] - org.apache.httpcomponents.httpcore.source [4.3.3.v201411290715,4.3.3.v201411290715] - org.apache.httpcomponents.httpclient [4.3.6.v201411290715,4.3.6.v201411290715] - org.apache.httpcomponents.httpclient.source [4.3.6.v201411290715,4.3.6.v201411290715] - org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] - org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] - org.kohsuke.args4j [2.0.21.v201301150030,2.0.21.v201301150030] - org.kohsuke.args4j.source [2.0.21.v201301150030,2.0.21.v201301150030] - org.hamcrest.core [1.3.0.v201303031735,1.3.0.v201303031735] - org.hamcrest.core.source [1.3.0.v201303031735,1.3.0.v201303031735] - javaewah [0.7.9.v201401101600,0.7.9.v201401101600] - javaewah.source [0.7.9.v201401101600,0.7.9.v201401101600] - org.objenesis [1.0.0.v201105211943,1.0.0.v201105211943] - org.objenesis.source [1.0.0.v201105211943,1.0.0.v201105211943] - org.mockito [1.8.4.v201303031500,1.8.4.v201303031500] - org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500] - com.jcraft.jsch [0.1.51.v201410302000,0.1.51.v201410302000] - com.jcraft.jsch.source [0.1.51.v201410302000,0.1.51.v201410302000] - org.junit [4.11.0.v201303080030,4.11.0.v201303080030] - org.junit.source [4.11.0.v201303080030,4.11.0.v201303080030] - javax.servlet [3.1.0.v20140303-1611,3.1.0.v20140303-1611] - javax.servlet.source [3.1.0.v20140303-1611,3.1.0.v20140303-1611] - org.tukaani.xz [1.3.0.v201308270617,1.3.0.v201308270617] - org.tukaani.xz.source [1.3.0.v201308270617,1.3.0.v201308270617] - org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250] - org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250] - org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200] - org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200] -} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20150821153341-Mars.tpd 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -target "R20150821153341-Mars" with source configurePhase -// see http://download.eclipse.org/tools/orbit/downloads/ - -location "http://download.eclipse.org/tools/orbit/downloads/drops/R20150821153341/repository/" { - org.apache.ant [1.9.4.v201504302020,1.9.4.v201504302020] - org.apache.ant.source [1.9.4.v201504302020,1.9.4.v201504302020] - org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400] - org.apache.commons.compress.source [1.6.0.v201310281400,1.6.0.v201310281400] - org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721] - org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721] - org.apache.httpcomponents.httpcore [4.3.3.v201411290715,4.3.3.v201411290715] - org.apache.httpcomponents.httpcore.source [4.3.3.v201411290715,4.3.3.v201411290715] - org.apache.httpcomponents.httpclient [4.3.6.v201411290715,4.3.6.v201411290715] - org.apache.httpcomponents.httpclient.source [4.3.6.v201411290715,4.3.6.v201411290715] - org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] - org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] - org.kohsuke.args4j [2.0.21.v201301150030,2.0.21.v201301150030] - org.kohsuke.args4j.source [2.0.21.v201301150030,2.0.21.v201301150030] - org.hamcrest.core [1.3.0.v201303031735,1.3.0.v201303031735] - org.hamcrest.core.source [1.3.0.v201303031735,1.3.0.v201303031735] - javaewah [0.7.9.v201401101600,0.7.9.v201401101600] - javaewah.source [0.7.9.v201401101600,0.7.9.v201401101600] - org.objenesis [1.0.0.v201505121915,1.0.0.v201505121915] - org.objenesis.source [1.0.0.v201505121915,1.0.0.v201505121915] - org.mockito [1.8.4.v201303031500,1.8.4.v201303031500] - org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500] - com.jcraft.jsch [0.1.53.v201508180515,0.1.53.v201508180515] - com.jcraft.jsch.source [0.1.53.v201508180515,0.1.53.v201508180515] - org.junit [4.11.0.v201303080030,4.11.0.v201303080030] - org.junit.source [4.11.0.v201303080030,4.11.0.v201303080030] - javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] - javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] - org.tukaani.xz [1.3.0.v201308270617,1.3.0.v201308270617] - org.tukaani.xz.source [1.3.0.v201308270617,1.3.0.v201308270617] - org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250] - org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250] - org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200] - org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200] -} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180206163158-Oxygen.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180206163158-Oxygen.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180206163158-Oxygen.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180206163158-Oxygen.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,46 @@ +target "R20180206163158-Oxygen" with source configurePhase +// see http://download.eclipse.org/tools/orbit/downloads/ + +location "http://download.eclipse.org/tools/orbit/downloads/drops/R20180206163158/repository" { + org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327] + org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327] + org.apache.commons.codec [1.9.0.v20170208-1614,1.9.0.v20170208-1614] + org.apache.commons.codec.source [1.9.0.v20170208-1614,1.9.0.v20170208-1614] + org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400] + org.apache.commons.compress.source [1.6.0.v201310281400,1.6.0.v201310281400] + org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721] + org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721] + org.apache.httpcomponents.httpcore [4.4.6.v20170210-0925,4.4.6.v20170210-0925] + org.apache.httpcomponents.httpcore.source [4.4.6.v20170210-0925,4.4.6.v20170210-0925] + org.apache.httpcomponents.httpclient [4.5.2.v20170210-0925,4.5.2.v20170210-0925] + org.apache.httpcomponents.httpclient.source [4.5.2.v20170210-0925,4.5.2.v20170210-0925] + org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] + org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.hamcrest.core [1.3.0.v201303031735,1.3.0.v201303031735] + org.hamcrest.core.source [1.3.0.v201303031735,1.3.0.v201303031735] + org.hamcrest.library [1.3.0.v201505072020,1.3.0.v201505072020] + org.hamcrest.library.source [1.3.0.v201505072020,1.3.0.v201505072020] + javaewah [1.1.6.v20160919-1400,1.1.6.v20160919-1400] + javaewah.source [1.1.6.v20160919-1400,1.1.6.v20160919-1400] + org.objenesis [1.0.0.v201505121915,1.0.0.v201505121915] + org.objenesis.source [1.0.0.v201505121915,1.0.0.v201505121915] + org.mockito [1.8.4.v201303031500,1.8.4.v201303031500] + org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500] + com.google.gson [2.2.4.v201311231704,2.2.4.v201311231704] + com.jcraft.jsch [0.1.54.v20170116-1932,0.1.54.v20170116-1932] + com.jcraft.jsch.source [0.1.54.v20170116-1932,0.1.54.v20170116-1932] + org.junit [4.12.0.v201504281640,4.12.0.v201504281640] + org.junit.source [4.12.0.v201504281640,4.12.0.v201504281640] + javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] + javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] + org.tukaani.xz [1.3.0.v201308270617,1.3.0.v201308270617] + org.tukaani.xz.source [1.3.0.v201308270617,1.3.0.v201308270617] + org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250] + org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250] + org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200] + org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200] + com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] + com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] +} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,47 @@ +target "R20180606145124-Photon" with source configurePhase +// see http://download.eclipse.org/tools/orbit/downloads/ + +location "http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository" { + org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327] + org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327] + org.apache.commons.codec [1.9.0.v20170208-1614,1.9.0.v20170208-1614] + org.apache.commons.codec.source [1.9.0.v20170208-1614,1.9.0.v20170208-1614] + org.apache.commons.compress [1.15.0.v20180119-1613,1.15.0.v20180119-1613] + org.apache.commons.compress.source [1.15.0.v20180119-1613,1.15.0.v20180119-1613s] + org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721] + org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721] + org.apache.httpcomponents.httpcore [4.4.6.v20170210-0925,4.4.6.v20170210-0925] + org.apache.httpcomponents.httpcore.source [4.4.6.v20170210-0925,4.4.6.v20170210-0925] + org.apache.httpcomponents.httpclient [4.5.2.v20180410-1551,4.5.2.v20180410-1551] + org.apache.httpcomponents.httpclient.source [4.5.2.v20180410-1551,4.5.2.v20180410-1551] + org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] + org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + javaewah [1.1.6.v20160919-1400,1.1.6.v20160919-1400] + javaewah.source [1.1.6.v20160919-1400,1.1.6.v20160919-1400] + org.objenesis [1.0.0.v201505121915,1.0.0.v201505121915] + org.objenesis.source [1.0.0.v201505121915,1.0.0.v201505121915] + org.mockito [1.8.4.v201303031500,1.8.4.v201303031500] + org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500] + com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110] + com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110] + com.jcraft.jsch [0.1.54.v20170116-1932,0.1.54.v20170116-1932] + com.jcraft.jsch.source [0.1.54.v20170116-1932,0.1.54.v20170116-1932] + org.junit [4.12.0.v201504281640,4.12.0.v201504281640] + org.junit.source [4.12.0.v201504281640,4.12.0.v201504281640] + javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] + javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] + org.tukaani.xz [1.6.0.v20170629-1752,1.6.0.v20170629-1752] + org.tukaani.xz.source [1.6.0.v20170629-1752,1.6.0.v20170629-1752] + org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250] + org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250] + org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200] + org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200] + com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] + com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] +} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,7 @@ org.eclipse.jgit jgit.tycho.parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.target diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -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 -Nru jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd --- jgit-4.1.2/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,20 @@ +target "jetty-9.4.8" with source configurePhase + +location jetty-9.4.8 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121" { + org.eclipse.jetty.client [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.client.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.continuation [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.continuation.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.http [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.http.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.io [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.io.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.security [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.security.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.server [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.server.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.servlet [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.servlet.source [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.util [9.4.8.v20171121,9.4.8.v20171121] + org.eclipse.jetty.util.source [9.4.8.v20171121,9.4.8.v20171121] +} diff -Nru jgit-4.1.2/org.eclipse.jgit.packaging/pom.xml jgit-4.11.9/org.eclipse.jgit.packaging/pom.xml --- jgit-4.1.2/org.eclipse.jgit.packaging/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.packaging/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ + + + + org.bouncycastle + bcprov-jdk15on + 1.52 + test + + org.hamcrest hamcrest-library @@ -101,6 +110,24 @@ + + + + test.long + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djgit.test.long=true + + + + + + + src/ tst/ @@ -127,6 +154,10 @@ maven-surefire-plugin -Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory} + + **/*Test.java + **/*Tests.java + diff -Nru jgit-4.1.2/org.eclipse.jgit.test/.project jgit-4.11.9/org.eclipse.jgit.test/.project --- jgit-4.1.2/org.eclipse.jgit.test/.project 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/.project 2019-09-03 12:37:49.000000000 +0000 @@ -2,9 +2,6 @@ org.eclipse.jgit.test - - org.eclipse.jgit.java7 - org.eclipse.jdt.core.javabuilder diff -Nru jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java jgit-4.11.9/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java --- jgit-4.1.2/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.events; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * A {@link WorkingTreeModifiedListener} that can be used in tests to check + * expected events. + */ +public class ChangeRecorder implements WorkingTreeModifiedListener { + + public static final String[] EMPTY = new String[0]; + + private Set modified = new HashSet<>(); + + private Set deleted = new HashSet<>(); + + private int eventCount; + + @Override + public void onWorkingTreeModified(WorkingTreeModifiedEvent event) { + eventCount++; + modified.removeAll(event.getDeleted()); + deleted.removeAll(event.getModified()); + modified.addAll(event.getModified()); + deleted.addAll(event.getDeleted()); + } + + private String[] getModified() { + return modified.toArray(new String[modified.size()]); + } + + private String[] getDeleted() { + return deleted.toArray(new String[deleted.size()]); + } + + private void reset() { + eventCount = 0; + modified.clear(); + deleted.clear(); + } + + public void assertNoEvent() { + assertEquals("Unexpected WorkingTreeModifiedEvent ", 0, eventCount); + } + + public void assertEvent(String[] expectedModified, + String[] expectedDeleted) { + String[] actuallyModified = getModified(); + String[] actuallyDeleted = getDeleted(); + Arrays.sort(actuallyModified); + Arrays.sort(expectedModified); + Arrays.sort(actuallyDeleted); + Arrays.sort(expectedDeleted); + assertArrayEquals("Unexpected modifications reported", expectedModified, + actuallyModified); + assertArrayEquals("Unexpected deletions reported", expectedDeleted, + actuallyDeleted); + reset(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java jgit-4.11.9/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java --- jgit-4.1.2/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,8 +47,9 @@ import java.util.Set; public class Sets { + @SafeVarargs public static Set of(T... elements) { - Set ret = new HashSet(); + Set ret = new HashSet<>(); for (T element : elements) ret.add(element); return ret; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tests.bzl jgit-4.11.9/org.eclipse.jgit.test/tests.bzl --- jgit-4.1.2/org.eclipse.jgit.test/tests.bzl 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tests.bzl 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,56 @@ +load( + "@com_googlesource_gerrit_bazlets//tools:junit.bzl", + "junit_tests", +) + +def tests(tests): + for src in tests: + name = src[len("tst/"):len(src) - len(".java")].replace("/", "_") + labels = [] + if name.startswith("org_eclipse_jgit_"): + l = name[len("org.eclipse.jgit_"):] + if l.startswith("internal_storage_"): + l = l[len("internal.storage_"):] + i = l.find("_") + if i > 0: + labels.append(l[:i]) + else: + labels.append(i) + if "lib" not in labels: + labels.append("lib") + + additional_deps = [] + if src.endswith("RootLocaleTest.java"): + additional_deps = [ + "//org.eclipse.jgit.pgm:pgm", + "//org.eclipse.jgit.ui:ui", + ] + if src.endswith("WalkEncryptionTest.java"): + additional_deps = [ + "//org.eclipse.jgit:insecure_cipher_factory", + ] + if src.endswith("OpenSshConfigTest.java"): + additional_deps = [ + "//lib:jsch", + ] + if src.endswith("JschConfigSessionFactoryTest.java"): + additional_deps = [ + "//lib:jsch", + ] + + junit_tests( + name = name, + tags = labels, + srcs = [src], + deps = additional_deps + [ + ":helpers", + ":tst_rsrc", + "//lib:javaewah", + "//lib:junit", + "//lib:slf4j-api", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.junit:junit", + "//org.eclipse.jgit.lfs:jgit-lfs", + ], + jvm_flags = ["-Xmx256m", "-Dfile.encoding=UTF-8"], + ) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +public class AbstractRemoteCommandTest extends RepositoryTestCase { + + protected static final String REMOTE_NAME = "test"; + + protected RemoteConfig setupRemote() + throws IOException, URISyntaxException { + // create another repository + Repository remoteRepository = createWorkRepository(); + + // set it up as a remote to this repository + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, REMOTE_NAME); + + RefSpec refSpec = new RefSpec(); + refSpec = refSpec.setForceUpdate(true); + refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", + Constants.R_REMOTES + REMOTE_NAME + "/*"); + remoteConfig.addFetchRefSpec(refSpec); + + URIish uri = new URIish( + remoteRepository.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + + remoteConfig.update(config); + config.save(); + + return remoteConfig; + } + + protected void assertRemoteConfigEquals(RemoteConfig expected, + RemoteConfig actual) { + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getURIs(), actual.getURIs()); + assertEquals(expected.getPushURIs(), actual.getPushURIs()); + assertEquals(expected.getFetchRefSpecs(), actual.getFetchRefSpecs()); + assertEquals(expected.getPushRefSpecs(), actual.getPushRefSpecs()); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,40 +43,61 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.util.FileUtils.RECURSIVE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.util.Set; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lfs.BuiltinLFS; import org.eclipse.jgit.lib.ConfigConstants; 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.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +@RunWith(Theories.class) public class AddCommandTest extends RepositoryTestCase { + @DataPoints + public static boolean[] sleepBeforeAddOptions = { true, false }; + + + @Override + public void setUp() throws Exception { + BuiltinLFS.register(); + super.setUp(); + } @Test public void testAddNothing() throws GitAPIException { - Git git = new Git(db); - - try { + try (Git git = new Git(db)) { git.add().call(); fail("Expected IllegalArgumentException"); } catch (NoFilepatternException e) { @@ -87,28 +108,380 @@ @Test public void testAddNonExistingSingleFile() throws GitAPIException { - Git git = new Git(db); - - DirCache dc = git.add().addFilepattern("a.txt").call(); - assertEquals(0, dc.getEntryCount()); - + try (Git git = new Git(db)) { + DirCache dc = git.add().addFilepattern("a.txt").call(); + assertEquals(0, dc.getEntryCount()); + } } @Test public void testAddExistingSingleFile() throws IOException, GitAPIException { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - Git git = new Git(db); + try (Git git = new Git(db)) { + git.add().addFilepattern("a.txt").call(); - git.add().addFilepattern("a.txt").call(); + assertEquals( + "[a.txt, mode:100644, content:content]", + indexState(CONTENT)); + } + } - assertEquals( - "[a.txt, mode:100644, content:content]", - indexState(CONTENT)); + @Test + public void testCleanFilter() throws IOException, GitAPIException { + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .call(); + + assertEquals( + "[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]", + indexState(CONTENT)); + } + } + + @Theory + public void testBuiltinFilters(boolean sleepBeforeAdd) + throws IOException, + GitAPIException, InterruptedException { + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + File script = writeTempFile("sed s/o/e/g"); + File f = writeTrashFile("src/a.txt", "foo\n"); + + try (Git git = new Git(db)) { + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern(".gitattributes").call(); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "clean", + "sh " + slashify(script.getPath())); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.setBoolean("filter", "lfs", "useJGitBuiltin", true); + config.save(); + + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .addFilepattern(".gitattributes").call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]", + indexState(CONTENT)); + + RevCommit c1 = git.commit().setMessage("c1").call(); + assertTrue(git.status().call().isClean()); + f = writeTrashFile("src/a.txt", "foobar\n"); + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern("src/a.txt").call(); + git.commit().setMessage("c2").call(); + assertTrue(git.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]", + indexState(CONTENT)); + assertEquals("foobar\n", read("src/a.txt")); + git.checkout().setName(c1.getName()).call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]", + indexState(CONTENT)); + assertEquals( + "foo\n", read("src/a.txt")); + } + } + + @Theory + public void testBuiltinCleanFilter(boolean sleepBeforeAdd) + throws IOException, GitAPIException, InterruptedException { + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + File script = writeTempFile("sed s/o/e/g"); + File f = writeTrashFile("src/a.txt", "foo\n"); + + // unregister the smudge filter. Only clean filter should be builtin + FilterCommandRegistry.unregister( + org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX + + "lfs/smudge"); + + try (Git git = new Git(db)) { + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern(".gitattributes").call(); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "clean", + "sh " + slashify(script.getPath())); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.setBoolean("filter", "lfs", "useJGitBuiltin", true); + config.save(); + + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .addFilepattern(".gitattributes").call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]", + indexState(CONTENT)); + + RevCommit c1 = git.commit().setMessage("c1").call(); + assertTrue(git.status().call().isClean()); + f = writeTrashFile("src/a.txt", "foobar\n"); + if (!sleepBeforeAdd) { + fsTick(f); + } + git.add().addFilepattern("src/a.txt").call(); + git.commit().setMessage("c2").call(); + assertTrue(git.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]", + indexState(CONTENT)); + assertEquals("foobar\n", read("src/a.txt")); + git.checkout().setName(c1.getName()).call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]", + indexState(CONTENT)); + // due to lfs clean filter but dummy smudge filter we expect strange + // content. The smudge filter converts from real content to pointer + // file content (starting with "version ") but the smudge filter + // replaces 'o' by 'e' which results in a text starting with + // "versien " + assertEquals( + "versien https://git-lfs.github.cem/spec/v1\neid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n", + read("src/a.txt")); + } + } + + @Test + public void testAttributesWithTreeWalkFilter() + throws IOException, GitAPIException { + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + writeTrashFile("src/a.tmp", "foo"); + writeTrashFile("src/a.txt", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "clean", + "sh " + slashify(script.getPath())); + config.save(); + + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("attr").call(); + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .addFilepattern(".gitattributes").call(); + git.commit().setMessage("c1").call(); + assertTrue(git.status().call().isClean()); + } + } + + @Test + public void testAttributesConflictingMatch() throws Exception { + writeTrashFile(".gitattributes", "foo/** crlf=input\n*.jar binary"); + writeTrashFile("foo/bar.jar", "\r\n"); + // We end up with attributes [binary -diff -merge -text crlf=input]. + // crlf should have no effect when -text is present. + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + assertEquals( + "[.gitattributes, mode:100644, content:foo/** crlf=input\n*.jar binary]" + + "[foo/bar.jar, mode:100644, content:\r\n]", + indexState(CONTENT)); + } + } + + @Test + public void testCleanFilterEnvironment() + throws IOException, GitAPIException { + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + writeTrashFile("src/a.txt", "foo"); + File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + git.add().addFilepattern("src/a.txt").call(); + + String gitDir = db.getDirectory().getAbsolutePath(); + assertEquals("[src/a.txt, mode:100644, content:" + gitDir + + "\n]", indexState(CONTENT)); + assertTrue(new File(db.getWorkTree(), "xyz").exists()); + } + } + + @Test + public void testMultipleCleanFilter() throws IOException, GitAPIException { + writeTrashFile(".gitattributes", + "*.txt filter=tstFilter\n*.tmp filter=tstFilter2"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.tmp", "foo\n"); + writeTrashFile("src/a.txt", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + File script2 = writeTempFile("sed s/f/x/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.setString("filter", "tstFilter2", "clean", + "sh " + slashify(script2.getPath())); + config.save(); + + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .call(); + + assertEquals( + "[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]", + indexState(CONTENT)); + + // TODO: multiple clean filters for one file??? + } + } + + /** + * The path of an added file name contains ';' and afterwards malicious + * commands. Make sure when calling filter commands to properly escape the + * filenames + * + * @throws IOException + * @throws GitAPIException + */ + @Test + public void testCommandInjection() throws IOException, GitAPIException { + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("; echo virus", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath()) + " %f"); + writeTrashFile(".gitattributes", "* filter=tstFilter"); + + git.add().addFilepattern("; echo virus").call(); + // Without proper escaping the content would be "feovirus". The sed + // command and the "echo virus" would contribute to the content + assertEquals("[; echo virus, mode:100644, content:fee\n]", + indexState(CONTENT)); + } + } + + @Test + public void testBadCleanFilter() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sedfoo s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(127, e.getReturnCode()); + } + } + } + + @Test + public void testBadCleanFilter2() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sed s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "shfoo " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(127, e.getReturnCode()); + } + } + } + + @Test + public void testCleanFilterReturning12() throws IOException, + GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("exit 12"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(12, e.getReturnCode()); + } + } + } + + @Test + public void testNotApplicableFilter() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sed s/o/e/g"); + + try (Git git = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "something", + "sh " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + git.add().addFilepattern("a.txt").call(); + + assertEquals("[a.txt, mode:100644, content:foo]", + indexState(CONTENT)); + } + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("AddCommandTest_", ""); + JGitTestUtil.write(f, body); + return f; } @Test @@ -116,23 +489,24 @@ GitAPIException { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("row1\r\nrow2"); - writer.close(); - - Git git = new Git(db); - db.getConfig().setString("core", null, "autocrlf", "false"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "true"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\nrow2]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "input"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\nrow2]", - indexState(CONTENT)); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("row1\r\nrow2"); + } + + try (Git git = new Git(db)) { + db.getConfig().setString("core", null, "autocrlf", "false"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "true"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\nrow2]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "input"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\nrow2]", + indexState(CONTENT)); + } } @Test @@ -145,23 +519,24 @@ data.append("row1\r\nrow2"); } String crData = data.toString(); - PrintWriter writer = new PrintWriter(file); - writer.print(crData); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print(crData); + } String lfData = data.toString().replaceAll("\r", ""); - Git git = new Git(db); - db.getConfig().setString("core", null, "autocrlf", "false"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:" + data + "]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "true"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:" + lfData + "]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "input"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:" + lfData + "]", - indexState(CONTENT)); + try (Git git = new Git(db)) { + db.getConfig().setString("core", null, "autocrlf", "false"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:" + data + "]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "true"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:" + lfData + "]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "input"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:" + lfData + "]", + indexState(CONTENT)); + } } @Test @@ -169,23 +544,24 @@ GitAPIException { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("row1\r\nrow2\u0000"); - writer.close(); - - Git git = new Git(db); - db.getConfig().setString("core", null, "autocrlf", "false"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "true"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", - indexState(CONTENT)); - db.getConfig().setString("core", null, "autocrlf", "input"); - git.add().addFilepattern("a.txt").call(); - assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", - indexState(CONTENT)); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("row1\r\nrow2\u0000"); + } + + try (Git git = new Git(db)) { + db.getConfig().setString("core", null, "autocrlf", "false"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "true"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + db.getConfig().setString("core", null, "autocrlf", "input"); + git.add().addFilepattern("a.txt").call(); + assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]", + indexState(CONTENT)); + } } @Test @@ -194,17 +570,17 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); - - Git git = new Git(db); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - git.add().addFilepattern("sub/a.txt").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("sub/a.txt").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]", - indexState(CONTENT)); + assertEquals( + "[sub/a.txt, mode:100644, content:content]", + indexState(CONTENT)); + } } @Test @@ -212,96 +588,100 @@ GitAPIException { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").call(); + try (Git git = new Git(db)) { + DirCache dc = git.add().addFilepattern("a.txt").call(); - dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); - writer = new PrintWriter(file); - writer.print("other content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("other content"); + } - dc = git.add().addFilepattern("a.txt").call(); + dc = git.add().addFilepattern("a.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:other content]", - indexState(CONTENT)); + assertEquals( + "[a.txt, mode:100644, content:other content]", + indexState(CONTENT)); + } } @Test public void testAddExistingSingleFileTwiceWithCommit() throws Exception { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").call(); + try (Git git = new Git(db)) { + DirCache dc = git.add().addFilepattern("a.txt").call(); - dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); - git.commit().setMessage("commit a.txt").call(); + git.commit().setMessage("commit a.txt").call(); - writer = new PrintWriter(file); - writer.print("other content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("other content"); + } - dc = git.add().addFilepattern("a.txt").call(); + dc = git.add().addFilepattern("a.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:other content]", - indexState(CONTENT)); + assertEquals( + "[a.txt, mode:100644, content:other content]", + indexState(CONTENT)); + } } @Test public void testAddRemovedFile() throws Exception { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").call(); + try (Git git = new Git(db)) { + DirCache dc = git.add().addFilepattern("a.txt").call(); - dc.getEntry(0).getObjectId(); - FileUtils.delete(file); + dc.getEntry(0).getObjectId(); + FileUtils.delete(file); - // is supposed to do nothing - dc = git.add().addFilepattern("a.txt").call(); + // is supposed to do nothing + dc = git.add().addFilepattern("a.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:content]", - indexState(CONTENT)); + assertEquals( + "[a.txt, mode:100644, content:content]", + indexState(CONTENT)); + } } @Test public void testAddRemovedCommittedFile() throws Exception { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } - Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").call(); + try (Git git = new Git(db)) { + DirCache dc = git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("commit a.txt").call(); + git.commit().setMessage("commit a.txt").call(); - dc.getEntry(0).getObjectId(); - FileUtils.delete(file); + dc.getEntry(0).getObjectId(); + FileUtils.delete(file); - // is supposed to do nothing - dc = git.add().addFilepattern("a.txt").call(); + // is supposed to do nothing + dc = git.add().addFilepattern("a.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:content]", - indexState(CONTENT)); + assertEquals( + "[a.txt, mode:100644, content:content]", + indexState(CONTENT)); + } } @Test @@ -310,15 +690,15 @@ File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } ObjectInserter newObjectInserter = db.newObjectInserter(); DirCache dc = db.lockDirCache(); @@ -327,14 +707,14 @@ addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0); addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1); - writer = new PrintWriter(file); - writer.print("other content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("other content"); + } addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3); - writer = new PrintWriter(file); - writer.print("our content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("our content"); + } addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2) .getObjectId(); @@ -349,35 +729,37 @@ // now the test begins - Git git = new Git(db); - dc = git.add().addFilepattern("a.txt").call(); + try (Git git = new Git(db)) { + dc = git.add().addFilepattern("a.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:our content]" + - "[b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + assertEquals( + "[a.txt, mode:100644, content:our content]" + + "[b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + } } @Test public void testAddTwoFiles() throws Exception { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); - assertEquals( - "[a.txt, mode:100644, content:content]" + - "[b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + try (Git git = new Git(db)) { + git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); + assertEquals( + "[a.txt, mode:100644, content:content]" + + "[b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + } } @Test @@ -385,22 +767,23 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "sub/b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern("sub").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]" + - "[sub/b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + try (Git git = new Git(db)) { + git.add().addFilepattern("sub").call(); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + } } @Test @@ -408,28 +791,29 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File ignoreFile = new File(db.getWorkTree(), ".gitignore"); FileUtils.createNewFile(ignoreFile); - writer = new PrintWriter(ignoreFile); - writer.print("sub/b.txt"); - writer.close(); + try (PrintWriter writer = new PrintWriter(ignoreFile)) { + writer.print("sub/b.txt"); + } File file2 = new File(db.getWorkTree(), "sub/b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern("sub").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("sub").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]", - indexState(CONTENT)); + assertEquals( + "[sub/a.txt, mode:100644, content:content]", + indexState(CONTENT)); + } } @Test @@ -437,22 +821,23 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "sub/b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern(".").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]" + - "[sub/b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + } } // the same three cases as in testAddWithParameterUpdate @@ -464,50 +849,51 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "sub/b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern("sub").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("sub").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]" + - "[sub/b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + + git.commit().setMessage("commit").call(); + + // new unstaged file sub/c.txt + File file3 = new File(db.getWorkTree(), "sub/c.txt"); + FileUtils.createNewFile(file3); + try (PrintWriter writer = new PrintWriter(file3)) { + writer.print("content c"); + } - git.commit().setMessage("commit").call(); + // file sub/a.txt is modified + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("modified content"); + } - // new unstaged file sub/c.txt - File file3 = new File(db.getWorkTree(), "sub/c.txt"); - FileUtils.createNewFile(file3); - writer = new PrintWriter(file3); - writer.print("content c"); - writer.close(); - - // file sub/a.txt is modified - writer = new PrintWriter(file); - writer.print("modified content"); - writer.close(); - - // file sub/b.txt is deleted - FileUtils.delete(file2); - - git.add().addFilepattern("sub").call(); - // change in sub/a.txt is staged - // deletion of sub/b.txt is not staged - // sub/c.txt is staged - assertEquals( - "[sub/a.txt, mode:100644, content:modified content]" + - "[sub/b.txt, mode:100644, content:content b]" + - "[sub/c.txt, mode:100644, content:content c]", - indexState(CONTENT)); + // file sub/b.txt is deleted + FileUtils.delete(file2); + + git.add().addFilepattern("sub").call(); + // change in sub/a.txt is staged + // deletion of sub/b.txt is not staged + // sub/c.txt is staged + assertEquals( + "[sub/a.txt, mode:100644, content:modified content]" + + "[sub/b.txt, mode:100644, content:content b]" + + "[sub/c.txt, mode:100644, content:content c]", + indexState(CONTENT)); + } } // file a exists in workdir and in index -> added @@ -518,81 +904,178 @@ FileUtils.mkdir(new File(db.getWorkTree(), "sub")); File file = new File(db.getWorkTree(), "sub/a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } File file2 = new File(db.getWorkTree(), "sub/b.txt"); FileUtils.createNewFile(file2); - writer = new PrintWriter(file2); - writer.print("content b"); - writer.close(); + try (PrintWriter writer = new PrintWriter(file2)) { + writer.print("content b"); + } - Git git = new Git(db); - git.add().addFilepattern("sub").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("sub").call(); - assertEquals( - "[sub/a.txt, mode:100644, content:content]" + - "[sub/b.txt, mode:100644, content:content b]", - indexState(CONTENT)); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/b.txt, mode:100644, content:content b]", + indexState(CONTENT)); + + git.commit().setMessage("commit").call(); + + // new unstaged file sub/c.txt + File file3 = new File(db.getWorkTree(), "sub/c.txt"); + FileUtils.createNewFile(file3); + try (PrintWriter writer = new PrintWriter(file3)) { + writer.print("content c"); + } - git.commit().setMessage("commit").call(); + // file sub/a.txt is modified + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("modified content"); + } - // new unstaged file sub/c.txt - File file3 = new File(db.getWorkTree(), "sub/c.txt"); - FileUtils.createNewFile(file3); - writer = new PrintWriter(file3); - writer.print("content c"); - writer.close(); - - // file sub/a.txt is modified - writer = new PrintWriter(file); - writer.print("modified content"); - writer.close(); - - FileUtils.delete(file2); - - // change in sub/a.txt is staged - // deletion of sub/b.txt is staged - // sub/c.txt is not staged - git.add().addFilepattern("sub").setUpdate(true).call(); - // change in sub/a.txt is staged - assertEquals( - "[sub/a.txt, mode:100644, content:modified content]", - indexState(CONTENT)); + FileUtils.delete(file2); + + // change in sub/a.txt is staged + // deletion of sub/b.txt is staged + // sub/c.txt is not staged + git.add().addFilepattern("sub").setUpdate(true).call(); + // change in sub/a.txt is staged + assertEquals( + "[sub/a.txt, mode:100644, content:modified content]", + indexState(CONTENT)); + } } @Test public void testAssumeUnchanged() throws Exception { - Git git = new Git(db); - String path = "a.txt"; - writeTrashFile(path, "content"); - git.add().addFilepattern(path).call(); - String path2 = "b.txt"; - writeTrashFile(path2, "content"); - git.add().addFilepattern(path2).call(); - git.commit().setMessage("commit").call(); - assertEquals("[a.txt, mode:100644, content:" - + "content, assume-unchanged:false]" - + "[b.txt, mode:100644, content:content, " - + "assume-unchanged:false]", indexState(CONTENT - | ASSUME_UNCHANGED)); - assumeUnchanged(path2); - assertEquals("[a.txt, mode:100644, content:content, " - + "assume-unchanged:false][b.txt, mode:100644, " - + "content:content, assume-unchanged:true]", indexState(CONTENT - | ASSUME_UNCHANGED)); - writeTrashFile(path, "more content"); - writeTrashFile(path2, "more content"); - - git.add().addFilepattern(".").call(); - - assertEquals("[a.txt, mode:100644, content:more content," - + " assume-unchanged:false][b.txt, mode:100644," - + "" + "" - + " content:content, assume-unchanged:true]", - indexState(CONTENT - | ASSUME_UNCHANGED)); + try (Git git = new Git(db)) { + String path = "a.txt"; + writeTrashFile(path, "content"); + git.add().addFilepattern(path).call(); + String path2 = "b.txt"; + writeTrashFile(path2, "content"); + git.add().addFilepattern(path2).call(); + git.commit().setMessage("commit").call(); + assertEquals("[a.txt, mode:100644, content:" + + "content, assume-unchanged:false]" + + "[b.txt, mode:100644, content:content, " + + "assume-unchanged:false]", indexState(CONTENT + | ASSUME_UNCHANGED)); + assumeUnchanged(path2); + assertEquals("[a.txt, mode:100644, content:content, " + + "assume-unchanged:false][b.txt, mode:100644, " + + "content:content, assume-unchanged:true]", indexState(CONTENT + | ASSUME_UNCHANGED)); + writeTrashFile(path, "more content"); + writeTrashFile(path2, "more content"); + + git.add().addFilepattern(".").call(); + + assertEquals("[a.txt, mode:100644, content:more content," + + " assume-unchanged:false][b.txt, mode:100644," + + " content:content, assume-unchanged:true]", + indexState(CONTENT + | ASSUME_UNCHANGED)); + } + } + + @Test + public void testReplaceFileWithDirectory() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("df", "before replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df, mode:100644, content:before replacement]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "df")); + writeTrashFile("df/f", "after replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df/f, mode:100644, content:after replacement]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceDirectoryWithFile() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("df/f", "before replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df/f, mode:100644, content:before replacement]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE); + writeTrashFile("df", "after replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df, mode:100644, content:after replacement]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceFileByPartOfDirectory() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("src/main", "df", "before replacement"); + writeTrashFile("src/main", "z", "z"); + writeTrashFile("z", "z2"); + git.add().addFilepattern("src/main/df") + .addFilepattern("src/main/z") + .addFilepattern("z") + .call(); + assertEquals( + "[src/main/df, mode:100644, content:before replacement]" + + "[src/main/z, mode:100644, content:z]" + + "[z, mode:100644, content:z2]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "src/main/df")); + writeTrashFile("src/main/df", "a", "after replacement"); + writeTrashFile("src/main/df", "b", "unrelated file"); + git.add().addFilepattern("src/main/df/a").call(); + assertEquals( + "[src/main/df/a, mode:100644, content:after replacement]" + + "[src/main/z, mode:100644, content:z]" + + "[z, mode:100644, content:z2]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceDirectoryConflictsWithFile() + throws IOException, NoFilepatternException, GitAPIException { + DirCache dc = db.lockDirCache(); + try (ObjectInserter oi = db.newObjectInserter()) { + DirCacheBuilder builder = dc.builder(); + File f = writeTrashFile("a", "df", "content"); + addEntryToBuilder("a", f, oi, builder, 1); + + f = writeTrashFile("a", "df", "other content"); + addEntryToBuilder("a/df", f, oi, builder, 3); + + f = writeTrashFile("a", "df", "our content"); + addEntryToBuilder("a/df", f, oi, builder, 2); + + f = writeTrashFile("z", "z"); + addEntryToBuilder("z", f, oi, builder, 0); + builder.commit(); + } + assertEquals( + "[a, mode:100644, stage:1, content:content]" + + "[a/df, mode:100644, stage:2, content:our content]" + + "[a/df, mode:100644, stage:3, content:other content]" + + "[z, mode:100644, content:z]", + indexState(CONTENT)); + + try (Git git = new Git(db)) { + FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE); + writeTrashFile("a", "merged"); + git.add().addFilepattern("a").call(); + assertEquals("[a, mode:100644, content:merged]" + + "[z, mode:100644, content:z]", + indexState(CONTENT)); + } } @Test @@ -604,32 +1087,43 @@ FS executableFs = new FS() { + @Override public boolean supportsExecute() { return true; } + @Override public boolean setExecute(File f, boolean canExec) { return true; } + @Override public ProcessBuilder runInShell(String cmd, String[] args) { return null; } + @Override public boolean retryFailedLockFileCommit() { return false; } + @Override public FS newInstance() { return this; } + @Override protected File discoverGitExe() { return null; } + @Override public boolean canExecute(File f) { - return true; + try { + return read(f).startsWith("binary:"); + } catch (IOException e) { + return false; + } } @Override @@ -640,70 +1134,135 @@ Git git = Git.open(db.getDirectory(), executableFs); String path = "a.txt"; + String path2 = "a.sh"; writeTrashFile(path, "content"); - git.add().addFilepattern(path).call(); + writeTrashFile(path2, "binary: content"); + git.add().addFilepattern(path).addFilepattern(path2).call(); RevCommit commit1 = git.commit().setMessage("commit").call(); - TreeWalk walk = TreeWalk.forPath(db, path, commit1.getTree()); - assertNotNull(walk); - assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(commit1.getTree()); + walk.next(); + assertEquals(path2, walk.getPathString()); + assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); + walk.next(); + assertEquals(path, walk.getPathString()); + assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0)); + } - FS nonExecutableFs = new FS() { + config = db.getConfig(); + config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_FILEMODE, false); + config.save(); - public boolean supportsExecute() { - return false; - } + Git git2 = Git.open(db.getDirectory(), executableFs); + writeTrashFile(path2, "content2"); + writeTrashFile(path, "binary: content2"); + git2.add().addFilepattern(path).addFilepattern(path2).call(); + RevCommit commit2 = git2.commit().setMessage("commit2").call(); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(commit2.getTree()); + walk.next(); + assertEquals(path2, walk.getPathString()); + assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); + walk.next(); + assertEquals(path, walk.getPathString()); + assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0)); + } + } - public boolean setExecute(File f, boolean canExec) { - return false; - } + @Test + public void testAddGitlink() throws Exception { + createNestedRepo("git-link-dir"); + try (Git git = new Git(db)) { + git.add().addFilepattern("git-link-dir").call(); + + assertEquals( + "[git-link-dir, mode:160000]", + indexState(0)); + Set untrackedFiles = git.status().call().getUntracked(); + assert (untrackedFiles.isEmpty()); + } - public ProcessBuilder runInShell(String cmd, String[] args) { - return null; - } + } - public boolean retryFailedLockFileCommit() { - return false; - } + @Test + public void testAddSubrepoWithDirNoGitlinks() throws Exception { + createNestedRepo("nested-repo"); - public FS newInstance() { - return this; - } + // Set DIR_NO_GITLINKS + StoredConfig config = db.getConfig(); + config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true); + config.save(); - protected File discoverGitExe() { - return null; - } + assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks()); - public boolean canExecute(File f) { - return false; - } + try (Git git = new Git(db)) { + git.add().addFilepattern("nested-repo").call(); - @Override - public boolean isCaseSensitive() { - return false; - } - }; + assertEquals( + "[nested-repo/README1.md, mode:100644]" + + "[nested-repo/README2.md, mode:100644]", + indexState(0)); + } - config = db.getConfig(); + // Turn off DIR_NO_GITLINKS, ensure nested-repo is still treated as + // a normal directory + // Set DIR_NO_GITLINKS config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_FILEMODE, false); + ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false); config.save(); - Git git2 = Git.open(db.getDirectory(), nonExecutableFs); - writeTrashFile(path, "content2"); - git2.add().addFilepattern(path).call(); - RevCommit commit2 = git2.commit().setMessage("commit2").call(); - walk = TreeWalk.forPath(db, path, commit2.getTree()); - assertNotNull(walk); - assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); + writeTrashFile("nested-repo", "README3.md", "content"); + + try (Git git = new Git(db)) { + git.add().addFilepattern("nested-repo").call(); + + assertEquals( + "[nested-repo/README1.md, mode:100644]" + + "[nested-repo/README2.md, mode:100644]" + + "[nested-repo/README3.md, mode:100644]", + indexState(0)); + } + } + + @Test + public void testAddGitlinkDoesNotChange() throws Exception { + createNestedRepo("nested-repo"); + + try (Git git = new Git(db)) { + git.add().addFilepattern("nested-repo").call(); + + assertEquals( + "[nested-repo, mode:160000]", + indexState(0)); + } + + // Set DIR_NO_GITLINKS + StoredConfig config = db.getConfig(); + config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true); + config.save(); + + assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks()); + + try (Git git = new Git(db)) { + git.add().addFilepattern("nested-repo").call(); + // with gitlinks ignored, we treat this as a normal directory + assertEquals( + "[nested-repo/README1.md, mode:100644][nested-repo/README2.md, mode:100644]", + indexState(0)); + } } private static DirCacheEntry addEntryToBuilder(String path, File file, ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage) throws IOException { - FileInputStream inputStream = new FileInputStream(file); - ObjectId id = newObjectInserter.insert( + ObjectId id; + try (FileInputStream inputStream = new FileInputStream(file)) { + id = newObjectInserter.insert( Constants.OBJ_BLOB, file.length(), inputStream); - inputStream.close(); + } DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setObjectId(id); entry.setFileMode(FileMode.REGULAR_FILE); @@ -724,4 +1283,25 @@ throw new IOException("could not commit"); } + private void createNestedRepo(String path) throws IOException { + File gitLinkDir = new File(db.getWorkTree(), path); + FileUtils.mkdir(gitLinkDir); + + FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder(); + nestedBuilder.setWorkTree(gitLinkDir); + + Repository nestedRepo = nestedBuilder.build(); + nestedRepo.create(); + + writeTrashFile(path, "README1.md", "content"); + writeTrashFile(path, "README2.md", "content"); + + // Commit these changes in the subrepo + try (Git git = new Git(nestedRepo)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("subrepo commit").call(); + } catch (GitAPIException e) { + throw new RuntimeException(e); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; @@ -69,23 +70,24 @@ private ApplyResult init(final String name, final boolean preExists, final boolean postExists) throws Exception { - Git git = new Git(db); - - if (preExists) { - a = new RawText(readFile(name + "_PreImage")); - write(new File(db.getDirectory().getParent(), name), - a.getString(0, a.size(), false)); - - git.add().addFilepattern(name).call(); - git.commit().setMessage("PreImage").call(); + try (Git git = new Git(db)) { + if (preExists) { + a = new RawText(readFile(name + "_PreImage")); + write(new File(db.getDirectory().getParent(), name), + a.getString(0, a.size(), false)); + + git.add().addFilepattern(name).call(); + git.commit().setMessage("PreImage").call(); + } + + if (postExists) { + b = new RawText(readFile(name + "_PostImage")); + } + + return git + .apply() + .setPatch(getTestResource(name + ".patch")).call(); } - - if (postExists) - b = new RawText(readFile(name + "_PostImage")); - - return git - .apply() - .setPatch(getTestResource(name + ".patch")).call(); } @Test @@ -146,6 +148,43 @@ } @Test + public void testModifyW() throws Exception { + ApplyResult result = init("W"); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "W"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "W"), + b.getString(0, b.size(), false)); + } + + @Test + public void testAddM1() throws Exception { + ApplyResult result = init("M1", false, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertTrue(result.getUpdatedFiles().get(0).canExecute()); + checkFile(new File(db.getWorkTree(), "M1"), + b.getString(0, b.size(), false)); + } + + @Test + public void testModifyM2() throws Exception { + ApplyResult result = init("M2", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertTrue(result.getUpdatedFiles().get(0).canExecute()); + checkFile(new File(db.getWorkTree(), "M2"), + b.getString(0, b.size(), false)); + } + + @Test + public void testModifyM3() throws Exception { + ApplyResult result = init("M3", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertFalse(result.getUpdatedFiles().get(0).canExecute()); + checkFile(new File(db.getWorkTree(), "M3"), + b.getString(0, b.size(), false)); + } + + @Test public void testModifyX() throws Exception { ApplyResult result = init("X"); assertEquals(1, result.getUpdatedFiles().size()); @@ -185,6 +224,55 @@ b.getString(0, b.size(), false)); } + @Test + public void testNonASCII() throws Exception { + ApplyResult result = init("NonASCII"); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "NonASCII"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "NonASCII"), + b.getString(0, b.size(), false)); + } + + @Test + public void testNonASCII2() throws Exception { + ApplyResult result = init("NonASCII2"); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "NonASCII2"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "NonASCII2"), + b.getString(0, b.size(), false)); + } + + @Test + public void testNonASCIIAdd() throws Exception { + ApplyResult result = init("NonASCIIAdd"); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "NonASCIIAdd"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "NonASCIIAdd"), + b.getString(0, b.size(), false)); + } + + @Test + public void testNonASCIIAdd2() throws Exception { + ApplyResult result = init("NonASCIIAdd2", false, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "NonASCIIAdd2"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "NonASCIIAdd2"), + b.getString(0, b.size(), false)); + } + + @Test + public void testNonASCIIDel() throws Exception { + ApplyResult result = init("NonASCIIDel", true, false); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "NonASCIIDel"), + result.getUpdatedFiles().get(0)); + assertFalse(new File(db.getWorkTree(), "NonASCIIDel").exists()); + } + private static byte[] readFile(final String patchFile) throws IOException { final InputStream in = getTestResource(patchFile); if (in == null) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,6 +57,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.util.StringUtils; @@ -78,6 +79,7 @@ ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format); } + @Override @After public void tearDown() { ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0)); @@ -85,108 +87,112 @@ @Test public void archiveHeadAllFiles() throws IOException, GitAPIException { - Git git = new Git(db); - writeTrashFile("file_1.txt", "content_1_1"); - git.add().addFilepattern("file_1.txt").call(); - git.commit().setMessage("create file").call(); - - writeTrashFile("file_1.txt", "content_1_2"); - writeTrashFile("file_2.txt", "content_2_2"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("updated file").call(); - - git.archive().setOutputStream(new MockOutputStream()) - .setFormat(format.SUFFIXES.get(0)) - .setTree(git.getRepository().resolve("HEAD")).call(); - - assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt")); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt")); + try (Git git = new Git(db)) { + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + writeTrashFile("file_2.txt", "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")).call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt")); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt")); + } } @Test public void archiveHeadSpecificPath() throws IOException, GitAPIException { - Git git = new Git(db); - writeTrashFile("file_1.txt", "content_1_1"); - git.add().addFilepattern("file_1.txt").call(); - git.commit().setMessage("create file").call(); - - writeTrashFile("file_1.txt", "content_1_2"); - String expectedFilePath = "some_directory/file_2.txt"; - writeTrashFile(expectedFilePath, "content_2_2"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("updated file").call(); - - git.archive().setOutputStream(new MockOutputStream()) - .setFormat(format.SUFFIXES.get(0)) - .setTree(git.getRepository().resolve("HEAD")) - .setPaths(expectedFilePath).call(); - - assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath)); - assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); + try (Git git = new Git(db)) { + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + String expectedFilePath = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath, "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")) + .setPaths(expectedFilePath).call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath)); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); + } } @Test public void archiveByIdSpecificFile() throws IOException, GitAPIException { - Git git = new Git(db); - writeTrashFile("file_1.txt", "content_1_1"); - git.add().addFilepattern("file_1.txt").call(); - RevCommit first = git.commit().setMessage("create file").call(); - - writeTrashFile("file_1.txt", "content_1_2"); - String expectedFilePath = "some_directory/file_2.txt"; - writeTrashFile(expectedFilePath, "content_2_2"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("updated file").call(); - - Map options = new HashMap<>(); - Integer opt = Integer.valueOf(42); - options.put("foo", opt); - MockOutputStream out = new MockOutputStream(); - git.archive().setOutputStream(out) - .setFormat(format.SUFFIXES.get(0)) - .setFormatOptions(options) - .setTree(first) - .setPaths("file_1.txt").call(); - - assertEquals(opt.intValue(), out.getFoo()); - assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size()); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt")); + try (Git git = new Git(db)) { + writeTrashFile("file_1.txt", "content_1_1"); + git.add().addFilepattern("file_1.txt").call(); + RevCommit first = git.commit().setMessage("create file").call(); + + writeTrashFile("file_1.txt", "content_1_2"); + String expectedFilePath = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath, "content_2_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("updated file").call(); + + Map options = new HashMap<>(); + Integer opt = Integer.valueOf(42); + options.put("foo", opt); + MockOutputStream out = new MockOutputStream(); + git.archive().setOutputStream(out) + .setFormat(format.SUFFIXES.get(0)) + .setFormatOptions(options) + .setTree(first) + .setPaths("file_1.txt").call(); + + assertEquals(opt.intValue(), out.getFoo()); + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt")); + } } @Test public void archiveByDirectoryPath() throws GitAPIException, IOException { - Git git = new Git(db); - writeTrashFile("file_0.txt", "content_0_1"); - git.add().addFilepattern("file_0.txt").call(); - git.commit().setMessage("commit_1").call(); - - writeTrashFile("file_0.txt", "content_0_2"); - String expectedFilePath1 = "some_directory/file_1.txt"; - writeTrashFile(expectedFilePath1, "content_1_2"); - String expectedFilePath2 = "some_directory/file_2.txt"; - writeTrashFile(expectedFilePath2, "content_2_2"); - String expectedFilePath3 = "some_directory/nested_directory/file_3.txt"; - writeTrashFile(expectedFilePath3, "content_3_2"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("commit_2").call(); - git.archive().setOutputStream(new MockOutputStream()) - .setFormat(format.SUFFIXES.get(0)) - .setTree(git.getRepository().resolve("HEAD")) - .setPaths("some_directory/").call(); - - assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size()); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1)); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2)); - assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3)); - assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); - assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory")); + try (Git git = new Git(db)) { + writeTrashFile("file_0.txt", "content_0_1"); + git.add().addFilepattern("file_0.txt").call(); + git.commit().setMessage("commit_1").call(); + + writeTrashFile("file_0.txt", "content_0_2"); + String expectedFilePath1 = "some_directory/file_1.txt"; + writeTrashFile(expectedFilePath1, "content_1_2"); + String expectedFilePath2 = "some_directory/file_2.txt"; + writeTrashFile(expectedFilePath2, "content_2_2"); + String expectedFilePath3 = "some_directory/nested_directory/file_3.txt"; + writeTrashFile(expectedFilePath3, "content_3_2"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit_2").call(); + git.archive().setOutputStream(new MockOutputStream()) + .setFormat(format.SUFFIXES.get(0)) + .setTree(git.getRepository().resolve("HEAD")) + .setPaths("some_directory/").call(); + + assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size()); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1)); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2)); + assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3)); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory")); + assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory")); + } } private class MockFormat implements ArchiveCommand.Format { - private Map entries = new HashMap(); + private Map entries = new HashMap<>(); private int size() { return entries.size(); @@ -199,12 +205,14 @@ private final List SUFFIXES = Collections .unmodifiableList(Arrays.asList(".mck")); + @Override public MockOutputStream createArchiveOutputStream(OutputStream s) throws IOException { return createArchiveOutputStream(s, Collections. emptyMap()); } + @Override public MockOutputStream createArchiveOutputStream(OutputStream s, Map o) throws IOException { for (Map.Entry p : o.entrySet()) { @@ -220,11 +228,18 @@ return new MockOutputStream(); } + @Override public void putEntry(MockOutputStream out, String path, FileMode mode, ObjectLoader loader) { + putEntry(out, null, path, mode, loader); + } + + @Override + public void putEntry(MockOutputStream out, ObjectId tree, String path, FileMode mode, ObjectLoader loader) { String content = mode != FileMode.TREE ? new String(loader.getBytes()) : null; entries.put(path, content); } + @Override public Iterable suffixes() { return SUFFIXES; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,141 +58,142 @@ public class BlameGeneratorTest extends RepositoryTestCase { @Test public void testBoundLineDelete() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "first", "second" }; + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + + String[] content2 = new String[] { "third", "first", "second" }; + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + RevCommit c2 = git.commit().setMessage("create file").call(); + + try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) { + generator.push(null, db.resolve(Constants.HEAD)); + assertEquals(3, generator.getResultContents().size()); + + assertTrue(generator.next()); + assertEquals(c2, generator.getSourceCommit()); + assertEquals(1, generator.getRegionLength()); + assertEquals(0, generator.getResultStart()); + assertEquals(1, generator.getResultEnd()); + assertEquals(0, generator.getSourceStart()); + assertEquals(1, generator.getSourceEnd()); + assertEquals("file.txt", generator.getSourcePath()); + + assertTrue(generator.next()); + assertEquals(c1, generator.getSourceCommit()); + assertEquals(2, generator.getRegionLength()); + assertEquals(1, generator.getResultStart()); + assertEquals(3, generator.getResultEnd()); + assertEquals(0, generator.getSourceStart()); + assertEquals(2, generator.getSourceEnd()); + assertEquals("file.txt", generator.getSourcePath()); - String[] content1 = new String[] { "first", "second" }; - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - - String[] content2 = new String[] { "third", "first", "second" }; - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - RevCommit c2 = git.commit().setMessage("create file").call(); - - try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) { - generator.push(null, db.resolve(Constants.HEAD)); - assertEquals(3, generator.getResultContents().size()); - - assertTrue(generator.next()); - assertEquals(c2, generator.getSourceCommit()); - assertEquals(1, generator.getRegionLength()); - assertEquals(0, generator.getResultStart()); - assertEquals(1, generator.getResultEnd()); - assertEquals(0, generator.getSourceStart()); - assertEquals(1, generator.getSourceEnd()); - assertEquals("file.txt", generator.getSourcePath()); - - assertTrue(generator.next()); - assertEquals(c1, generator.getSourceCommit()); - assertEquals(2, generator.getRegionLength()); - assertEquals(1, generator.getResultStart()); - assertEquals(3, generator.getResultEnd()); - assertEquals(0, generator.getSourceStart()); - assertEquals(2, generator.getSourceEnd()); - assertEquals("file.txt", generator.getSourcePath()); - - assertFalse(generator.next()); + assertFalse(generator.next()); + } } } @Test public void testRenamedBoundLineDelete() throws Exception { - Git git = new Git(db); - final String FILENAME_1 = "subdir/file1.txt"; - final String FILENAME_2 = "subdir/file2.txt"; - - String[] content1 = new String[] { "first", "second" }; - writeTrashFile(FILENAME_1, join(content1)); - git.add().addFilepattern(FILENAME_1).call(); - RevCommit c1 = git.commit().setMessage("create file1").call(); - - // rename it - writeTrashFile(FILENAME_2, join(content1)); - git.add().addFilepattern(FILENAME_2).call(); - deleteTrashFile(FILENAME_1); - git.rm().addFilepattern(FILENAME_1).call(); - git.commit().setMessage("rename file1.txt to file2.txt").call(); - - // and change the new file - String[] content2 = new String[] { "third", "first", "second" }; - writeTrashFile(FILENAME_2, join(content2)); - git.add().addFilepattern(FILENAME_2).call(); - RevCommit c2 = git.commit().setMessage("change file2").call(); - - try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) { - generator.push(null, db.resolve(Constants.HEAD)); - assertEquals(3, generator.getResultContents().size()); - - assertTrue(generator.next()); - assertEquals(c2, generator.getSourceCommit()); - assertEquals(1, generator.getRegionLength()); - assertEquals(0, generator.getResultStart()); - assertEquals(1, generator.getResultEnd()); - assertEquals(0, generator.getSourceStart()); - assertEquals(1, generator.getSourceEnd()); - assertEquals(FILENAME_2, generator.getSourcePath()); - - assertTrue(generator.next()); - assertEquals(c1, generator.getSourceCommit()); - assertEquals(2, generator.getRegionLength()); - assertEquals(1, generator.getResultStart()); - assertEquals(3, generator.getResultEnd()); - assertEquals(0, generator.getSourceStart()); - assertEquals(2, generator.getSourceEnd()); - assertEquals(FILENAME_1, generator.getSourcePath()); - - assertFalse(generator.next()); - } - - // and test again with other BlameGenerator API: - try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) { - generator.push(null, db.resolve(Constants.HEAD)); - BlameResult result = generator.computeBlameResult(); - - assertEquals(3, result.getResultContents().size()); - - assertEquals(c2, result.getSourceCommit(0)); - assertEquals(FILENAME_2, result.getSourcePath(0)); - - assertEquals(c1, result.getSourceCommit(1)); - assertEquals(FILENAME_1, result.getSourcePath(1)); - - assertEquals(c1, result.getSourceCommit(2)); - assertEquals(FILENAME_1, result.getSourcePath(2)); + try (Git git = new Git(db)) { + final String FILENAME_1 = "subdir/file1.txt"; + final String FILENAME_2 = "subdir/file2.txt"; + + String[] content1 = new String[] { "first", "second" }; + writeTrashFile(FILENAME_1, join(content1)); + git.add().addFilepattern(FILENAME_1).call(); + RevCommit c1 = git.commit().setMessage("create file1").call(); + + // rename it + writeTrashFile(FILENAME_2, join(content1)); + git.add().addFilepattern(FILENAME_2).call(); + deleteTrashFile(FILENAME_1); + git.rm().addFilepattern(FILENAME_1).call(); + git.commit().setMessage("rename file1.txt to file2.txt").call(); + + // and change the new file + String[] content2 = new String[] { "third", "first", "second" }; + writeTrashFile(FILENAME_2, join(content2)); + git.add().addFilepattern(FILENAME_2).call(); + RevCommit c2 = git.commit().setMessage("change file2").call(); + + try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) { + generator.push(null, db.resolve(Constants.HEAD)); + assertEquals(3, generator.getResultContents().size()); + + assertTrue(generator.next()); + assertEquals(c2, generator.getSourceCommit()); + assertEquals(1, generator.getRegionLength()); + assertEquals(0, generator.getResultStart()); + assertEquals(1, generator.getResultEnd()); + assertEquals(0, generator.getSourceStart()); + assertEquals(1, generator.getSourceEnd()); + assertEquals(FILENAME_2, generator.getSourcePath()); + + assertTrue(generator.next()); + assertEquals(c1, generator.getSourceCommit()); + assertEquals(2, generator.getRegionLength()); + assertEquals(1, generator.getResultStart()); + assertEquals(3, generator.getResultEnd()); + assertEquals(0, generator.getSourceStart()); + assertEquals(2, generator.getSourceEnd()); + assertEquals(FILENAME_1, generator.getSourcePath()); + + assertFalse(generator.next()); + } + + // and test again with other BlameGenerator API: + try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) { + generator.push(null, db.resolve(Constants.HEAD)); + BlameResult result = generator.computeBlameResult(); + + assertEquals(3, result.getResultContents().size()); + + assertEquals(c2, result.getSourceCommit(0)); + assertEquals(FILENAME_2, result.getSourcePath(0)); + + assertEquals(c1, result.getSourceCommit(1)); + assertEquals(FILENAME_1, result.getSourcePath(1)); + + assertEquals(c1, result.getSourceCommit(2)); + assertEquals(FILENAME_1, result.getSourcePath(2)); + } } } @Test public void testLinesAllDeletedShortenedWalk() throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "first", "second", "third" }; - - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - - String[] content2 = new String[] { "" }; - - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - RevCommit c3 = git.commit().setMessage("create file").call(); - - try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) { - generator.push(null, db.resolve(Constants.HEAD)); - assertEquals(3, generator.getResultContents().size()); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "first", "second", "third" }; - assertTrue(generator.next()); - assertEquals(c3, generator.getSourceCommit()); - assertEquals(0, generator.getResultStart()); - assertEquals(3, generator.getResultEnd()); + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + + String[] content2 = new String[] { "" }; + + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + RevCommit c3 = git.commit().setMessage("create file").call(); + + try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) { + generator.push(null, db.resolve(Constants.HEAD)); + assertEquals(3, generator.getResultContents().size()); + + assertTrue(generator.next()); + assertEquals(c3, generator.getSourceCommit()); + assertEquals(0, generator.getResultStart()); + assertEquals(3, generator.getResultEnd()); - assertFalse(generator.next()); + assertFalse(generator.next()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,53 +72,53 @@ @Test public void testSingleRevision() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + String[] content = new String[] { "first", "second", "third" }; - String[] content = new String[] { "first", "second", "third" }; - - writeTrashFile("file.txt", join(content)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertNotNull(lines); - assertEquals(3, lines.getResultContents().size()); - - for (int i = 0; i < 3; i++) { - assertEquals(commit, lines.getSourceCommit(i)); - assertEquals(i, lines.getSourceLine(i)); + writeTrashFile("file.txt", join(content)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertNotNull(lines); + assertEquals(3, lines.getResultContents().size()); + + for (int i = 0; i < 3; i++) { + assertEquals(commit, lines.getSourceCommit(i)); + assertEquals(i, lines.getSourceLine(i)); + } } } @Test public void testTwoRevisions() throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "first", "second" }; - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit1 = git.commit().setMessage("create file").call(); - - String[] content2 = new String[] { "first", "second", "third" }; - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit2 = git.commit().setMessage("create file").call(); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertEquals(3, lines.getResultContents().size()); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "first", "second" }; + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); + + String[] content2 = new String[] { "first", "second", "third" }; + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit2 = git.commit().setMessage("create file").call(); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertEquals(3, lines.getResultContents().size()); - assertEquals(commit1, lines.getSourceCommit(0)); - assertEquals(0, lines.getSourceLine(0)); + assertEquals(commit1, lines.getSourceCommit(0)); + assertEquals(0, lines.getSourceLine(0)); - assertEquals(commit1, lines.getSourceCommit(1)); - assertEquals(1, lines.getSourceLine(1)); + assertEquals(commit1, lines.getSourceCommit(1)); + assertEquals(1, lines.getSourceLine(1)); - assertEquals(commit2, lines.getSourceCommit(2)); - assertEquals(2, lines.getSourceLine(2)); + assertEquals(commit2, lines.getSourceCommit(2)); + assertEquals(2, lines.getSourceLine(2)); + } } @Test @@ -138,200 +138,200 @@ private void testRename(final String sourcePath, final String destPath) throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "a", "b", "c" }; - writeTrashFile(sourcePath, join(content1)); - git.add().addFilepattern(sourcePath).call(); - RevCommit commit1 = git.commit().setMessage("create file").call(); - - writeTrashFile(destPath, join(content1)); - git.add().addFilepattern(destPath).call(); - git.rm().addFilepattern(sourcePath).call(); - git.commit().setMessage("moving file").call(); - - String[] content2 = new String[] { "a", "b", "c2" }; - writeTrashFile(destPath, join(content2)); - git.add().addFilepattern(destPath).call(); - RevCommit commit3 = git.commit().setMessage("editing file").call(); - - BlameCommand command = new BlameCommand(db); - command.setFollowFileRenames(true); - command.setFilePath(destPath); - BlameResult lines = command.call(); - - assertEquals(commit1, lines.getSourceCommit(0)); - assertEquals(0, lines.getSourceLine(0)); - assertEquals(sourcePath, lines.getSourcePath(0)); - - assertEquals(commit1, lines.getSourceCommit(1)); - assertEquals(1, lines.getSourceLine(1)); - assertEquals(sourcePath, lines.getSourcePath(1)); - - assertEquals(commit3, lines.getSourceCommit(2)); - assertEquals(2, lines.getSourceLine(2)); - assertEquals(destPath, lines.getSourcePath(2)); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "a", "b", "c" }; + writeTrashFile(sourcePath, join(content1)); + git.add().addFilepattern(sourcePath).call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); + + writeTrashFile(destPath, join(content1)); + git.add().addFilepattern(destPath).call(); + git.rm().addFilepattern(sourcePath).call(); + git.commit().setMessage("moving file").call(); + + String[] content2 = new String[] { "a", "b", "c2" }; + writeTrashFile(destPath, join(content2)); + git.add().addFilepattern(destPath).call(); + RevCommit commit3 = git.commit().setMessage("editing file").call(); + + BlameCommand command = new BlameCommand(db); + command.setFollowFileRenames(true); + command.setFilePath(destPath); + BlameResult lines = command.call(); + + assertEquals(commit1, lines.getSourceCommit(0)); + assertEquals(0, lines.getSourceLine(0)); + assertEquals(sourcePath, lines.getSourcePath(0)); + + assertEquals(commit1, lines.getSourceCommit(1)); + assertEquals(1, lines.getSourceLine(1)); + assertEquals(sourcePath, lines.getSourcePath(1)); + + assertEquals(commit3, lines.getSourceCommit(2)); + assertEquals(2, lines.getSourceLine(2)); + assertEquals(destPath, lines.getSourcePath(2)); + } } @Test public void testTwoRenames() throws Exception { - Git git = new Git(db); - - // Commit 1: Add file.txt - String[] content1 = new String[] { "a" }; - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit1 = git.commit().setMessage("create file").call(); - - // Commit 2: Rename to file1.txt - writeTrashFile("file1.txt", join(content1)); - git.add().addFilepattern("file1.txt").call(); - git.rm().addFilepattern("file.txt").call(); - git.commit().setMessage("moving file").call(); - - // Commit 3: Edit file1.txt - String[] content2 = new String[] { "a", "b" }; - writeTrashFile("file1.txt", join(content2)); - git.add().addFilepattern("file1.txt").call(); - RevCommit commit3 = git.commit().setMessage("editing file").call(); - - // Commit 4: Rename to file2.txt - writeTrashFile("file2.txt", join(content2)); - git.add().addFilepattern("file2.txt").call(); - git.rm().addFilepattern("file1.txt").call(); - git.commit().setMessage("moving file again").call(); - - BlameCommand command = new BlameCommand(db); - command.setFollowFileRenames(true); - command.setFilePath("file2.txt"); - BlameResult lines = command.call(); - - assertEquals(commit1, lines.getSourceCommit(0)); - assertEquals(0, lines.getSourceLine(0)); - assertEquals("file.txt", lines.getSourcePath(0)); - - assertEquals(commit3, lines.getSourceCommit(1)); - assertEquals(1, lines.getSourceLine(1)); - assertEquals("file1.txt", lines.getSourcePath(1)); + try (Git git = new Git(db)) { + // Commit 1: Add file.txt + String[] content1 = new String[] { "a" }; + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); + + // Commit 2: Rename to file1.txt + writeTrashFile("file1.txt", join(content1)); + git.add().addFilepattern("file1.txt").call(); + git.rm().addFilepattern("file.txt").call(); + git.commit().setMessage("moving file").call(); + + // Commit 3: Edit file1.txt + String[] content2 = new String[] { "a", "b" }; + writeTrashFile("file1.txt", join(content2)); + git.add().addFilepattern("file1.txt").call(); + RevCommit commit3 = git.commit().setMessage("editing file").call(); + + // Commit 4: Rename to file2.txt + writeTrashFile("file2.txt", join(content2)); + git.add().addFilepattern("file2.txt").call(); + git.rm().addFilepattern("file1.txt").call(); + git.commit().setMessage("moving file again").call(); + + BlameCommand command = new BlameCommand(db); + command.setFollowFileRenames(true); + command.setFilePath("file2.txt"); + BlameResult lines = command.call(); + + assertEquals(commit1, lines.getSourceCommit(0)); + assertEquals(0, lines.getSourceLine(0)); + assertEquals("file.txt", lines.getSourcePath(0)); + + assertEquals(commit3, lines.getSourceCommit(1)); + assertEquals(1, lines.getSourceLine(1)); + assertEquals("file1.txt", lines.getSourcePath(1)); + } } @Test public void testDeleteTrailingLines() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "a", "b", "c", "d" }; + String[] content2 = new String[] { "a", "b" }; - String[] content1 = new String[] { "a", "b", "c", "d" }; - String[] content2 = new String[] { "a", "b" }; + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit1 = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); + BlameCommand command = new BlameCommand(db); - BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertEquals(content2.length, lines.getResultContents().size()); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertEquals(content2.length, lines.getResultContents().size()); + assertEquals(commit1, lines.getSourceCommit(0)); + assertEquals(commit1, lines.getSourceCommit(1)); - assertEquals(commit1, lines.getSourceCommit(0)); - assertEquals(commit1, lines.getSourceCommit(1)); - - assertEquals(0, lines.getSourceLine(0)); - assertEquals(1, lines.getSourceLine(1)); + assertEquals(0, lines.getSourceLine(0)); + assertEquals(1, lines.getSourceLine(1)); + } } @Test public void testDeleteMiddleLines() throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "a", "b", "c", "d", "e" }; - String[] content2 = new String[] { "a", "c", "e" }; + try (Git git = new Git(db)) { + String[] content1 = new String[] { "a", "b", "c", "d", "e" }; + String[] content2 = new String[] { "a", "c", "e" }; - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit1 = git.commit().setMessage("edit file").call(); + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("edit file").call(); - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); - BlameCommand command = new BlameCommand(db); + BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertEquals(content2.length, lines.getResultContents().size()); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertEquals(content2.length, lines.getResultContents().size()); - assertEquals(commit1, lines.getSourceCommit(0)); - assertEquals(0, lines.getSourceLine(0)); + assertEquals(commit1, lines.getSourceCommit(0)); + assertEquals(0, lines.getSourceLine(0)); - assertEquals(commit1, lines.getSourceCommit(1)); - assertEquals(1, lines.getSourceLine(1)); + assertEquals(commit1, lines.getSourceCommit(1)); + assertEquals(1, lines.getSourceLine(1)); - assertEquals(commit1, lines.getSourceCommit(2)); - assertEquals(2, lines.getSourceLine(2)); + assertEquals(commit1, lines.getSourceCommit(2)); + assertEquals(2, lines.getSourceLine(2)); + } } @Test public void testEditAllLines() throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "a", "1" }; - String[] content2 = new String[] { "b", "2" }; - - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); - - writeTrashFile("file.txt", join(content2)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit2 = git.commit().setMessage("create file").call(); - - BlameCommand command = new BlameCommand(db); - - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertEquals(content2.length, lines.getResultContents().size()); - assertEquals(commit2, lines.getSourceCommit(0)); - assertEquals(commit2, lines.getSourceCommit(1)); + try (Git git = new Git(db)) { + String[] content1 = new String[] { "a", "1" }; + String[] content2 = new String[] { "b", "2" }; + + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); + + writeTrashFile("file.txt", join(content2)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit2 = git.commit().setMessage("create file").call(); + + BlameCommand command = new BlameCommand(db); + + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertEquals(content2.length, lines.getResultContents().size()); + assertEquals(commit2, lines.getSourceCommit(0)); + assertEquals(commit2, lines.getSourceCommit(1)); + } } @Test public void testMiddleClearAllLines() throws Exception { - Git git = new Git(db); - - String[] content1 = new String[] { "a", "b", "c" }; + try (Git git = new Git(db)) { + String[] content1 = new String[] { "a", "b", "c" }; - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); - - writeTrashFile("file.txt", ""); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - - writeTrashFile("file.txt", join(content1)); - git.add().addFilepattern("file.txt").call(); - RevCommit commit3 = git.commit().setMessage("edit file").call(); - - BlameCommand command = new BlameCommand(db); - - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - assertEquals(content1.length, lines.getResultContents().size()); - assertEquals(commit3, lines.getSourceCommit(0)); - assertEquals(commit3, lines.getSourceCommit(1)); - assertEquals(commit3, lines.getSourceCommit(2)); + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); + + writeTrashFile("file.txt", ""); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + + writeTrashFile("file.txt", join(content1)); + git.add().addFilepattern("file.txt").call(); + RevCommit commit3 = git.commit().setMessage("edit file").call(); + + BlameCommand command = new BlameCommand(db); + + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + assertEquals(content1.length, lines.getResultContents().size()); + assertEquals(commit3, lines.getSourceCommit(0)); + assertEquals(commit3, lines.getSourceCommit(1)); + assertEquals(commit3, lines.getSourceCommit(2)); + } } @Test @@ -361,130 +361,132 @@ private void testCoreAutoCrlf(AutoCRLF modeForCommitting, AutoCRLF modeForReset) throws Exception { - Git git = new Git(db); - FileBasedConfig config = db.getConfig(); - config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForCommitting); - config.save(); - - String joinedCrlf = "a\r\nb\r\nc\r\n"; - File trashFile = writeTrashFile("file.txt", joinedCrlf); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); - - // re-create file from the repo - trashFile.delete(); - config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForReset); - config.save(); - git.reset().setMode(ResetType.HARD).call(); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - - assertEquals(3, lines.getResultContents().size()); - assertEquals(commit, lines.getSourceCommit(0)); - assertEquals(commit, lines.getSourceCommit(1)); - assertEquals(commit, lines.getSourceCommit(2)); + try (Git git = new Git(db)) { + FileBasedConfig config = db.getConfig(); + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForCommitting); + config.save(); + + String joinedCrlf = "a\r\nb\r\nc\r\n"; + File trashFile = writeTrashFile("file.txt", joinedCrlf); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + // re-create file from the repo + trashFile.delete(); + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForReset); + config.save(); + git.reset().setMode(ResetType.HARD).call(); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + + assertEquals(3, lines.getResultContents().size()); + assertEquals(commit, lines.getSourceCommit(0)); + assertEquals(commit, lines.getSourceCommit(1)); + assertEquals(commit, lines.getSourceCommit(2)); + } } @Test public void testConflictingMerge1() throws Exception { - Git git = new Git(db); - - RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"), - "master"); - - git.checkout().setName("side").setCreateBranch(true) - .setStartPoint(base).call(); - RevCommit side = commitFile("file.txt", - join("0", "1 side", "2", "3 on side", "4"), "side"); - - commitFile("file.txt", join("0", "1", "2"), "master"); - - checkoutBranch("refs/heads/master"); - git.merge().include(side).call(); - - // The merge results in a conflict, which we resolve using mostly the - // side branch contents. Especially the "4" survives. - RevCommit merge = commitFile("file.txt", - join("0", "1 side", "2", "3 resolved", "4"), "master"); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - - assertEquals(5, lines.getResultContents().size()); - assertEquals(base, lines.getSourceCommit(0)); - assertEquals(side, lines.getSourceCommit(1)); - assertEquals(base, lines.getSourceCommit(2)); - assertEquals(merge, lines.getSourceCommit(3)); - assertEquals(base, lines.getSourceCommit(4)); + try (Git git = new Git(db)) { + RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"), + "master"); + + git.checkout().setName("side").setCreateBranch(true) + .setStartPoint(base).call(); + RevCommit side = commitFile("file.txt", + join("0", "1 side", "2", "3 on side", "4"), "side"); + + commitFile("file.txt", join("0", "1", "2"), "master"); + + checkoutBranch("refs/heads/master"); + git.merge().include(side).call(); + + // The merge results in a conflict, which we resolve using mostly the + // side branch contents. Especially the "4" survives. + RevCommit merge = commitFile("file.txt", + join("0", "1 side", "2", "3 resolved", "4"), "master"); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + + assertEquals(5, lines.getResultContents().size()); + assertEquals(base, lines.getSourceCommit(0)); + assertEquals(side, lines.getSourceCommit(1)); + assertEquals(base, lines.getSourceCommit(2)); + assertEquals(merge, lines.getSourceCommit(3)); + assertEquals(base, lines.getSourceCommit(4)); + } } // this test inverts the order of the master and side commit and is // otherwise identical to testConflictingMerge1 @Test public void testConflictingMerge2() throws Exception { - Git git = new Git(db); - - RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"), - "master"); - - commitFile("file.txt", join("0", "1", "2"), "master"); - - git.checkout().setName("side").setCreateBranch(true) - .setStartPoint(base).call(); - RevCommit side = commitFile("file.txt", - join("0", "1 side", "2", "3 on side", "4"), "side"); - - checkoutBranch("refs/heads/master"); - git.merge().include(side).call(); - - // The merge results in a conflict, which we resolve using mostly the - // side branch contents. Especially the "4" survives. - RevCommit merge = commitFile("file.txt", - join("0", "1 side", "2", "3 resolved", "4"), "master"); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt"); - BlameResult lines = command.call(); - - assertEquals(5, lines.getResultContents().size()); - assertEquals(base, lines.getSourceCommit(0)); - assertEquals(side, lines.getSourceCommit(1)); - assertEquals(base, lines.getSourceCommit(2)); - assertEquals(merge, lines.getSourceCommit(3)); - assertEquals(base, lines.getSourceCommit(4)); + try (Git git = new Git(db)) { + RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"), + "master"); + + commitFile("file.txt", join("0", "1", "2"), "master"); + + git.checkout().setName("side").setCreateBranch(true) + .setStartPoint(base).call(); + RevCommit side = commitFile("file.txt", + join("0", "1 side", "2", "3 on side", "4"), "side"); + + checkoutBranch("refs/heads/master"); + git.merge().include(side).call(); + + // The merge results in a conflict, which we resolve using mostly the + // side branch contents. Especially the "4" survives. + RevCommit merge = commitFile("file.txt", + join("0", "1 side", "2", "3 resolved", "4"), "master"); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt"); + BlameResult lines = command.call(); + + assertEquals(5, lines.getResultContents().size()); + assertEquals(base, lines.getSourceCommit(0)); + assertEquals(side, lines.getSourceCommit(1)); + assertEquals(base, lines.getSourceCommit(2)); + assertEquals(merge, lines.getSourceCommit(3)); + assertEquals(base, lines.getSourceCommit(4)); + } } @Test public void testWhitespaceMerge() throws Exception { - Git git = new Git(db); - RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master"); - RevCommit side = commitFile("file.txt", join("0", "1", " 2 side "), - "side"); - - checkoutBranch("refs/heads/master"); - git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call(); - - // change whitespace, so the merge content is not identical to side, but - // is the same when ignoring whitespace - writeTrashFile("file.txt", join("0", "1", "2 side")); - RevCommit merge = git.commit().setAll(true).setMessage("merge") - .setAmend(true) - .call(); - - BlameCommand command = new BlameCommand(db); - command.setFilePath("file.txt") - .setTextComparator(RawTextComparator.WS_IGNORE_ALL) - .setStartCommit(merge.getId()); - BlameResult lines = command.call(); - - assertEquals(3, lines.getResultContents().size()); - assertEquals(base, lines.getSourceCommit(0)); - assertEquals(base, lines.getSourceCommit(1)); - assertEquals(side, lines.getSourceCommit(2)); + try (Git git = new Git(db)) { + RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master"); + RevCommit side = commitFile("file.txt", join("0", "1", " 2 side "), + "side"); + + checkoutBranch("refs/heads/master"); + git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call(); + + // change whitespace, so the merge content is not identical to side, but + // is the same when ignoring whitespace + writeTrashFile("file.txt", join("0", "1", "2 side")); + RevCommit merge = git.commit().setAll(true).setMessage("merge") + .setAmend(true) + .call(); + + BlameCommand command = new BlameCommand(db); + command.setFilePath("file.txt") + .setTextComparator(RawTextComparator.WS_IGNORE_ALL) + .setStartCommit(merge.getId()); + BlameResult lines = command.call(); + + assertEquals(3, lines.getResultContents().size()); + assertEquals(base, lines.getSourceCommit(0)); + assertEquals(base, lines.getSourceCommit(1)); + assertEquals(side, lines.getSourceCommit(2)); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -104,37 +104,38 @@ private Git setUpRepoWithRemote() throws Exception { Repository remoteRepository = createWorkRepository(); - Git remoteGit = new Git(remoteRepository); - // commit something - writeTrashFile("Test.txt", "Hello world"); - remoteGit.add().addFilepattern("Test.txt").call(); - initialCommit = remoteGit.commit().setMessage("Initial commit").call(); - writeTrashFile("Test.txt", "Some change"); - remoteGit.add().addFilepattern("Test.txt").call(); - secondCommit = remoteGit.commit().setMessage("Second commit").call(); - // create a master branch - RefUpdate rup = remoteRepository.updateRef("refs/heads/master"); - rup.setNewObjectId(initialCommit.getId()); - rup.forceUpdate(); - - Repository localRepository = createWorkRepository(); - Git localGit = new Git(localRepository); - StoredConfig config = localRepository.getConfig(); - RemoteConfig rc = new RemoteConfig(config, "origin"); - rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath())); - rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); - rc.update(config); - config.save(); - FetchResult res = localGit.fetch().setRemote("origin").call(); - assertFalse(res.getTrackingRefUpdates().isEmpty()); - rup = localRepository.updateRef("refs/heads/master"); - rup.setNewObjectId(initialCommit.getId()); - rup.forceUpdate(); - rup = localRepository.updateRef(Constants.HEAD); - rup.link("refs/heads/master"); - rup.setNewObjectId(initialCommit.getId()); - rup.update(); - return localGit; + try (Git remoteGit = new Git(remoteRepository)) { + // commit something + writeTrashFile("Test.txt", "Hello world"); + remoteGit.add().addFilepattern("Test.txt").call(); + initialCommit = remoteGit.commit().setMessage("Initial commit").call(); + writeTrashFile("Test.txt", "Some change"); + remoteGit.add().addFilepattern("Test.txt").call(); + secondCommit = remoteGit.commit().setMessage("Second commit").call(); + // create a master branch + RefUpdate rup = remoteRepository.updateRef("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.forceUpdate(); + + Repository localRepository = createWorkRepository(); + Git localGit = new Git(localRepository); + StoredConfig config = localRepository.getConfig(); + RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath())); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + rc.update(config); + config.save(); + FetchResult res = localGit.fetch().setRemote("origin").call(); + assertFalse(res.getTrackingRefUpdates().isEmpty()); + rup = localRepository.updateRef("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.forceUpdate(); + rup = localRepository.updateRef(Constants.HEAD); + rup.link("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.update(); + return localGit; + } } @Test @@ -192,8 +193,7 @@ @Test public void testListAllBranchesShouldNotDie() throws Exception { - Git git = setUpRepoWithRemote(); - git.branchList().setListMode(ListMode.ALL).call(); + setUpRepoWithRemote().branchList().setListMode(ListMode.ALL).call(); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -70,19 +72,22 @@ import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lfs.BuiltinLFS; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; @@ -96,6 +101,7 @@ @Override @Before public void setUp() throws Exception { + BuiltinLFS.register(); super.setUp(); git = new Git(db); // commit something @@ -134,7 +140,7 @@ @Test public void testCreateBranchOnCheckout() throws Exception { git.checkout().setCreateBranch(true).setName("test2").call(); - assertNotNull(db.getRef("test2")); + assertNotNull(db.exactRef("refs/heads/test2")); } @Test @@ -164,15 +170,12 @@ @Test public void testCheckoutWithNonDeletedFiles() throws Exception { File testFile = writeTrashFile("temp", ""); - FileInputStream fis = new FileInputStream(testFile); - try { + try (FileInputStream fis = new FileInputStream(testFile)) { FileUtils.delete(testFile); return; } catch (IOException e) { // the test makes only sense if deletion of // a file with open stream fails - } finally { - fis.close(); } FileUtils.delete(testFile); CheckoutCommand co = git.checkout(); @@ -186,15 +189,12 @@ git.checkout().setName("master").call(); assertTrue(testFile.exists()); // lock the file so it can't be deleted (in Windows, that is) - fis = new FileInputStream(testFile); - try { + try (FileInputStream fis = new FileInputStream(testFile)) { assertEquals(Status.NOT_TRIED, co.getResult().getStatus()); co.setName("test").call(); assertTrue(testFile.exists()); assertEquals(Status.NONDELETED, co.getResult().getStatus()); assertTrue(co.getResult().getUndeletedList().contains("Test.txt")); - } finally { - fis.close(); } } @@ -237,8 +237,8 @@ .setStartPoint("origin/test") .setUpstreamMode(SetupUpstreamMode.TRACK).call(); - assertEquals("refs/heads/test", db2.getRef(Constants.HEAD).getTarget() - .getName()); + assertEquals("refs/heads/test", + db2.exactRef(Constants.HEAD).getTarget().getName()); StoredConfig config = db2.getConfig(); assertEquals("origin", config.getString( ConfigConstants.CONFIG_BRANCH_SECTION, "test", @@ -345,7 +345,7 @@ CheckoutCommand co = git.checkout(); co.setName("master").call(); - String commitId = db.getRef(Constants.MASTER).getObjectId().name(); + String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name(); co = git.checkout(); co.setName(commitId).call(); @@ -412,20 +412,20 @@ InvalidRemoteException, TransportException { // create second repository Repository db2 = createWorkRepository(); - Git git2 = new Git(db2); - - // setup the second repository to fetch from the first repository - final StoredConfig config = db2.getConfig(); - RemoteConfig remoteConfig = new RemoteConfig(config, "origin"); - URIish uri = new URIish(db.getDirectory().toURI().toURL()); - remoteConfig.addURI(uri); - remoteConfig.update(config); - config.save(); - - // fetch from first repository - RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*"); - git2.fetch().setRemote("origin").setRefSpecs(spec).call(); - return db2; + try (Git git2 = new Git(db2)) { + // setup the second repository to fetch from the first repository + final StoredConfig config = db2.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "origin"); + URIish uri = new URIish(db.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.update(config); + config.save(); + + // fetch from first repository + git2.fetch().setRemote("origin") + .setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call(); + return db2; + } } private CheckoutCommand newOrphanBranchCommand() { @@ -443,7 +443,7 @@ } private void assertHeadDetached() throws IOException { - Ref head = db.getRef(Constants.HEAD); + Ref head = db.exactRef(Constants.HEAD); assertFalse(head.isSymbolic()); assertSame(head, head.getTarget()); } @@ -554,4 +554,270 @@ } org.junit.Assume.assumeTrue(foundUnsmudged); } + + @Test + public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "x"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "x\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content1 = git.commit().setMessage("add content").call(); + + writeTrashFile("src/a.tmp", "foo"); + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content2 = git.commit().setMessage("changed content").call(); + + git.checkout().setName(content1.getName()).call(); + git.checkout().setName(content2.getName()).call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals(Sets.of("src/a.txt"), git.status().call().getModified()); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeFilter_createNew() + throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile("foo", "foo"); + git.add().addFilepattern("foo").call(); + RevCommit initial = git.commit().setMessage("initial").call(); + + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content = git.commit().setMessage("added content").call(); + + git.checkout().setName(initial.getName()).call(); + git.checkout().setName(content.getName()).call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeFilter_deleteFileAndRestoreFromCommit() + throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile("foo", "foo"); + git.add().addFilepattern("foo").call(); + git.commit().setMessage("initial").call(); + + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content = git.commit().setMessage("added content").call(); + + deleteTrashFile("src/a.txt"); + git.checkout().setStartPoint(content.getName()).addPath("src/a.txt") + .call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeFilter_deleteFileAndRestoreFromIndex() + throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile("foo", "foo"); + git.add().addFilepattern("foo").call(); + git.commit().setMessage("initial").call(); + + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + git.commit().setMessage("added content").call(); + + deleteTrashFile("src/a.txt"); + git.checkout().addPath("src/a.txt").call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeFilter_deleteFileAndCreateBranchAndRestoreFromCommit() + throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile("foo", "foo"); + git.add().addFilepattern("foo").call(); + git.commit().setMessage("initial").call(); + + writeTrashFile(".gitattributes", "*.txt filter=lfs"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content = git.commit().setMessage("added content").call(); + + deleteTrashFile("src/a.txt"); + git.checkout().setName("newBranch").setCreateBranch(true) + .setStartPoint(content).addPath("src/a.txt").call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=lfs][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeAndClean() throws Exception { + File clean_filter = writeTempFile("sed s/V1/@version/g"); + File smudge_filter = writeTempFile("sed s/@version/V1/g"); + + try (Git git2 = new Git(db)) { + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "lfs", "smudge", + "sh " + slashify(smudge_filter.getPath())); + config.setString("filter", "lfs", "clean", + "sh " + slashify(clean_filter.getPath())); + config.setBoolean("filter", "lfs", "useJGitBuiltin", true); + config.save(); + writeTrashFile(".gitattributes", "filterTest.txt filter=lfs"); + git2.add().addFilepattern(".gitattributes").call(); + git2.commit().setMessage("add attributes").call(); + + fsTick(writeTrashFile("filterTest.txt", "hello world, V1\n")); + git2.add().addFilepattern("filterTest.txt").call(); + RevCommit one = git2.commit().setMessage("add filterText.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:7bd5d32e5c494354aa4c2473a1306d0ce7b52cc3bffeb342c03cd517ef8cf8da\nsize 16\n]", + indexState(CONTENT)); + + fsTick(writeTrashFile("filterTest.txt", "bon giorno world, V1\n")); + git2.add().addFilepattern("filterTest.txt").call(); + RevCommit two = git2.commit().setMessage("modified filterTest.txt").call(); + + assertTrue(git2.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:087148cccf53b0049c56475c1595113c9da4b638997c3489af8ac7108d51ef13\nsize 21\n]", + indexState(CONTENT)); + + git2.checkout().setName(one.getName()).call(); + assertTrue(git2.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:7bd5d32e5c494354aa4c2473a1306d0ce7b52cc3bffeb342c03cd517ef8cf8da\nsize 16\n]", + indexState(CONTENT)); + assertEquals("hello world, V1\n", read("filterTest.txt")); + + git2.checkout().setName(two.getName()).call(); + assertTrue(git2.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:filterTest.txt filter=lfs][Test.txt, mode:100644, content:Some change][filterTest.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:087148cccf53b0049c56475c1595113c9da4b638997c3489af8ac7108d51ef13\nsize 21\n]", + indexState(CONTENT)); + assertEquals("bon giorno world, V1\n", read("filterTest.txt")); + } + } + + @Test + public void testNonDeletableFilesOnWindows() + throws GitAPIException, IOException { + // Only on windows a FileInputStream blocks us from deleting a file + org.junit.Assume.assumeTrue(SystemReader.getInstance().isWindows()); + writeTrashFile("toBeModified.txt", "a"); + writeTrashFile("toBeDeleted.txt", "a"); + git.add().addFilepattern(".").call(); + RevCommit addFiles = git.commit().setMessage("add more files").call(); + + git.rm().setCached(false).addFilepattern("Test.txt") + .addFilepattern("toBeDeleted.txt").call(); + writeTrashFile("toBeModified.txt", "b"); + writeTrashFile("toBeCreated.txt", "a"); + git.add().addFilepattern(".").call(); + RevCommit crudCommit = git.commit().setMessage("delete, modify, add") + .call(); + git.checkout().setName(addFiles.getName()).call(); + try ( FileInputStream fis=new FileInputStream(new File(db.getWorkTree(), "Test.txt")) ) { + CheckoutCommand coCommand = git.checkout(); + coCommand.setName(crudCommit.getName()).call(); + CheckoutResult result = coCommand.getResult(); + assertEquals(Status.NONDELETED, result.getStatus()); + assertEquals("[Test.txt, toBeDeleted.txt]", + result.getRemovedList().toString()); + assertEquals("[toBeCreated.txt, toBeModified.txt]", + result.getModifiedList().toString()); + assertEquals("[Test.txt]", result.getUndeletedList().toString()); + assertTrue(result.getConflictList().isEmpty()); + } + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("CheckoutCommandTest_", ""); + JGitTestUtil.write(f, body); + return f; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -88,136 +88,139 @@ private void doTestCherryPick(boolean noCommit) throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - writeTrashFile("a", "first line\nsec. line\nthird line\n"); - git.add().addFilepattern("a").call(); - RevCommit firstCommit = git.commit().setMessage("create a").call(); - - writeTrashFile("b", "content\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("create b").call(); - - writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("enlarged a").call(); - - writeTrashFile("a", - "first line\nsecond line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - RevCommit fixingA = git.commit().setMessage("fixed a").call(); - - git.branchCreate().setName("side").setStartPoint(firstCommit).call(); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("enhanced a").call(); - - CherryPickResult pickResult = git.cherryPick().include(fixingA) - .setNoCommit(noCommit).call(); - - assertEquals(CherryPickStatus.OK, pickResult.getStatus()); - assertFalse(new File(db.getWorkTree(), "b").exists()); - checkFile(new File(db.getWorkTree(), "a"), - "first line\nsecond line\nthird line\nfeature++\n"); - Iterator history = git.log().call().iterator(); - if (!noCommit) - assertEquals("fixed a", history.next().getFullMessage()); - assertEquals("enhanced a", history.next().getFullMessage()); - assertEquals("create a", history.next().getFullMessage()); - assertFalse(history.hasNext()); + try (Git git = new Git(db)) { + writeTrashFile("a", "first line\nsec. line\nthird line\n"); + git.add().addFilepattern("a").call(); + RevCommit firstCommit = git.commit().setMessage("create a").call(); + + writeTrashFile("b", "content\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("create b").call(); + + writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("enlarged a").call(); + + writeTrashFile("a", + "first line\nsecond line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + RevCommit fixingA = git.commit().setMessage("fixed a").call(); + + git.branchCreate().setName("side").setStartPoint(firstCommit).call(); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("enhanced a").call(); + + CherryPickResult pickResult = git.cherryPick().include(fixingA) + .setNoCommit(noCommit).call(); + + assertEquals(CherryPickStatus.OK, pickResult.getStatus()); + assertFalse(new File(db.getWorkTree(), "b").exists()); + checkFile(new File(db.getWorkTree(), "a"), + "first line\nsecond line\nthird line\nfeature++\n"); + Iterator history = git.log().call().iterator(); + if (!noCommit) + assertEquals("fixed a", history.next().getFullMessage()); + assertEquals("enhanced a", history.next().getFullMessage()); + assertEquals("create a", history.next().getFullMessage()); + assertFalse(history.hasNext()); + } } @Test public void testSequentialCherryPick() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - writeTrashFile("a", "first line\nsec. line\nthird line\n"); - git.add().addFilepattern("a").call(); - RevCommit firstCommit = git.commit().setMessage("create a").call(); - - writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - RevCommit enlargingA = git.commit().setMessage("enlarged a").call(); - - writeTrashFile("a", - "first line\nsecond line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - RevCommit fixingA = git.commit().setMessage("fixed a").call(); - - git.branchCreate().setName("side").setStartPoint(firstCommit).call(); - checkoutBranch("refs/heads/side"); - - writeTrashFile("b", "nothing to do with a"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("create b").call(); - - CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call(); - assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus()); - - Iterator history = git.log().call().iterator(); - assertEquals("fixed a", history.next().getFullMessage()); - assertEquals("enlarged a", history.next().getFullMessage()); - assertEquals("create b", history.next().getFullMessage()); - assertEquals("create a", history.next().getFullMessage()); - assertFalse(history.hasNext()); + try (Git git = new Git(db)) { + writeTrashFile("a", "first line\nsec. line\nthird line\n"); + git.add().addFilepattern("a").call(); + RevCommit firstCommit = git.commit().setMessage("create a").call(); + + writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + RevCommit enlargingA = git.commit().setMessage("enlarged a").call(); + + writeTrashFile("a", + "first line\nsecond line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + RevCommit fixingA = git.commit().setMessage("fixed a").call(); + + git.branchCreate().setName("side").setStartPoint(firstCommit).call(); + checkoutBranch("refs/heads/side"); + + writeTrashFile("b", "nothing to do with a"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("create b").call(); + + CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call(); + assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus()); + + Iterator history = git.log().call().iterator(); + assertEquals("fixed a", history.next().getFullMessage()); + assertEquals("enlarged a", history.next().getFullMessage()); + assertEquals("create b", history.next().getFullMessage()); + assertEquals("create a", history.next().getFullMessage()); + assertFalse(history.hasNext()); + } } @Test public void testCherryPickDirtyIndex() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareCherryPick(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - // modify and add file a - writeTrashFile("a", "a(modified)"); - git.add().addFilepattern("a").call(); - // do not commit + // modify and add file a + writeTrashFile("a", "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit - doCherryPickAndCheckResult(git, sideCommit, - MergeFailureReason.DIRTY_INDEX); + doCherryPickAndCheckResult(git, sideCommit, + MergeFailureReason.DIRTY_INDEX); + } } @Test public void testCherryPickDirtyWorktree() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareCherryPick(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - // modify file a - writeTrashFile("a", "a(modified)"); - // do not add and commit + // modify file a + writeTrashFile("a", "a(modified)"); + // do not add and commit - doCherryPickAndCheckResult(git, sideCommit, - MergeFailureReason.DIRTY_WORKTREE); + doCherryPickAndCheckResult(git, sideCommit, + MergeFailureReason.DIRTY_WORKTREE); + } } @Test public void testCherryPickConflictResolution() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareCherryPick(git); - - CherryPickResult result = git.cherryPick().include(sideCommit.getId()) - .call(); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); - assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists()); - assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg()); - assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) - .exists()); - assertEquals(sideCommit.getId(), db.readCherryPickHead()); - assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState()); + CherryPickResult result = git.cherryPick().include(sideCommit.getId()) + .call(); - // Resolve - writeTrashFile("a", "a"); - git.add().addFilepattern("a").call(); + assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); + assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists()); + assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg()); + assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) + .exists()); + assertEquals(sideCommit.getId(), db.readCherryPickHead()); + assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState()); + + // Resolve + writeTrashFile("a", "a"); + git.add().addFilepattern("a").call(); - assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED, - db.getRepositoryState()); + assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED, + db.getRepositoryState()); - git.commit().setOnly("a").setMessage("resolve").call(); + git.commit().setOnly("a").setMessage("resolve").call(); - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } } @Test @@ -251,85 +254,88 @@ @Test public void testCherryPickConflictReset() throws Exception { - Git git = new Git(db); - - RevCommit sideCommit = prepareCherryPick(git); - - CherryPickResult result = git.cherryPick().include(sideCommit.getId()) - .call(); - - assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); - assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState()); - assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) - .exists()); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - git.reset().setMode(ResetType.MIXED).setRef("HEAD").call(); + CherryPickResult result = git.cherryPick().include(sideCommit.getId()) + .call(); - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); - assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) - .exists()); + assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); + assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState()); + assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) + .exists()); + + git.reset().setMode(ResetType.MIXED).setRef("HEAD").call(); + + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD) + .exists()); + } } @Test public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem() throws Exception { - Git git = new Git(db); - File file = writeTrashFile("test.txt", "a"); - assertNotNull(git.add().addFilepattern("test.txt").call()); - assertNotNull(git.commit().setMessage("commit1").call()); - - assertNotNull(git.checkout().setCreateBranch(true).setName("a").call()); - - writeTrashFile("test.txt", "b"); - assertNotNull(git.add().addFilepattern("test.txt").call()); - RevCommit commit2 = git.commit().setMessage("commit2").call(); - assertNotNull(commit2); - - assertNotNull(git.checkout().setName(Constants.MASTER).call()); - - DirCache cache = db.lockDirCache(); - cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE); - cache.write(); - assertTrue(cache.commit()); - cache.unlock(); - - assertNotNull(git.commit().setMessage("commit3").call()); - - db.getFS().setExecute(file, false); - git.getRepository() - .getConfig() - .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_FILEMODE, false); - - CherryPickResult result = git.cherryPick().include(commit2).call(); - assertNotNull(result); - assertEquals(CherryPickStatus.OK, result.getStatus()); + try (Git git = new Git(db)) { + File file = writeTrashFile("test.txt", "a"); + assertNotNull(git.add().addFilepattern("test.txt").call()); + assertNotNull(git.commit().setMessage("commit1").call()); + + assertNotNull(git.checkout().setCreateBranch(true).setName("a").call()); + + writeTrashFile("test.txt", "b"); + assertNotNull(git.add().addFilepattern("test.txt").call()); + RevCommit commit2 = git.commit().setMessage("commit2").call(); + assertNotNull(commit2); + + assertNotNull(git.checkout().setName(Constants.MASTER).call()); + + DirCache cache = db.lockDirCache(); + cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE); + cache.write(); + assertTrue(cache.commit()); + cache.unlock(); + + assertNotNull(git.commit().setMessage("commit3").call()); + + db.getFS().setExecute(file, false); + git.getRepository() + .getConfig() + .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_FILEMODE, false); + + CherryPickResult result = git.cherryPick().include(commit2).call(); + assertNotNull(result); + assertEquals(CherryPickStatus.OK, result.getStatus()); + } } @Test public void testCherryPickConflictMarkers() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareCherryPick(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - CherryPickResult result = git.cherryPick().include(sideCommit.getId()) - .call(); - assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); + CherryPickResult result = git.cherryPick().include(sideCommit.getId()) + .call(); + assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); - String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n"; - checkFile(new File(db.getWorkTree(), "a"), expected); + String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n"; + checkFile(new File(db.getWorkTree(), "a"), expected); + } } @Test public void testCherryPickOurCommitName() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareCherryPick(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareCherryPick(git); - CherryPickResult result = git.cherryPick().include(sideCommit.getId()) - .setOurCommitName("custom name").call(); - assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); + CherryPickResult result = git.cherryPick().include(sideCommit.getId()) + .setOurCommitName("custom name").call(); + assertEquals(CherryPickStatus.CONFLICTING, result.getStatus()); - String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n"; - checkFile(new File(db.getWorkTree(), "a"), expected); + String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n"; + checkFile(new File(db.getWorkTree(), "a"), expected); + } } private RevCommit prepareCherryPick(final Git git) throws Exception { @@ -399,43 +405,43 @@ */ @Test public void testCherryPickMerge() throws Exception { - Git git = new Git(db); - - commitFile("file", "1\n2\n3\n", "master"); - commitFile("file", "1\n2\n3\n", "side"); - checkoutBranch("refs/heads/side"); - RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2"); - commitFile("file", "a\n2\n3\n", "side"); - MergeResult mergeResult = git.merge().include(commitD).call(); - ObjectId commitM = mergeResult.getNewHead(); - checkoutBranch("refs/heads/master"); - RevCommit commitT = commitFile("another", "t", "master"); - - try { - git.cherryPick().include(commitM).call(); - fail("merges should not be cherry-picked by default"); - } catch (MultipleParentsNotAllowedException e) { - // expected + try (Git git = new Git(db)) { + commitFile("file", "1\n2\n3\n", "master"); + commitFile("file", "1\n2\n3\n", "side"); + checkoutBranch("refs/heads/side"); + RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2"); + commitFile("file", "a\n2\n3\n", "side"); + MergeResult mergeResult = git.merge().include(commitD).call(); + ObjectId commitM = mergeResult.getNewHead(); + checkoutBranch("refs/heads/master"); + RevCommit commitT = commitFile("another", "t", "master"); + + try { + git.cherryPick().include(commitM).call(); + fail("merges should not be cherry-picked by default"); + } catch (MultipleParentsNotAllowedException e) { + // expected + } + try { + git.cherryPick().include(commitM).setMainlineParentNumber(3).call(); + fail("specifying a non-existent parent should fail"); + } catch (JGitInternalException e) { + // expected + assertTrue(e.getMessage().endsWith( + "does not have a parent number 3.")); + } + + CherryPickResult result = git.cherryPick().include(commitM) + .setMainlineParentNumber(1).call(); + assertEquals(CherryPickStatus.OK, result.getStatus()); + checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n"); + + git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call(); + + CherryPickResult result2 = git.cherryPick().include(commitM) + .setMainlineParentNumber(2).call(); + assertEquals(CherryPickStatus.OK, result2.getStatus()); + checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n"); } - try { - git.cherryPick().include(commitM).setMainlineParentNumber(3).call(); - fail("specifying a non-existent parent should fail"); - } catch (JGitInternalException e) { - // expected - assertTrue(e.getMessage().endsWith( - "does not have a parent number 3.")); - } - - CherryPickResult result = git.cherryPick().include(commitM) - .setMainlineParentNumber(1).call(); - assertEquals(CherryPickStatus.OK, result.getStatus()); - checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n"); - - git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call(); - - CherryPickResult result2 = git.cherryPick().include(commitM) - .setMainlineParentNumber(2).call(); - assertEquals(CherryPickStatus.OK, result2.getStatus()); - checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n"); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,16 +42,21 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; import java.util.Set; import java.util.TreeSet; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Repository; import org.junit.Before; import org.junit.Test; @@ -61,6 +66,7 @@ public class CleanCommandTest extends RepositoryTestCase { private Git git; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -140,7 +146,7 @@ assertTrue(files.size() > 0); // run clean with setPaths - Set paths = new TreeSet(); + Set paths = new TreeSet<>(); paths.add("File3.txt"); Set cleanedFiles = git.clean().setPaths(paths).call(); @@ -227,4 +233,84 @@ assertTrue(cleanedFiles.contains("ignored-dir/")); } + @Test + public void testCleanDirsWithSubmodule() throws Exception { + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + command.setPath(path); + String uri = db.getDirectory().toURI().toString(); + command.setURI(uri); + try (Repository repo = command.call()) { + // Unused + } + + Status beforeCleanStatus = git.status().call(); + assertTrue(beforeCleanStatus.getAdded().contains(DOT_GIT_MODULES)); + assertTrue(beforeCleanStatus.getAdded().contains(path)); + + Set cleanedFiles = git.clean().setCleanDirectories(true).call(); + + // The submodule should not be cleaned. + assertTrue(!cleanedFiles.contains(path + "/")); + + assertTrue(cleanedFiles.contains("File2.txt")); + assertTrue(cleanedFiles.contains("File3.txt")); + assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt")); + assertTrue(cleanedFiles.contains("sub-noclean/File2.txt")); + assertTrue(cleanedFiles.contains("sub-clean/")); + assertTrue(cleanedFiles.size() == 4); + } + + @Test + public void testCleanDirsWithRepository() throws Exception { + // Set up a repository inside the outer repository + String innerRepoName = "inner-repo"; + File innerDir = new File(trash, innerRepoName); + innerDir.mkdir(); + InitCommand initRepoCommand = new InitCommand(); + initRepoCommand.setDirectory(innerDir); + initRepoCommand.call(); + + Status beforeCleanStatus = git.status().call(); + Set untrackedFolders = beforeCleanStatus.getUntrackedFolders(); + Set untrackedFiles = beforeCleanStatus.getUntracked(); + + // The inner repository should be listed as an untracked file + assertTrue(untrackedFiles.contains(innerRepoName)); + + // The inner repository should not be listed as an untracked folder + assertTrue(!untrackedFolders.contains(innerRepoName)); + + Set cleanedFiles = git.clean().setCleanDirectories(true).call(); + + // The inner repository should not be cleaned. + assertTrue(!cleanedFiles.contains(innerRepoName + "/")); + + assertTrue(cleanedFiles.contains("File2.txt")); + assertTrue(cleanedFiles.contains("File3.txt")); + assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt")); + assertTrue(cleanedFiles.contains("sub-noclean/File2.txt")); + assertTrue(cleanedFiles.contains("sub-clean/")); + assertTrue(cleanedFiles.size() == 4); + + Set forceCleanedFiles = git.clean().setCleanDirectories(true) + .setForce(true).call(); + + // The inner repository should be cleaned this time + assertTrue(forceCleanedFiles.contains(innerRepoName + "/")); + } + + @Test + // To proof Bug 514434. No assertions, but before the bugfix + // this test was throwing Exceptions + public void testFilesShouldBeCleanedInSubSubFolders() + throws IOException, NoFilepatternException, GitAPIException { + writeTrashFile(".gitignore", + "/ignored-dir\n/sub-noclean/Ignored.txt\n/this_is_ok\n/this_is/not_ok\n"); + git.add().addFilepattern(".gitignore").call(); + git.commit().setMessage("adding .gitignore").call(); + writeTrashFile("this_is_ok/more/subdirs/file.txt", "1"); + writeTrashFile("this_is/not_ok/more/subdirs/file.txt", "2"); + git.clean().setCleanDirectories(true).setIgnore(false).call(); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,6 +62,7 @@ import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -75,6 +76,7 @@ import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.SystemReader; import org.junit.Test; @@ -84,9 +86,10 @@ private TestRepository tr; + @Override public void setUp() throws Exception { super.setUp(); - tr = new TestRepository(db); + tr = new TestRepository<>(db); git = new Git(db); // commit something @@ -143,16 +146,36 @@ File directory = createTempDirectory("testCloneRepository"); CloneCommand command = Git.cloneRepository(); command.setDirectory(directory); - command.setGitDir(new File(directory, ".git")); + command.setGitDir(new File(directory, Constants.DOT_GIT)); command.setURI(fileUri()); Git git2 = command.call(); addRepoToClose(git2.getRepository()); assertEquals(directory, git2.getRepository().getWorkTree()); - assertEquals(new File(directory, ".git"), git2.getRepository() + assertEquals(new File(directory, Constants.DOT_GIT), git2.getRepository() .getDirectory()); } @Test + public void testCloneRepositoryDefaultDirectory() + throws URISyntaxException, JGitInternalException { + CloneCommand command = Git.cloneRepository().setURI(fileUri()); + + command.verifyDirectories(new URIish(fileUri())); + File directory = command.getDirectory(); + assertEquals(git.getRepository().getWorkTree().getName(), directory.getName()); + } + + @Test + public void testCloneBareRepositoryDefaultDirectory() + throws URISyntaxException, JGitInternalException { + CloneCommand command = Git.cloneRepository().setURI(fileUri()).setBare(true); + + command.verifyDirectories(new URIish(fileUri())); + File directory = command.getDirectory(); + assertEquals(git.getRepository().getWorkTree().getName() + Constants.DOT_GIT_EXT, directory.getName()); + } + + @Test public void testCloneRepositoryExplicitGitDirNonStd() throws IOException, JGitInternalException, GitAPIException { File directory = createTempDirectory("testCloneRepository"); @@ -166,8 +189,8 @@ assertEquals(directory, git2.getRepository().getWorkTree()); assertEquals(gDir, git2.getRepository() .getDirectory()); - assertTrue(new File(directory, ".git").isFile()); - assertFalse(new File(gDir, ".git").exists()); + assertTrue(new File(directory, Constants.DOT_GIT).isFile()); + assertFalse(new File(gDir, Constants.DOT_GIT).exists()); } @Test @@ -578,17 +601,17 @@ SubmoduleWalk walk = SubmoduleWalk.forIndex(git2.getRepository()); assertTrue(walk.next()); - Repository clonedSub1 = walk.getRepository(); - assertNotNull(clonedSub1); - assertEquals( - new File(git2.getRepository().getWorkTree(), walk.getPath()), - clonedSub1.getWorkTree()); - assertEquals(new File(new File(git2.getRepository().getDirectory(), - "modules"), walk.getPath()), - clonedSub1.getDirectory()); - status = new SubmoduleStatusCommand(clonedSub1); - statuses = status.call(); - clonedSub1.close(); + try (Repository clonedSub1 = walk.getRepository()) { + assertNotNull(clonedSub1); + assertEquals(new File(git2.getRepository().getWorkTree(), + walk.getPath()), clonedSub1.getWorkTree()); + assertEquals( + new File(new File(git2.getRepository().getDirectory(), + "modules"), walk.getPath()), + clonedSub1.getDirectory()); + status = new SubmoduleStatusCommand(clonedSub1); + statuses = status.call(); + } pathStatus = statuses.get(path); assertNotNull(pathStatus); assertEquals(SubmoduleStatusType.INITIALIZED, pathStatus.getType()); @@ -605,11 +628,10 @@ command.setURI(fileUri()); Git git2 = command.call(); addRepoToClose(git2.getRepository()); - assertFalse(git2 - .getRepository() - .getConfig() - .getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test", - ConfigConstants.CONFIG_KEY_REBASE, false)); + assertNull(git2.getRepository().getConfig().getEnum( + BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, "test", + ConfigConstants.CONFIG_KEY_REBASE, null)); FileBasedConfig userConfig = SystemReader.getInstance().openUserConfig( null, git.getRepository().getFS()); @@ -623,11 +645,12 @@ command.setURI(fileUri()); git2 = command.call(); addRepoToClose(git2.getRepository()); - assertTrue(git2 - .getRepository() - .getConfig() - .getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test", - ConfigConstants.CONFIG_KEY_REBASE, false)); + assertEquals(BranchRebaseMode.REBASE, + git2.getRepository().getConfig().getEnum( + BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, "test", + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); userConfig.setString(ConfigConstants.CONFIG_BRANCH_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE, @@ -639,11 +662,12 @@ command.setURI(fileUri()); git2 = command.call(); addRepoToClose(git2.getRepository()); - assertTrue(git2 - .getRepository() - .getConfig() - .getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test", - ConfigConstants.CONFIG_KEY_REBASE, false)); + assertEquals(BranchRebaseMode.REBASE, + git2.getRepository().getConfig().getEnum( + BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, "test", + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import java.io.File; import java.io.IOException; @@ -78,96 +79,96 @@ GitAPIException { // do 4 commits - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - git.commit().setMessage("second commit").setCommitter(committer).call(); - git.commit().setMessage("third commit").setAuthor(author).call(); - git.commit().setMessage("fourth commit").setAuthor(author) - .setCommitter(committer).call(); - Iterable commits = git.log().call(); - - // check that all commits came in correctly - PersonIdent defaultCommitter = new PersonIdent(db); - PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter, - committer, author, author }; - PersonIdent expectedCommitters[] = new PersonIdent[] { - defaultCommitter, committer, defaultCommitter, committer }; - String expectedMessages[] = new String[] { "initial commit", - "second commit", "third commit", "fourth commit" }; - int l = expectedAuthors.length - 1; - for (RevCommit c : commits) { - assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() - .getName()); - assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() - .getName()); - assertEquals(c.getFullMessage(), expectedMessages[l]); - l--; - } - assertEquals(l, -1); - ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); - reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + git.commit().setMessage("second commit").setCommitter(committer).call(); + git.commit().setMessage("third commit").setAuthor(author).call(); + git.commit().setMessage("fourth commit").setAuthor(author) + .setCommitter(committer).call(); + Iterable commits = git.log().call(); + + // check that all commits came in correctly + PersonIdent defaultCommitter = new PersonIdent(db); + PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter, + committer, author, author }; + PersonIdent expectedCommitters[] = new PersonIdent[] { + defaultCommitter, committer, defaultCommitter, committer }; + String expectedMessages[] = new String[] { "initial commit", + "second commit", "third commit", "fourth commit" }; + int l = expectedAuthors.length - 1; + for (RevCommit c : commits) { + assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() + .getName()); + assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() + .getName()); + assertEquals(c.getFullMessage(), expectedMessages[l]); + l--; + } + assertEquals(l, -1); + ReflogReader reader = db.getReflogReader(Constants.HEAD); + assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); + reader = db.getReflogReader(db.getBranch()); + assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); + } } @Test public void testLogWithFilter() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - // create first file - File file = new File(db.getWorkTree(), "a.txt"); - FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content1"); - writer.close(); - - // First commit - a.txt file - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("commit1").setCommitter(committer).call(); - - // create second file - file = new File(db.getWorkTree(), "b.txt"); - FileUtils.createNewFile(file); - writer = new PrintWriter(file); - writer.print("content2"); - writer.close(); - - // Second commit - b.txt file - git.add().addFilepattern("b.txt").call(); - git.commit().setMessage("commit2").setCommitter(committer).call(); - - // First log - a.txt filter - int count = 0; - for (RevCommit c : git.log().addPath("a.txt").call()) { - assertEquals("commit1", c.getFullMessage()); - count++; - } - assertEquals(1, count); - - // Second log - b.txt filter - count = 0; - for (RevCommit c : git.log().addPath("b.txt").call()) { - assertEquals("commit2", c.getFullMessage()); - count++; - } - assertEquals(1, count); - - // Third log - without filter - count = 0; - for (RevCommit c : git.log().call()) { - assertEquals(committer, c.getCommitterIdent()); - count++; + try (Git git = new Git(db)) { + // create first file + File file = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content1"); + } + + // First commit - a.txt file + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("commit1").setCommitter(committer).call(); + + // create second file + file = new File(db.getWorkTree(), "b.txt"); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content2"); + } + + // Second commit - b.txt file + git.add().addFilepattern("b.txt").call(); + git.commit().setMessage("commit2").setCommitter(committer).call(); + + // First log - a.txt filter + int count = 0; + for (RevCommit c : git.log().addPath("a.txt").call()) { + assertEquals("commit1", c.getFullMessage()); + count++; + } + assertEquals(1, count); + + // Second log - b.txt filter + count = 0; + for (RevCommit c : git.log().addPath("b.txt").call()) { + assertEquals("commit2", c.getFullMessage()); + count++; + } + assertEquals(1, count); + + // Third log - without filter + count = 0; + for (RevCommit c : git.log().call()) { + assertEquals(committer, c.getCommitterIdent()); + count++; + } + assertEquals(2, count); } - assertEquals(2, count); } // try to do a commit without specifying a message. Should fail! @Test public void testWrongParams() throws GitAPIException { - Git git = new Git(db); - try { + try (Git git = new Git(db)) { git.commit().setAuthor(author).call(); fail("Didn't get the expected exception"); } catch (NoMessageException e) { @@ -179,48 +180,50 @@ // exceptions @Test public void testMultipleInvocations() throws GitAPIException { - Git git = new Git(db); - CommitCommand commitCmd = git.commit(); - commitCmd.setMessage("initial commit").call(); - try { - // check that setters can't be called after invocation - commitCmd.setAuthor(author); - fail("didn't catch the expected exception"); - } catch (IllegalStateException e) { - // expected - } - LogCommand logCmd = git.log(); - logCmd.call(); - try { - // check that call can't be called twice + try (Git git = new Git(db)) { + CommitCommand commitCmd = git.commit(); + commitCmd.setMessage("initial commit").call(); + try { + // check that setters can't be called after invocation + commitCmd.setAuthor(author); + fail("didn't catch the expected exception"); + } catch (IllegalStateException e) { + // expected + } + LogCommand logCmd = git.log(); logCmd.call(); - fail("didn't catch the expected exception"); - } catch (IllegalStateException e) { - // expected + try { + // check that call can't be called twice + logCmd.call(); + fail("didn't catch the expected exception"); + } catch (IllegalStateException e) { + // expected + } } } @Test public void testMergeEmptyBranches() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - RefUpdate r = db.updateRef("refs/heads/side"); - r.setNewObjectId(db.resolve(Constants.HEAD)); - assertEquals(r.forceUpdate(), RefUpdate.Result.NEW); - RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call(); - db.updateRef(Constants.HEAD).link("refs/heads/side"); - RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call(); - - write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId - .toString(db.resolve("refs/heads/master"))); - write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging"); - - RevCommit commit = git.commit().call(); - RevCommit[] parents = commit.getParents(); - assertEquals(parents[0], firstSide); - assertEquals(parents[1], second); - assertEquals(2, parents.length); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + RefUpdate r = db.updateRef("refs/heads/side"); + r.setNewObjectId(db.resolve(Constants.HEAD)); + assertEquals(r.forceUpdate(), RefUpdate.Result.NEW); + RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call(); + db.updateRef(Constants.HEAD).link("refs/heads/side"); + RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call(); + + write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId + .toString(db.resolve("refs/heads/master"))); + write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging"); + + RevCommit commit = git.commit().call(); + RevCommit[] parents = commit.getParents(); + assertEquals(parents[0], firstSide); + assertEquals(parents[1], second); + assertEquals(2, parents.length); + } } @Test @@ -228,60 +231,60 @@ JGitInternalException, GitAPIException { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content"); - writer.close(); - - Git git = new Git(db); - git.add().addFilepattern("a.txt").call(); - RevCommit commit = git.commit().setMessage("initial commit").call(); - TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); - assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", - tw.getObjectId(0).getName()); - - writer = new PrintWriter(file); - writer.print("content2"); - writer.close(); - commit = git.commit().setMessage("second commit").call(); - tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); - assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", - tw.getObjectId(0).getName()); - - commit = git.commit().setAll(true).setMessage("third commit") - .setAll(true).call(); - tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); - assertEquals("db00fd65b218578127ea51f3dffac701f12f486a", - tw.getObjectId(0).getName()); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content"); + } + + try (Git git = new Git(db)) { + git.add().addFilepattern("a.txt").call(); + RevCommit commit = git.commit().setMessage("initial commit").call(); + TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", + tw.getObjectId(0).getName()); + + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content2"); + } + commit = git.commit().setMessage("second commit").call(); + tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", + tw.getObjectId(0).getName()); + + commit = git.commit().setAll(true).setMessage("third commit") + .setAll(true).call(); + tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("db00fd65b218578127ea51f3dffac701f12f486a", + tw.getObjectId(0).getName()); + } } @Test public void testModeChange() throws IOException, GitAPIException { - if (System.getProperty("os.name").startsWith("Windows")) - return; // SKIP - Git git = new Git(db); - - // create file - File file = new File(db.getWorkTree(), "a.txt"); - FileUtils.createNewFile(file); - PrintWriter writer = new PrintWriter(file); - writer.print("content1"); - writer.close(); - - // First commit - a.txt file - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("commit1").setCommitter(committer).call(); - - // pure mode change should be committable - FS fs = db.getFS(); - fs.setExecute(file, true); - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("mode change").setCommitter(committer).call(); - - // pure mode change should be committable with -o option - fs.setExecute(file, false); - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("mode change").setCommitter(committer) - .setOnly("a.txt").call(); + assumeFalse(System.getProperty("os.name").startsWith("Windows"));// SKIP + try (Git git = new Git(db)) { + // create file + File file = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print("content1"); + } + + // First commit - a.txt file + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("commit1").setCommitter(committer).call(); + + // pure mode change should be committable + FS fs = db.getFS(); + fs.setExecute(file, true); + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("mode change").setCommitter(committer).call(); + + // pure mode change should be committable with -o option + fs.setExecute(file, false); + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("mode change").setCommitter(committer) + .setOnly("a.txt").call(); + } } @Test @@ -289,112 +292,115 @@ JGitInternalException, MissingObjectException, IncorrectObjectTypeException { // do 4 commits and set the range to the second and fourth one - Git git = new Git(db); - git.commit().setMessage("first commit").call(); - RevCommit second = git.commit().setMessage("second commit") - .setCommitter(committer).call(); - git.commit().setMessage("third commit").setAuthor(author).call(); - RevCommit last = git.commit().setMessage("fourth commit").setAuthor( - author) - .setCommitter(committer).call(); - Iterable commits = git.log().addRange(second.getId(), - last.getId()).call(); - - // check that we have the third and fourth commit - PersonIdent defaultCommitter = new PersonIdent(db); - PersonIdent expectedAuthors[] = new PersonIdent[] { author, author }; - PersonIdent expectedCommitters[] = new PersonIdent[] { - defaultCommitter, committer }; - String expectedMessages[] = new String[] { "third commit", - "fourth commit" }; - int l = expectedAuthors.length - 1; - for (RevCommit c : commits) { - assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() - .getName()); - assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() - .getName()); - assertEquals(c.getFullMessage(), expectedMessages[l]); - l--; + try (Git git = new Git(db)) { + git.commit().setMessage("first commit").call(); + RevCommit second = git.commit().setMessage("second commit") + .setCommitter(committer).call(); + git.commit().setMessage("third commit").setAuthor(author).call(); + RevCommit last = git.commit().setMessage("fourth commit").setAuthor( + author) + .setCommitter(committer).call(); + Iterable commits = git.log().addRange(second.getId(), + last.getId()).call(); + + // check that we have the third and fourth commit + PersonIdent defaultCommitter = new PersonIdent(db); + PersonIdent expectedAuthors[] = new PersonIdent[] { author, author }; + PersonIdent expectedCommitters[] = new PersonIdent[] { + defaultCommitter, committer }; + String expectedMessages[] = new String[] { "third commit", + "fourth commit" }; + int l = expectedAuthors.length - 1; + for (RevCommit c : commits) { + assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() + .getName()); + assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() + .getName()); + assertEquals(c.getFullMessage(), expectedMessages[l]); + l--; + } + assertEquals(l, -1); } - assertEquals(l, -1); } @Test public void testCommitAmend() throws JGitInternalException, IOException, GitAPIException { - Git git = new Git(db); - git.commit().setMessage("first comit").call(); // typo - git.commit().setAmend(true).setMessage("first commit").call(); - - Iterable commits = git.log().call(); - int c = 0; - for (RevCommit commit : commits) { - assertEquals("first commit", commit.getFullMessage()); - c++; - } - assertEquals(1, c); - ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment() - .startsWith("commit (amend):")); - reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment() - .startsWith("commit (amend):")); + try (Git git = new Git(db)) { + git.commit().setMessage("first comit").call(); // typo + git.commit().setAmend(true).setMessage("first commit").call(); + + Iterable commits = git.log().call(); + int c = 0; + for (RevCommit commit : commits) { + assertEquals("first commit", commit.getFullMessage()); + c++; + } + assertEquals(1, c); + ReflogReader reader = db.getReflogReader(Constants.HEAD); + assertTrue(reader.getLastEntry().getComment() + .startsWith("commit (amend):")); + reader = db.getReflogReader(db.getBranch()); + assertTrue(reader.getLastEntry().getComment() + .startsWith("commit (amend):")); + } } @Test public void testInsertChangeId() throws JGitInternalException, GitAPIException { - Git git = new Git(db); - String messageHeader = "Some header line\n\nSome detail explanation\n"; - String changeIdTemplate = "\nChange-Id: I" - + ObjectId.zeroId().getName() + "\n"; - String messageFooter = "Some foooter lines\nAnother footer line\n"; - RevCommit commit = git.commit().setMessage( - messageHeader + messageFooter) - .setInsertChangeId(true).call(); - // we should find a real change id (at the end of the file) - byte[] chars = commit.getFullMessage().getBytes(); - int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2); - String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1, - chars.length); - assertTrue(lastLine.contains("Change-Id:")); - assertFalse(lastLine.contains( - "Change-Id: I" + ObjectId.zeroId().getName())); - - commit = git.commit().setMessage( - messageHeader + changeIdTemplate + messageFooter) - .setInsertChangeId(true).call(); - // we should find a real change id (in the line as dictated by the - // template) - chars = commit.getFullMessage().getBytes(); - int lineStart = 0; - int lineEnd = 0; - for (int i = 0; i < 4; i++) { - lineStart = RawParseUtils.nextLF(chars, lineStart); - } - lineEnd = RawParseUtils.nextLF(chars, lineStart); - - String line = RawParseUtils.decode(chars, lineStart, lineEnd); - - assertTrue(line.contains("Change-Id:")); - assertFalse(line.contains( - "Change-Id: I" + ObjectId.zeroId().getName())); - - commit = git.commit().setMessage( - messageHeader + changeIdTemplate + messageFooter) - .setInsertChangeId(false).call(); - // we should find the untouched template - chars = commit.getFullMessage().getBytes(); - lineStart = 0; - lineEnd = 0; - for (int i = 0; i < 4; i++) { - lineStart = RawParseUtils.nextLF(chars, lineStart); - } - lineEnd = RawParseUtils.nextLF(chars, lineStart); + try (Git git = new Git(db)) { + String messageHeader = "Some header line\n\nSome detail explanation\n"; + String changeIdTemplate = "\nChange-Id: I" + + ObjectId.zeroId().getName() + "\n"; + String messageFooter = "Some foooter lines\nAnother footer line\n"; + RevCommit commit = git.commit().setMessage( + messageHeader + messageFooter) + .setInsertChangeId(true).call(); + // we should find a real change id (at the end of the file) + byte[] chars = commit.getFullMessage().getBytes(); + int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2); + String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1, + chars.length); + assertTrue(lastLine.contains("Change-Id:")); + assertFalse(lastLine.contains( + "Change-Id: I" + ObjectId.zeroId().getName())); + + commit = git.commit().setMessage( + messageHeader + changeIdTemplate + messageFooter) + .setInsertChangeId(true).call(); + // we should find a real change id (in the line as dictated by the + // template) + chars = commit.getFullMessage().getBytes(); + int lineStart = 0; + int lineEnd = 0; + for (int i = 0; i < 4; i++) { + lineStart = RawParseUtils.nextLF(chars, lineStart); + } + lineEnd = RawParseUtils.nextLF(chars, lineStart); + + String line = RawParseUtils.decode(chars, lineStart, lineEnd); + + assertTrue(line.contains("Change-Id:")); + assertFalse(line.contains( + "Change-Id: I" + ObjectId.zeroId().getName())); + + commit = git.commit().setMessage( + messageHeader + changeIdTemplate + messageFooter) + .setInsertChangeId(false).call(); + // we should find the untouched template + chars = commit.getFullMessage().getBytes(); + lineStart = 0; + lineEnd = 0; + for (int i = 0; i < 4; i++) { + lineStart = RawParseUtils.nextLF(chars, lineStart); + } + lineEnd = RawParseUtils.nextLF(chars, lineStart); - line = RawParseUtils.decode(chars, lineStart, lineEnd); + line = RawParseUtils.decode(chars, lineStart, lineEnd); - assertTrue(commit.getFullMessage().contains( - "Change-Id: I" + ObjectId.zeroId().getName())); + assertTrue(commit.getFullMessage().contains( + "Change-Id: I" + ObjectId.zeroId().getName())); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,15 +43,18 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; +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.File; import java.util.Date; import java.util.List; import java.util.TimeZone; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.dircache.DirCache; @@ -65,6 +68,7 @@ import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; @@ -72,6 +76,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FS; +import org.junit.Ignore; import org.junit.Test; /** @@ -88,30 +93,37 @@ FS executableFs = new FS() { + @Override public boolean supportsExecute() { return true; } + @Override public boolean setExecute(File f, boolean canExec) { return true; } + @Override public ProcessBuilder runInShell(String cmd, String[] args) { return null; } + @Override public boolean retryFailedLockFileCommit() { return false; } + @Override public FS newInstance() { return this; } + @Override protected File discoverGitExe() { return null; } + @Override public boolean canExecute(File f) { return true; } @@ -133,30 +145,37 @@ FS nonExecutableFs = new FS() { + @Override public boolean supportsExecute() { return false; } + @Override public boolean setExecute(File f, boolean canExec) { return false; } + @Override public ProcessBuilder runInShell(String cmd, String[] args) { return null; } + @Override public boolean retryFailedLockFileCommit() { return false; } + @Override public FS newInstance() { return this; } + @Override protected File discoverGitExe() { return null; } + @Override public boolean canExecute(File f) { return false; } @@ -183,295 +202,367 @@ @Test public void commitNewSubmodule() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); - - SubmoduleAddCommand command = new SubmoduleAddCommand(db); - String path = "sub"; - command.setPath(path); - String uri = db.getDirectory().toURI().toString(); - command.setURI(uri); - Repository repo = command.call(); - assertNotNull(repo); - addRepoToClose(repo); - - SubmoduleWalk generator = SubmoduleWalk.forIndex(db); - assertTrue(generator.next()); - assertEquals(path, generator.getPath()); - assertEquals(commit, generator.getObjectId()); - assertEquals(uri, generator.getModulesUrl()); - assertEquals(path, generator.getModulesPath()); - assertEquals(uri, generator.getConfigUrl()); - Repository subModRepo = generator.getRepository(); - assertNotNull(subModRepo); - subModRepo.close(); - assertEquals(commit, repo.resolve(Constants.HEAD)); - - RevCommit submoduleCommit = git.commit().setMessage("submodule add") - .setOnly(path).call(); - assertNotNull(submoduleCommit); - TreeWalk walk = new TreeWalk(db); - walk.addTree(commit.getTree()); - walk.addTree(submoduleCommit.getTree()); - walk.setFilter(TreeFilter.ANY_DIFF); - List diffs = DiffEntry.scan(walk); - assertEquals(1, diffs.size()); - DiffEntry subDiff = diffs.get(0); - assertEquals(FileMode.MISSING, subDiff.getOldMode()); - assertEquals(FileMode.GITLINK, subDiff.getNewMode()); - assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId()); - assertEquals(commit, subDiff.getNewId().toObjectId()); - assertEquals(path, subDiff.getNewPath()); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + command.setPath(path); + String uri = db.getDirectory().toURI().toString(); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + addRepoToClose(repo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + assertEquals(uri, generator.getConfigUrl()); + try (Repository subModRepo = generator.getRepository()) { + assertNotNull(subModRepo); + } + assertEquals(commit, repo.resolve(Constants.HEAD)); + + RevCommit submoduleCommit = git.commit().setMessage("submodule add") + .setOnly(path).call(); + assertNotNull(submoduleCommit); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(commit.getTree()); + walk.addTree(submoduleCommit.getTree()); + walk.setFilter(TreeFilter.ANY_DIFF); + List diffs = DiffEntry.scan(walk); + assertEquals(1, diffs.size()); + DiffEntry subDiff = diffs.get(0); + assertEquals(FileMode.MISSING, subDiff.getOldMode()); + assertEquals(FileMode.GITLINK, subDiff.getNewMode()); + assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId()); + assertEquals(commit, subDiff.getNewId().toObjectId()); + assertEquals(path, subDiff.getNewPath()); + } + } } @Test public void commitSubmoduleUpdate() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit2 = git.commit().setMessage("edit file").call(); - - SubmoduleAddCommand command = new SubmoduleAddCommand(db); - String path = "sub"; - command.setPath(path); - String uri = db.getDirectory().toURI().toString(); - command.setURI(uri); - Repository repo = command.call(); - assertNotNull(repo); - addRepoToClose(repo); - - SubmoduleWalk generator = SubmoduleWalk.forIndex(db); - assertTrue(generator.next()); - assertEquals(path, generator.getPath()); - assertEquals(commit2, generator.getObjectId()); - assertEquals(uri, generator.getModulesUrl()); - assertEquals(path, generator.getModulesPath()); - assertEquals(uri, generator.getConfigUrl()); - Repository subModRepo = generator.getRepository(); - assertNotNull(subModRepo); - subModRepo.close(); - assertEquals(commit2, repo.resolve(Constants.HEAD)); - - RevCommit submoduleAddCommit = git.commit().setMessage("submodule add") - .setOnly(path).call(); - assertNotNull(submoduleAddCommit); - - RefUpdate update = repo.updateRef(Constants.HEAD); - update.setNewObjectId(commit); - assertEquals(Result.FORCED, update.forceUpdate()); - - RevCommit submoduleEditCommit = git.commit() - .setMessage("submodule add").setOnly(path).call(); - assertNotNull(submoduleEditCommit); - TreeWalk walk = new TreeWalk(db); - walk.addTree(submoduleAddCommit.getTree()); - walk.addTree(submoduleEditCommit.getTree()); - walk.setFilter(TreeFilter.ANY_DIFF); - List diffs = DiffEntry.scan(walk); - assertEquals(1, diffs.size()); - DiffEntry subDiff = diffs.get(0); - assertEquals(FileMode.GITLINK, subDiff.getOldMode()); - assertEquals(FileMode.GITLINK, subDiff.getNewMode()); - assertEquals(commit2, subDiff.getOldId().toObjectId()); - assertEquals(commit, subDiff.getNewId().toObjectId()); - assertEquals(path, subDiff.getNewPath()); - assertEquals(path, subDiff.getOldPath()); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit2 = git.commit().setMessage("edit file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + command.setPath(path); + String uri = db.getDirectory().toURI().toString(); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + addRepoToClose(repo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit2, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + assertEquals(uri, generator.getConfigUrl()); + try (Repository subModRepo = generator.getRepository()) { + assertNotNull(subModRepo); + } + assertEquals(commit2, repo.resolve(Constants.HEAD)); + + RevCommit submoduleAddCommit = git.commit().setMessage("submodule add") + .setOnly(path).call(); + assertNotNull(submoduleAddCommit); + + RefUpdate update = repo.updateRef(Constants.HEAD); + update.setNewObjectId(commit); + assertEquals(Result.FORCED, update.forceUpdate()); + + RevCommit submoduleEditCommit = git.commit() + .setMessage("submodule add").setOnly(path).call(); + assertNotNull(submoduleEditCommit); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(submoduleAddCommit.getTree()); + walk.addTree(submoduleEditCommit.getTree()); + walk.setFilter(TreeFilter.ANY_DIFF); + List diffs = DiffEntry.scan(walk); + assertEquals(1, diffs.size()); + DiffEntry subDiff = diffs.get(0); + assertEquals(FileMode.GITLINK, subDiff.getOldMode()); + assertEquals(FileMode.GITLINK, subDiff.getNewMode()); + assertEquals(commit2, subDiff.getOldId().toObjectId()); + assertEquals(commit, subDiff.getNewId().toObjectId()); + assertEquals(path, subDiff.getNewPath()); + assertEquals(path, subDiff.getOldPath()); + } + } } + @Ignore("very flaky when run with Hudson") @Test public void commitUpdatesSmudgedEntries() throws Exception { - Git git = new Git(db); - - File file1 = writeTrashFile("file1.txt", "content1"); - assertTrue(file1.setLastModified(file1.lastModified() - 5000)); - File file2 = writeTrashFile("file2.txt", "content2"); - assertTrue(file2.setLastModified(file2.lastModified() - 5000)); - File file3 = writeTrashFile("file3.txt", "content3"); - assertTrue(file3.setLastModified(file3.lastModified() - 5000)); - - assertNotNull(git.add().addFilepattern("file1.txt") - .addFilepattern("file2.txt").addFilepattern("file3.txt").call()); - RevCommit commit = git.commit().setMessage("add files").call(); - assertNotNull(commit); - - DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); - int file1Size = cache.getEntry("file1.txt").getLength(); - int file2Size = cache.getEntry("file2.txt").getLength(); - int file3Size = cache.getEntry("file3.txt").getLength(); - ObjectId file2Id = cache.getEntry("file2.txt").getObjectId(); - ObjectId file3Id = cache.getEntry("file3.txt").getObjectId(); - assertTrue(file1Size > 0); - assertTrue(file2Size > 0); - assertTrue(file3Size > 0); - - // Smudge entries - cache = DirCache.lock(db.getIndexFile(), db.getFS()); - cache.getEntry("file1.txt").setLength(0); - cache.getEntry("file2.txt").setLength(0); - cache.getEntry("file3.txt").setLength(0); - cache.write(); - assertTrue(cache.commit()); - - // Verify entries smudged - cache = DirCache.read(db.getIndexFile(), db.getFS()); - assertEquals(0, cache.getEntry("file1.txt").getLength()); - assertEquals(0, cache.getEntry("file2.txt").getLength()); - assertEquals(0, cache.getEntry("file3.txt").getLength()); - - long indexTime = db.getIndexFile().lastModified(); - db.getIndexFile().setLastModified(indexTime - 5000); - - write(file1, "content4"); - assertTrue(file1.setLastModified(file1.lastModified() + 2500)); - assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") - .call()); - - cache = db.readDirCache(); - assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); - assertEquals(file2Size, cache.getEntry("file2.txt").getLength()); - assertEquals(file3Size, cache.getEntry("file3.txt").getLength()); - assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId()); - assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId()); + try (Git git = new Git(db)) { + File file1 = writeTrashFile("file1.txt", "content1"); + assertTrue(file1.setLastModified(file1.lastModified() - 5000)); + File file2 = writeTrashFile("file2.txt", "content2"); + assertTrue(file2.setLastModified(file2.lastModified() - 5000)); + File file3 = writeTrashFile("file3.txt", "content3"); + assertTrue(file3.setLastModified(file3.lastModified() - 5000)); + + assertNotNull(git.add().addFilepattern("file1.txt") + .addFilepattern("file2.txt").addFilepattern("file3.txt").call()); + RevCommit commit = git.commit().setMessage("add files").call(); + assertNotNull(commit); + + DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); + int file1Size = cache.getEntry("file1.txt").getLength(); + int file2Size = cache.getEntry("file2.txt").getLength(); + int file3Size = cache.getEntry("file3.txt").getLength(); + ObjectId file2Id = cache.getEntry("file2.txt").getObjectId(); + ObjectId file3Id = cache.getEntry("file3.txt").getObjectId(); + assertTrue(file1Size > 0); + assertTrue(file2Size > 0); + assertTrue(file3Size > 0); + + // Smudge entries + cache = DirCache.lock(db.getIndexFile(), db.getFS()); + cache.getEntry("file1.txt").setLength(0); + cache.getEntry("file2.txt").setLength(0); + cache.getEntry("file3.txt").setLength(0); + cache.write(); + assertTrue(cache.commit()); + + // Verify entries smudged + cache = DirCache.read(db.getIndexFile(), db.getFS()); + assertEquals(0, cache.getEntry("file1.txt").getLength()); + assertEquals(0, cache.getEntry("file2.txt").getLength()); + assertEquals(0, cache.getEntry("file3.txt").getLength()); + + long indexTime = db.getIndexFile().lastModified(); + db.getIndexFile().setLastModified(indexTime - 5000); + + write(file1, "content4"); + assertTrue(file1.setLastModified(file1.lastModified() + 2500)); + assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") + .call()); + + cache = db.readDirCache(); + assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); + assertEquals(file2Size, cache.getEntry("file2.txt").getLength()); + assertEquals(file3Size, cache.getEntry("file3.txt").getLength()); + assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId()); + assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId()); + } } + @Ignore("very flaky when run with Hudson") @Test public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { - Git git = new Git(db); - - File file1 = writeTrashFile("file1.txt", "content1"); - assertTrue(file1.setLastModified(file1.lastModified() - 5000)); - File file2 = writeTrashFile("file2.txt", "content2"); - assertTrue(file2.setLastModified(file2.lastModified() - 5000)); - - assertNotNull(git.add().addFilepattern("file1.txt") - .addFilepattern("file2.txt").call()); - RevCommit commit = git.commit().setMessage("add files").call(); - assertNotNull(commit); - - DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); - int file1Size = cache.getEntry("file1.txt").getLength(); - int file2Size = cache.getEntry("file2.txt").getLength(); - assertTrue(file1Size > 0); - assertTrue(file2Size > 0); - - writeTrashFile("file2.txt", "content3"); - assertNotNull(git.add().addFilepattern("file2.txt").call()); - writeTrashFile("file2.txt", "content4"); - - // Smudge entries - cache = DirCache.lock(db.getIndexFile(), db.getFS()); - cache.getEntry("file1.txt").setLength(0); - cache.getEntry("file2.txt").setLength(0); - cache.write(); - assertTrue(cache.commit()); - - // Verify entries smudged - cache = db.readDirCache(); - assertEquals(0, cache.getEntry("file1.txt").getLength()); - assertEquals(0, cache.getEntry("file2.txt").getLength()); - - long indexTime = db.getIndexFile().lastModified(); - db.getIndexFile().setLastModified(indexTime - 5000); - - write(file1, "content5"); - assertTrue(file1.setLastModified(file1.lastModified() + 1000)); - - assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") - .call()); - - cache = db.readDirCache(); - assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); - assertEquals(0, cache.getEntry("file2.txt").getLength()); + try (Git git = new Git(db)) { + File file1 = writeTrashFile("file1.txt", "content1"); + assertTrue(file1.setLastModified(file1.lastModified() - 5000)); + File file2 = writeTrashFile("file2.txt", "content2"); + assertTrue(file2.setLastModified(file2.lastModified() - 5000)); + + assertNotNull(git.add().addFilepattern("file1.txt") + .addFilepattern("file2.txt").call()); + RevCommit commit = git.commit().setMessage("add files").call(); + assertNotNull(commit); + + DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); + int file1Size = cache.getEntry("file1.txt").getLength(); + int file2Size = cache.getEntry("file2.txt").getLength(); + assertTrue(file1Size > 0); + assertTrue(file2Size > 0); + + writeTrashFile("file2.txt", "content3"); + assertNotNull(git.add().addFilepattern("file2.txt").call()); + writeTrashFile("file2.txt", "content4"); + + // Smudge entries + cache = DirCache.lock(db.getIndexFile(), db.getFS()); + cache.getEntry("file1.txt").setLength(0); + cache.getEntry("file2.txt").setLength(0); + cache.write(); + assertTrue(cache.commit()); + + // Verify entries smudged + cache = db.readDirCache(); + assertEquals(0, cache.getEntry("file1.txt").getLength()); + assertEquals(0, cache.getEntry("file2.txt").getLength()); + + long indexTime = db.getIndexFile().lastModified(); + db.getIndexFile().setLastModified(indexTime - 5000); + + write(file1, "content5"); + assertTrue(file1.setLastModified(file1.lastModified() + 1000)); + + assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") + .call()); + + cache = db.readDirCache(); + assertEquals(file1Size, cache.getEntry("file1.txt").getLength()); + assertEquals(0, cache.getEntry("file2.txt").getLength()); + } } @Test public void commitAfterSquashMerge() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + createBranch(first, "refs/heads/branch1"); + checkoutBranch("refs/heads/branch1"); + + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + git.commit().setMessage("second commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + checkoutBranch("refs/heads/master"); + + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, + result.getMergeStatus()); + + // comment not set, should be inferred from SQUASH_MSG + RevCommit squashedCommit = git.commit().call(); + + assertEquals(1, squashedCommit.getParentCount()); + assertNull(db.readSquashCommitMsg()); + assertEquals("commit: Squashed commit of the following:", db + .getReflogReader(Constants.HEAD).getLastEntry().getComment()); + assertEquals("commit: Squashed commit of the following:", db + .getReflogReader(db.getBranch()).getLastEntry().getComment()); + } + } - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - createBranch(first, "refs/heads/branch1"); - checkoutBranch("refs/heads/branch1"); - - writeTrashFile("file2", "file2"); - git.add().addFilepattern("file2").call(); - git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - checkoutBranch("refs/heads/master"); - - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, - result.getMergeStatus()); - - // comment not set, should be inferred from SQUASH_MSG - RevCommit squashedCommit = git.commit().call(); - - assertEquals(1, squashedCommit.getParentCount()); - assertNull(db.readSquashCommitMsg()); - assertEquals("commit: Squashed commit of the following:", db - .getReflogReader(Constants.HEAD).getLastEntry().getComment()); - assertEquals("commit: Squashed commit of the following:", db - .getReflogReader(db.getBranch()).getLastEntry().getComment()); + @Test + public void testReflogs() throws Exception { + try (Git git = new Git(db)) { + writeTrashFile("f", "1"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("c1").call(); + writeTrashFile("f", "2"); + git.commit().setMessage("c2").setAll(true).setReflogComment(null) + .call(); + writeTrashFile("f", "3"); + git.commit().setMessage("c3").setAll(true) + .setReflogComment("testRl").call(); + + db.getReflogReader(Constants.HEAD).getReverseEntries(); + + assertEquals("testRl;commit (initial): c1;", reflogComments( + db.getReflogReader(Constants.HEAD).getReverseEntries())); + assertEquals("testRl;commit (initial): c1;", reflogComments( + db.getReflogReader(db.getBranch()).getReverseEntries())); + } + } + + private static String reflogComments(List entries) { + StringBuffer b = new StringBuffer(); + for (ReflogEntry e : entries) { + b.append(e.getComment()).append(";"); + } + return b.toString(); } @Test(expected = WrongRepositoryStateException.class) public void commitAmendOnInitialShouldFail() throws Exception { - Git git = new Git(db); - git.commit().setAmend(true).setMessage("initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setAmend(true).setMessage("initial commit").call(); + } } @Test public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime() throws Exception { - Git git = new Git(db); - - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - - final String authorName = "First Author"; - final String authorEmail = "author@example.org"; - final Date authorDate = new Date(1349621117000L); - PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail, - authorDate, TimeZone.getTimeZone("UTC")); - git.commit().setMessage("initial commit").setAuthor(firstAuthor).call(); - - RevCommit amended = git.commit().setAmend(true) - .setMessage("amend commit").call(); - - PersonIdent amendedAuthor = amended.getAuthorIdent(); - assertEquals(authorName, amendedAuthor.getName()); - assertEquals(authorEmail, amendedAuthor.getEmailAddress()); - assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime()); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + + final String authorName = "First Author"; + final String authorEmail = "author@example.org"; + final Date authorDate = new Date(1349621117000L); + PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail, + authorDate, TimeZone.getTimeZone("UTC")); + git.commit().setMessage("initial commit").setAuthor(firstAuthor).call(); + + RevCommit amended = git.commit().setAmend(true) + .setMessage("amend commit").call(); + + PersonIdent amendedAuthor = amended.getAuthorIdent(); + assertEquals(authorName, amendedAuthor.getName()); + assertEquals(authorEmail, amendedAuthor.getEmailAddress()); + assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime()); + } } @Test public void commitAmendWithAuthorShouldUseIt() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + git.commit().setMessage("initial commit").call(); + + RevCommit amended = git.commit().setAmend(true) + .setAuthor("New Author", "newauthor@example.org") + .setMessage("amend commit").call(); + + PersonIdent amendedAuthor = amended.getAuthorIdent(); + assertEquals("New Author", amendedAuthor.getName()); + assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress()); + } + } + + @Test + public void commitEmptyCommits() throws Exception { + try (Git git = new Git(db)) { - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - git.commit().setMessage("initial commit").call(); - - RevCommit amended = git.commit().setAmend(true) - .setAuthor("New Author", "newauthor@example.org") - .setMessage("amend commit").call(); - - PersonIdent amendedAuthor = amended.getAuthorIdent(); - assertEquals("New Author", amendedAuthor.getName()); - assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress()); + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit initial = git.commit().setMessage("initial commit") + .call(); + + RevCommit emptyFollowUp = git.commit() + .setAuthor("New Author", "newauthor@example.org") + .setMessage("no change").call(); + + assertNotEquals(initial.getId(), emptyFollowUp.getId()); + assertEquals(initial.getTree().getId(), + emptyFollowUp.getTree().getId()); + + try { + git.commit().setAuthor("New Author", "newauthor@example.org") + .setMessage("again no change").setAllowEmpty(false) + .call(); + fail("Didn't get the expected EmtpyCommitException"); + } catch (EmtpyCommitException e) { + // expect this exception + } + + // Allow empty commits also when setOnly was set + git.commit().setAuthor("New Author", "newauthor@example.org") + .setMessage("again no change").setOnly("file1") + .setAllowEmpty(true).call(); + } } @Test @@ -499,18 +590,19 @@ + "[unmerged2, mode:100644, stage:3]", indexState(0)); - Git git = new Git(db); - RevCommit commit = git.commit().setOnly("unmerged1") - .setMessage("Only one file").call(); + try (Git git = new Git(db)) { + RevCommit commit = git.commit().setOnly("unmerged1") + .setMessage("Only one file").call(); + + assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]" + + "[unmerged2, mode:100644, stage:1]" + + "[unmerged2, mode:100644, stage:2]" + + "[unmerged2, mode:100644, stage:3]", + indexState(0)); - assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]" - + "[unmerged2, mode:100644, stage:1]" - + "[unmerged2, mode:100644, stage:2]" - + "[unmerged2, mode:100644, stage:3]", - indexState(0)); - - try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) { - assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0)); + try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) { + assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0)); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,19 +70,19 @@ * --------------------------------------------------------------------- * | HEAD DirCache Worktree | HEAD DirCache * --------------------------------------------------------------------- - * f1_1 | - - c | => e: path unknown - * f1_2 | - c - | => no changes + * f1_1 | - - c | => e: path unknown + * f1_2 | - c - | => no changes * f1_3 | c - - | - - * f1_4 | - c c | c c * f1_5 | c c - | - - - * f1_6 | c - c | => no changes - * f1_7 | c c c | => no changes + * f1_6 | c - c | => no changes + * f1_7 | c c c | => no changes * --------------------------------------------------------------------- * f1_8 | - c c' | c' c' * f1_9 | c - c' | c' c' * f1_10 | c c' - | - - * f1_11 | c c c' | c' c' - * f1_12 | c c' c | => no changes + * f1_12 | c c' c | => no changes * f1_13 | c c' c' | c' c' * --------------------------------------------------------------------- * f1_14 | c c' c'' | c'' c'' @@ -97,7 +97,7 @@ * --------------------------------------------------------------------------- * | HEAD DirCache Worktree | HEAD DirCache * --------------------------------------------------------------------------- - * f1_1_f2_14 | - - c | => e: path unknown + * f1_1_f2_14 | - - c | => e: path unknown * f1_2_f2_14 | - c - | - - * f1_6_f2_14 | c - c | c c * f1_7_f2_14 | c c c | c c @@ -1294,10 +1294,12 @@ try { final Repository repo = git.getRepository(); final ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); - final TreeWalk tw = TreeWalk.forPath(repo, path, - new RevWalk(repo).parseTree(headId)); - return new String(tw.getObjectReader().open(tw.getObjectId(0)) - .getBytes()); + try (RevWalk rw = new RevWalk(repo)) { + final TreeWalk tw = TreeWalk.forPath(repo, path, + rw.parseTree(headId)); + return new String(tw.getObjectReader().open(tw.getObjectId(0)) + .getBytes()); + } } catch (Exception e) { return ""; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; @@ -54,6 +55,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.ObjectId; import org.junit.Test; @@ -92,35 +94,98 @@ ObjectId c1 = modify("aaa"); ObjectId c2 = modify("bbb"); - tag("t1"); + tag("alice-t1"); ObjectId c3 = modify("ccc"); - tag("t2"); + tag("bob-t2"); ObjectId c4 = modify("ddd"); assertNull(describe(c1)); assertNull(describe(c1, true)); - assertEquals("t1", describe(c2)); - assertEquals("t2", describe(c3)); - assertEquals("t2-0-g44579eb", describe(c3, true)); + assertNull(describe(c1, "a*", "b*", "c*")); + + assertEquals("alice-t1", describe(c2)); + assertEquals("alice-t1", describe(c2, "alice*")); + assertNull(describe(c2, "bob*")); + assertNull(describe(c2, "?ob*")); + assertEquals("alice-t1", describe(c2, "a*", "b*", "c*")); + + assertEquals("bob-t2", describe(c3)); + assertEquals("bob-t2-0-g44579eb", describe(c3, true)); + assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*")); + assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*")); + assertEquals("bob-t2", describe(c3, "bob*")); + assertEquals("bob-t2", describe(c3, "?ob*")); + assertEquals("bob-t2", describe(c3, "a*", "b*", "c*")); assertNameStartsWith(c4, "3e563c5"); // the value verified with git-describe(1) - assertEquals("t2-1-g3e563c5", describe(c4)); - assertEquals("t2-1-g3e563c5", describe(c4, true)); + assertEquals("bob-t2-1-g3e563c5", describe(c4)); + assertEquals("bob-t2-1-g3e563c5", describe(c4, true)); + assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*")); + assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*")); + assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*")); // test default target - assertEquals("t2-1-g3e563c5", git.describe().call()); + assertEquals("bob-t2-1-g3e563c5", git.describe().call()); + } + + @Test + public void testDescribeMultiMatch() throws Exception { + ObjectId c1 = modify("aaa"); + tag("v1.0.0"); + tick(); + tag("v1.0.1"); + tick(); + tag("v1.1.0"); + tick(); + tag("v1.1.1"); + ObjectId c2 = modify("bbb"); + + // Ensure that if we're interested in any tags, we get the most recent tag + // as per Git behaviour since 1.7.1.1 + if (useAnnotatedTags) { + assertEquals("v1.1.1", describe(c1)); + assertEquals("v1.1.1-1-gb89dead", describe(c2)); + + // Ensure that if we're only interested in one of multiple tags, we get the right match + assertEquals("v1.0.1", describe(c1, "v1.0*")); + assertEquals("v1.1.1", describe(c1, "v1.1*")); + assertEquals("v1.0.1-1-gb89dead", describe(c2, "v1.0*")); + assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*")); + + // Ensure that ordering of match precedence is preserved as per Git behaviour + assertEquals("v1.1.1", describe(c1, "v1.0*", "v1.1*")); + assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*")); + assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.0*", "v1.1*")); + assertEquals("v1.1.1-1-gb89dead", describe(c2, "v1.1*", "v1.0*")); + } else { + // no timestamps so no guarantees on which tag is chosen + assertNotNull(describe(c1)); + assertNotNull(describe(c2)); + + assertNotNull(describe(c1, "v1.0*")); + assertNotNull(describe(c1, "v1.1*")); + assertNotNull(describe(c2, "v1.0*")); + assertNotNull(describe(c2, "v1.1*")); + + // Ensure that ordering of match precedence is preserved as per Git behaviour + assertNotNull(describe(c1, "v1.0*", "v1.1*")); + assertNotNull(describe(c1, "v1.1*", "v1.0*")); + assertNotNull(describe(c2, "v1.0*", "v1.1*")); + assertNotNull(describe(c2, "v1.1*", "v1.0*")); + + } } /** * Make sure it finds a tag when not all ancestries include a tag. * *
        -	 * c1 -+-> T  -
        +	 * c1 -+-> T  -
         	 *     |       |
        -	 *     +-> c3 -+-> c4
        +	 *     +-> c3 -+-> c4
         	 * 
        * * @throws Exception @@ -153,9 +218,9 @@ * When t2 dominates t1, it's clearly preferable to describe by using t2. * *
        -	 * t1 -+-> t2  -
        +	 * t1 -+-> t2  -
         	 *     |       |
        -	 *     +-> c3 -+-> c4
        +	 *     +-> c3 -+-> c4
         	 * 
        * * @throws Exception @@ -185,9 +250,9 @@ * When t1 is nearer than t2, t2 should be found * *
        -	 * c1 -+-> c2 -> t1 -+
        +	 * c1 -+-> c2 -> t1 -+
         	 *     |             |
        -	 *     +-> t2 -> c3 -+-> c4
        +	 *     +-> t2 -> c3 -+-> c4
         	 * 
        * * @throws Exception @@ -214,9 +279,9 @@ * paths * *
        -	 * c1 -+-> t1 -> c2 -+
        +	 * c1 -+-> t1 -> c2 -+
         	 *     |             |
        -	 *     +-> t2 -> c3 -+-> c4
        +	 *     +-> t2 -> c3 -+-> c4
         	 * 
        * * @throws Exception @@ -257,9 +322,9 @@ } private static void touch(File f, String contents) throws Exception { - FileWriter w = new FileWriter(f); - w.write(contents); - w.close(); + try (FileWriter w = new FileWriter(f)) { + w.write(contents); + } } private String describe(ObjectId c1, boolean longDesc) @@ -271,6 +336,10 @@ return describe(c1, false); } + private String describe(ObjectId c1, String... patterns) throws GitAPIException, IOException, InvalidPatternException { + return git.describe().setTarget(c1).setMatch(patterns).call(); + } + private static void assertNameStartsWith(ObjectId c4, String prefix) { assertTrue(c4.name(), c4.name().startsWith(prefix)); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,32 +70,33 @@ File folder = new File(db.getWorkTree(), "folder"); folder.mkdir(); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - - OutputStream out = new ByteArrayOutputStream(); - List entries = git.diff().setOutputStream(out).call(); - assertEquals(1, entries.size()); - assertEquals(ChangeType.MODIFY, entries.get(0) - .getChangeType()); - assertEquals("folder/folder.txt", entries.get(0) - .getOldPath()); - assertEquals("folder/folder.txt", entries.get(0) - .getNewPath()); - - String actual = out.toString(); - String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "index 0119635..95c4c65 100644\n" - + "--- a/folder/folder.txt\n" - + "+++ b/folder/folder.txt\n" - + "@@ -1 +1 @@\n" - + "-folder\n" - + "\\ No newline at end of file\n" - + "+folder change\n" - + "\\ No newline at end of file\n"; - assertEquals(expected.toString(), actual); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + + OutputStream out = new ByteArrayOutputStream(); + List entries = git.diff().setOutputStream(out).call(); + assertEquals(1, entries.size()); + assertEquals(ChangeType.MODIFY, entries.get(0) + .getChangeType()); + assertEquals("folder/folder.txt", entries.get(0) + .getOldPath()); + assertEquals("folder/folder.txt", entries.get(0) + .getNewPath()); + + String actual = out.toString(); + String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "index 0119635..95c4c65 100644\n" + + "--- a/folder/folder.txt\n" + + "+++ b/folder/folder.txt\n" + + "@@ -1 +1 @@\n" + + "-folder\n" + + "\\ No newline at end of file\n" + + "+folder change\n" + + "\\ No newline at end of file\n"; + assertEquals(expected, actual); + } } @Test @@ -103,33 +104,34 @@ write(new File(db.getWorkTree(), "test.txt"), "test"); File folder = new File(db.getWorkTree(), "folder"); folder.mkdir(); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder"); - git.add().addFilepattern(".").call(); - - OutputStream out = new ByteArrayOutputStream(); - List entries = git.diff().setOutputStream(out) - .setCached(true).call(); - assertEquals(1, entries.size()); - assertEquals(ChangeType.ADD, entries.get(0) - .getChangeType()); - assertEquals("/dev/null", entries.get(0) - .getOldPath()); - assertEquals("folder/folder.txt", entries.get(0) - .getNewPath()); - - String actual = out.toString(); - String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "new file mode 100644\n" - + "index 0000000..0119635\n" - + "--- /dev/null\n" - + "+++ b/folder/folder.txt\n" - + "@@ -0,0 +1 @@\n" - + "+folder\n" - + "\\ No newline at end of file\n"; - assertEquals(expected.toString(), actual); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder"); + git.add().addFilepattern(".").call(); + + OutputStream out = new ByteArrayOutputStream(); + List entries = git.diff().setOutputStream(out) + .setCached(true).call(); + assertEquals(1, entries.size()); + assertEquals(ChangeType.ADD, entries.get(0) + .getChangeType()); + assertEquals("/dev/null", entries.get(0) + .getOldPath()); + assertEquals("folder/folder.txt", entries.get(0) + .getNewPath()); + + String actual = out.toString(); + String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "new file mode 100644\n" + + "index 0000000..0119635\n" + + "--- /dev/null\n" + + "+++ b/folder/folder.txt\n" + + "@@ -0,0 +1 @@\n" + + "+folder\n" + + "\\ No newline at end of file\n"; + assertEquals(expected, actual); + } } @Test @@ -138,107 +140,109 @@ File folder = new File(db.getWorkTree(), "folder"); folder.mkdir(); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("second commit").call(); - write(new File(folder, "folder.txt"), "second folder change"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("third commit").call(); - - // bad filter - DiffCommand diff = git.diff().setShowNameAndStatusOnly(true) - .setPathFilter(PathFilter.create("test.txt")) - .setOldTree(getTreeIterator("HEAD^^")) - .setNewTree(getTreeIterator("HEAD^")); - List entries = diff.call(); - assertEquals(0, entries.size()); - - // no filter, two commits - OutputStream out = new ByteArrayOutputStream(); - diff = git.diff().setOutputStream(out) - .setOldTree(getTreeIterator("HEAD^^")) - .setNewTree(getTreeIterator("HEAD^")); - entries = diff.call(); - assertEquals(1, entries.size()); - assertEquals(ChangeType.MODIFY, entries.get(0).getChangeType()); - assertEquals("folder/folder.txt", entries.get(0).getOldPath()); - assertEquals("folder/folder.txt", entries.get(0).getNewPath()); - - String actual = out.toString(); - String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "index 0119635..95c4c65 100644\n" - + "--- a/folder/folder.txt\n" - + "+++ b/folder/folder.txt\n" - + "@@ -1 +1 @@\n" - + "-folder\n" - + "\\ No newline at end of file\n" - + "+folder change\n" - + "\\ No newline at end of file\n"; - assertEquals(expected.toString(), actual); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("second commit").call(); + write(new File(folder, "folder.txt"), "second folder change"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("third commit").call(); + + // bad filter + DiffCommand diff = git.diff().setShowNameAndStatusOnly(true) + .setPathFilter(PathFilter.create("test.txt")) + .setOldTree(getTreeIterator("HEAD^^")) + .setNewTree(getTreeIterator("HEAD^")); + List entries = diff.call(); + assertEquals(0, entries.size()); + + // no filter, two commits + OutputStream out = new ByteArrayOutputStream(); + diff = git.diff().setOutputStream(out) + .setOldTree(getTreeIterator("HEAD^^")) + .setNewTree(getTreeIterator("HEAD^")); + entries = diff.call(); + assertEquals(1, entries.size()); + assertEquals(ChangeType.MODIFY, entries.get(0).getChangeType()); + assertEquals("folder/folder.txt", entries.get(0).getOldPath()); + assertEquals("folder/folder.txt", entries.get(0).getNewPath()); + + String actual = out.toString(); + String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "index 0119635..95c4c65 100644\n" + + "--- a/folder/folder.txt\n" + + "+++ b/folder/folder.txt\n" + + "@@ -1 +1 @@\n" + + "-folder\n" + + "\\ No newline at end of file\n" + + "+folder change\n" + + "\\ No newline at end of file\n"; + assertEquals(expected, actual); + } } @Test public void testDiffWithPrefixes() throws Exception { write(new File(db.getWorkTree(), "test.txt"), "test"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(db.getWorkTree(), "test.txt"), "test change"); - - OutputStream out = new ByteArrayOutputStream(); - git.diff().setOutputStream(out).setSourcePrefix("old/") - .setDestinationPrefix("new/") - .call(); - - String actual = out.toString(); - String expected = "diff --git old/test.txt new/test.txt\n" - + "index 30d74d2..4dba797 100644\n" + "--- old/test.txt\n" - + "+++ new/test.txt\n" + "@@ -1 +1 @@\n" + "-test\n" - + "\\ No newline at end of file\n" + "+test change\n" - + "\\ No newline at end of file\n"; - assertEquals(expected.toString(), actual); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(db.getWorkTree(), "test.txt"), "test change"); + + OutputStream out = new ByteArrayOutputStream(); + git.diff().setOutputStream(out).setSourcePrefix("old/") + .setDestinationPrefix("new/").call(); + + String actual = out.toString(); + String expected = "diff --git old/test.txt new/test.txt\n" + + "index 30d74d2..4dba797 100644\n" + "--- old/test.txt\n" + + "+++ new/test.txt\n" + "@@ -1 +1 @@\n" + "-test\n" + + "\\ No newline at end of file\n" + "+test change\n" + + "\\ No newline at end of file\n"; + assertEquals(expected, actual); + } } @Test public void testDiffWithNegativeLineCount() throws Exception { write(new File(db.getWorkTree(), "test.txt"), "0\n1\n2\n3\n4\n5\n6\n7\n8\n9"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(db.getWorkTree(), "test.txt"), - "0\n1\n2\n3\n4a\n5\n6\n7\n8\n9"); - - OutputStream out = new ByteArrayOutputStream(); - git.diff().setOutputStream(out).setContextLines(1) - .call(); - - String actual = out.toString(); - String expected = "diff --git a/test.txt b/test.txt\n" - + "index f55b5c9..c5ec8fd 100644\n" + "--- a/test.txt\n" - + "+++ b/test.txt\n" + "@@ -4,3 +4,3 @@\n" + " 3\n" + "-4\n" - + "+4a\n" + " 5\n"; - assertEquals(expected.toString(), actual); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(db.getWorkTree(), "test.txt"), + "0\n1\n2\n3\n4a\n5\n6\n7\n8\n9"); + + OutputStream out = new ByteArrayOutputStream(); + git.diff().setOutputStream(out).setContextLines(1).call(); + + String actual = out.toString(); + String expected = "diff --git a/test.txt b/test.txt\n" + + "index f55b5c9..c5ec8fd 100644\n" + "--- a/test.txt\n" + + "+++ b/test.txt\n" + "@@ -4,3 +4,3 @@\n" + " 3\n" + "-4\n" + + "+4a\n" + " 5\n"; + assertEquals(expected, actual); + } } @Test public void testNoOutputStreamSet() throws Exception { File file = writeTrashFile("test.txt", "a"); assertTrue(file.setLastModified(file.lastModified() - 5000)); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - write(file, "b"); - - List diffs = git.diff().call(); - assertNotNull(diffs); - assertEquals(1, diffs.size()); - DiffEntry diff = diffs.get(0); - assertEquals(ChangeType.MODIFY, diff.getChangeType()); - assertEquals("test.txt", diff.getOldPath()); - assertEquals("test.txt", diff.getNewPath()); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + write(file, "b"); + + List diffs = git.diff().call(); + assertNotNull(diffs); + assertEquals(1, diffs.size()); + DiffEntry diff = diffs.get(0); + assertEquals(ChangeType.MODIFY, diff.getChangeType()); + assertEquals("test.txt", diff.getOldPath()); + assertEquals("test.txt", diff.getNewPath()); + } } private AbstractTreeIterator getTreeIterator(String name) @@ -247,8 +251,9 @@ if (id == null) throw new IllegalArgumentException(name); final CanonicalTreeParser p = new CanonicalTreeParser(); - try (ObjectReader or = db.newObjectReader()) { - p.reset(or, new RevWalk(db).parseTree(id)); + try (ObjectReader or = db.newObjectReader(); + RevWalk rw = new RevWalk(db)) { + p.reset(or, rw.parseTree(id)); return p; } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.api.ResetCommand.ResetType; +import org.eclipse.jgit.api.errors.CheckoutConflictException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.RevisionSyntaxException; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; +import org.eclipse.jgit.lib.CoreConfig.EOL; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.theories.DataPoint; +import org.junit.experimental.theories.Theories; +import org.junit.runner.RunWith; + +/** + * Unit tests for end-of-line conversion and settings using core.autocrlf, * + * core.eol and the .gitattributes eol, text, binary (macro for -diff -merge + * -text) + */ +@RunWith(Theories.class) +public class EolRepositoryTest extends RepositoryTestCase { + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + @DataPoint + public static boolean doSmudgeEntries = true; + + @DataPoint + public static boolean dontSmudgeEntries = false; + + private boolean smudge; + + @DataPoint + public static String smallContents[] = { + generateTestData(3, 1, true, false), + generateTestData(3, 1, false, true), + generateTestData(3, 1, true, true) }; + + @DataPoint + public static String hugeContents[] = { + generateTestData(1000000, 17, true, false), + generateTestData(1000000, 17, false, true), + generateTestData(1000000, 17, true, true) }; + + static String generateTestData(int size, int lineSize, boolean withCRLF, + boolean withLF) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < size; i++) { + if (i > 0 && i % lineSize == 0) { + // newline + if (withCRLF && withLF) { + // mixed + if (i % 2 == 0) + sb.append("\r\n"); + else + sb.append("\n"); + } else if (withCRLF) { + sb.append("\r\n"); + } else if (withLF) { + sb.append("\n"); + } + } + sb.append("A"); + } + return sb.toString(); + } + + public EolRepositoryTest(String[] testContent, boolean smudgeEntries) { + CONTENT_CRLF = testContent[0]; + CONTENT_LF = testContent[1]; + CONTENT_MIXED = testContent[2]; + this.smudge = smudgeEntries; + } + + protected String CONTENT_CRLF; + + protected String CONTENT_LF; + + protected String CONTENT_MIXED; + + private TreeWalk walk; + + /** work tree root .gitattributes */ + private File dotGitattributes; + + /** file containing CRLF */ + private File fileCRLF; + + /** file containing LF */ + private File fileLF; + + /** file containing mixed CRLF and LF */ + private File fileMixed; + + /** this values are set in {@link #collectRepositoryState()} */ + private static class ActualEntry { + private String attrs; + + private String file; + + private String index; + + private int indexContentLength; + } + + private ActualEntry entryCRLF = new ActualEntry(); + + private ActualEntry entryLF = new ActualEntry(); + + private ActualEntry entryMixed = new ActualEntry(); + + private DirCache dirCache; + + @Test + public void testDefaultSetup() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(null, null, null, null, "* text=auto"); + collectRepositoryState(); + assertEquals("text=auto", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + public void checkEntryContent(ActualEntry entry, String fileContent, + String indexContent) { + assertEquals(fileContent, entry.file); + assertEquals(indexContent, entry.index); + if (entry.indexContentLength != 0) { + assertEquals(fileContent.length(), entry.indexContentLength); + } + } + + @Test + public void test_ConfigAutoCRLF_false() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.FALSE, null, null, null, "* text=auto"); + collectRepositoryState(); + assertEquals("text=auto", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_true() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.TRUE, null, null, null, "* text=auto"); + collectRepositoryState(); + assertEquals("text=auto", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_input() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.INPUT, null, null, null, "* text=auto"); + collectRepositoryState(); + assertEquals("text=auto", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigEOL_lf() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(null, EOL.LF, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigEOL_crlf() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(null, EOL.CRLF, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigEOL_native_windows() throws Exception { + String origLineSeparator = System.getProperty("line.separator", "\n"); + System.setProperty("line.separator", "\r\n"); + try { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + } finally { + System.setProperty("line.separator", origLineSeparator); + } + } + + @Test + public void test_ConfigEOL_native_xnix() throws Exception { + String origLineSeparator = System.getProperty("line.separator", "\n"); + System.setProperty("line.separator", "\n"); + try { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + } finally { + System.setProperty("line.separator", origLineSeparator); + } + } + + @Test + public void test_ConfigAutoCRLF_false_ConfigEOL_lf() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_false_ConfigEOL_native() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.NATIVE, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_true_ConfigEOL_lf() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_switchToBranchWithTextAttributes() + throws Exception { + Git git = Git.wrap(db); + + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.CRLF, null, null, + "file1.txt text\nfile2.txt text\nfile3.txt text"); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + + // switch to binary for file1 + dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES, + "file1.txt binary\nfile2.txt text\nfile3.txt text"); + gitCommit(git, "switchedToBinaryFor1"); + recreateWorktree(git); + collectRepositoryState(); + assertEquals("binary -diff -merge -text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + assertEquals("text", entryLF.attrs); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + assertEquals("text", entryMixed.attrs); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + + // checkout the commit which has text for file1 + gitCheckout(git, "HEAD^"); + recreateWorktree(git); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_switchToBranchWithBinaryAttributes() throws Exception { + Git git = Git.wrap(db); + + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, null, null, + "file1.txt binary\nfile2.txt binary\nfile3.txt binary"); + collectRepositoryState(); + assertEquals("binary -diff -merge -text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED); + + // switch to text for file1 + dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES, + "file1.txt text\nfile2.txt binary\nfile3.txt binary"); + gitCommit(git, "switchedToTextFor1"); + recreateWorktree(git); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + assertEquals("binary -diff -merge -text", entryLF.attrs); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + assertEquals("binary -diff -merge -text", entryMixed.attrs); + checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED); + + // checkout the commit which has text for file1 + gitCheckout(git, "HEAD^"); + recreateWorktree(git); + collectRepositoryState(); + assertEquals("binary -diff -merge -text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED); + } + + @Test + public void test_ConfigAutoCRLF_input_ConfigEOL_lf() throws Exception { + // for EOL to work, the text attribute must be set + setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt text", null, null); + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_true_GlobalEOL_lf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=lf", null, null); + collectRepositoryState(); + assertEquals("eol=lf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_false_GlobalEOL_lf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=lf", null, null); + collectRepositoryState(); + assertEquals("eol=lf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_input_GlobalEOL_lf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=lf", null, null); + collectRepositoryState(); + assertEquals("eol=lf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_true_GlobalEOL_crlf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=crlf", null, null); + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_false_GlobalEOL_crlf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=crlf", null, null); + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_input_GlobalEOL_crlf() throws Exception { + setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=crlf", null, null); + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_true_GlobalEOL_lf_InfoEOL_crlf() + throws Exception { + setupGitAndDoHardReset(AutoCRLF.TRUE, null, "*.txt eol=lf", "*.txt eol=crlf", null); + // info decides + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_ConfigAutoCRLF_false_GlobalEOL_crlf_InfoEOL_lf() + throws Exception { + setupGitAndDoHardReset(AutoCRLF.FALSE, null, "*.txt eol=crlf", "*.txt eol=lf", null); + // info decides + collectRepositoryState(); + assertEquals("eol=lf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void test_GlobalEOL_lf_RootEOL_crlf() throws Exception { + setupGitAndDoHardReset(null, null, "*.txt eol=lf", null, "*.txt eol=crlf"); + // root over global + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_lf() throws Exception { + setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf", "*.txt eol=lf"); + // info overrides all + collectRepositoryState(); + assertEquals("eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_unspec() + throws Exception { + setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf", + "*.txt text !eol"); + // info overrides all + collectRepositoryState(); + assertEquals("eol=crlf text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF); + } + + @Test + public void test_GlobalEOL_lf_InfoEOL_unspec_RootEOL_crlf() + throws Exception { + setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt !eol", + "*.txt text eol=crlf"); + // info overrides all + collectRepositoryState(); + assertEquals("text", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF); + } + + @Test + public void testBinary1() throws Exception { + setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text", "*.txt binary", + "*.txt eol=crlf"); + // info overrides all + collectRepositoryState(); + assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED); + } + + @Test + public void testBinary2() throws Exception { + setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text eol=crlf", null, + "*.txt binary"); + // root over global + collectRepositoryState(); + assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs); + checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF); + checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF); + checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED); + } + + // create new repo with + // global .gitattributes + // info .git/config/info/.gitattributes + // workdir root .gitattributes + // text file lf.txt CONTENT_LF + // text file crlf.txt CONTENT_CRLF + // + // commit files (checkin) + // delete working dir files + // reset hard (checkout) + private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol, + String globalAttributesContent, String infoAttributesContent, + String workDirRootAttributesContent) throws Exception { + Git git = new Git(db); + FileBasedConfig config = db.getConfig(); + if (autoCRLF != null) { + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, autoCRLF); + } + if (eol != null) { + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_EOL, eol); + } + if (globalAttributesContent != null) { + File f = new File(db.getDirectory(), "global/attrs"); + write(f, globalAttributesContent); + config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE, + f.getAbsolutePath()); + + } + if (infoAttributesContent != null) { + File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES); + write(f, infoAttributesContent); + } + config.save(); + + if (workDirRootAttributesContent != null) { + dotGitattributes = createAndAddFile(git, + Constants.DOT_GIT_ATTRIBUTES, workDirRootAttributesContent); + } else { + dotGitattributes = null; + } + + fileCRLF = createAndAddFile(git, "file1.txt", "a"); + + fileLF = createAndAddFile(git, "file2.txt", "a"); + + fileMixed = createAndAddFile(git, "file3.txt", "a"); + + RevCommit c = gitCommit(git, "create files"); + + fileCRLF = createAndAddFile(git, "file1.txt", CONTENT_CRLF); + + fileLF = createAndAddFile(git, "file2.txt", CONTENT_LF); + + fileMixed = createAndAddFile(git, "file3.txt", CONTENT_MIXED); + + gitCommit(git, "addFiles"); + + recreateWorktree(git); + + if (smudge) { + DirCache dc = DirCache.lock(git.getRepository().getIndexFile(), + FS.detect()); + DirCacheEditor editor = dc.editor(); + for (int i = 0; i < dc.getEntryCount(); i++) { + editor.add(new DirCacheEditor.PathEdit( + dc.getEntry(i).getPathString()) { + @Override + public void apply(DirCacheEntry ent) { + ent.smudgeRacilyClean(); + } + }); + } + editor.commit(); + } + + // @TODO: find out why the following assertion would break the tests + // assertTrue(git.status().call().isClean()); + git.checkout().setName(c.getName()).call(); + git.checkout().setName("master").call(); + } + + private void recreateWorktree(Git git) + throws GitAPIException, CheckoutConflictException, + InterruptedException, IOException, NoFilepatternException { + // re-create file from the repo + for (File f : new File[] { dotGitattributes, fileCRLF, fileLF, fileMixed }) { + if (f == null) + continue; + f.delete(); + Assert.assertFalse(f.exists()); + } + gitResetHard(git); + fsTick(db.getIndexFile()); + gitAdd(git, "."); + } + + protected RevCommit gitCommit(Git git, String msg) throws GitAPIException { + return git.commit().setMessage(msg).call(); + } + + protected void gitAdd(Git git, String path) throws GitAPIException { + git.add().addFilepattern(path).call(); + } + + protected void gitResetHard(Git git) throws GitAPIException { + git.reset().setMode(ResetType.HARD).call(); + } + + protected void gitCheckout(Git git, String revstr) + throws GitAPIException, RevisionSyntaxException, IOException { + git.checkout().setName(db.resolve(revstr).getName()).call(); + } + + // create a file and add it to the repo + private File createAndAddFile(Git git, String path, String content) + throws Exception { + File f; + int pos = path.lastIndexOf('/'); + if (pos < 0) { + f = writeTrashFile(path, content); + } else { + f = writeTrashFile(path.substring(0, pos), path.substring(pos + 1), + content); + } + gitAdd(git, path); + Assert.assertTrue(f.exists()); + return f; + } + + private void collectRepositoryState() throws Exception { + dirCache = db.readDirCache(); + walk = beginWalk(); + if (dotGitattributes != null) + collectEntryContentAndAttributes(F, ".gitattributes", null); + collectEntryContentAndAttributes(F, fileCRLF.getName(), entryCRLF); + collectEntryContentAndAttributes(F, fileLF.getName(), entryLF); + collectEntryContentAndAttributes(F, fileMixed.getName(), entryMixed); + endWalk(); + } + + private TreeWalk beginWalk() throws Exception { + TreeWalk newWalk = new TreeWalk(db); + newWalk.addTree(new FileTreeIterator(db)); + newWalk.addTree(new DirCacheIterator(db.readDirCache())); + return newWalk; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + } + + private void collectEntryContentAndAttributes(FileMode type, String pathName, + ActualEntry e) throws IOException { + assertTrue("walk has entry", walk.next()); + + assertEquals(pathName, walk.getPathString()); + assertEquals(type, walk.getFileMode(0)); + + if (e != null) { + e.attrs = ""; + for (Attribute a : walk.getAttributes().getAll()) { + e.attrs += " " + a.toString(); + } + e.attrs = e.attrs.trim(); + e.file = new String( + IO.readFully(new File(db.getWorkTree(), pathName))); + DirCacheEntry dce = dirCache.getEntry(pathName); + ObjectLoader open = walk.getObjectReader().open(dce.getObjectId()); + e.index = new String(open.getBytes()); + e.indexContentLength = dce.getLength(); + } + + if (D.equals(type)) + walk.enterSubtree(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.api; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.AUTO_CRLF; +import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.AUTO_LF; +import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.DIRECT; +import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.TEXT_CRLF; +import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.TEXT_LF; +import static org.junit.Assert.assertArrayEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; +import org.junit.Test; + +/** + * Unit tests for end-of-line conversion streams + */ +public class EolStreamTypeUtilTest { + + @Test + public void testCheckoutDirect() throws Exception { + testCheckout(DIRECT, DIRECT, "", ""); + testCheckout(DIRECT, DIRECT, "\r", "\r"); + testCheckout(DIRECT, DIRECT, "\n", "\n"); + + testCheckout(DIRECT, DIRECT, "\r\n", "\r\n"); + testCheckout(DIRECT, DIRECT, "\n\r", "\n\r"); + + testCheckout(DIRECT, DIRECT, "\n\r\n", "\n\r\n"); + testCheckout(DIRECT, DIRECT, "\r\n\r", "\r\n\r"); + + testCheckout(DIRECT, DIRECT, "a\nb\n", "a\nb\n"); + testCheckout(DIRECT, DIRECT, "a\rb\r", "a\rb\r"); + testCheckout(DIRECT, DIRECT, "a\n\rb\n\r", "a\n\rb\n\r"); + testCheckout(DIRECT, DIRECT, "a\r\nb\r\n", "a\r\nb\r\n"); + } + + @Test + public void testCheckoutLF() throws Exception { + testCheckout(TEXT_LF, AUTO_LF, "", ""); + testCheckout(TEXT_LF, AUTO_LF, "\r", "\r"); + testCheckout(TEXT_LF, AUTO_LF, "\n", "\n"); + + testCheckout(TEXT_LF, AUTO_LF, "\r\n", "\n"); + testCheckout(TEXT_LF, AUTO_LF, "\n\r", "\n\r"); + + testCheckout(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n"); + testCheckout(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r"); + + testCheckout(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n"); + testCheckout(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r"); + testCheckout(TEXT_LF, AUTO_LF, "a\n\rb\n\r", "a\n\rb\n\r"); + testCheckout(TEXT_LF, AUTO_LF, "a\r\nb\r\n", "a\nb\n"); + } + + @Test + public void testCheckoutCRLF() throws Exception { + testCheckout(TEXT_CRLF, AUTO_CRLF, "", ""); + testCheckout(TEXT_CRLF, AUTO_CRLF, "\r", "\r"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n"); + + testCheckout(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r"); + + testCheckout(TEXT_CRLF, AUTO_CRLF, "\n\r\n", "\r\n\r\n"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "\r\n\r", "\r\n\r"); + + testCheckout(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckout(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n"); + } + + /** + * Test stream type detection based on stream content. + *

        + * Tests three things with the output text: + *

        + * 1) conversion if output was declared as text + *

        + * 2) conversion if output was declared as potentially text (AUTO_...) and + * is in fact text + *

        + * 3) conversion if modified output (now with binary characters) was + * declared as potentially text but now contains binary characters + *

        + * + * @param streamTypeText + * is the enum meaning that the output is definitely text (no + * binary check at all) + * @param streamTypeWithBinaryCheck + * is the enum meaning that the output may be text (binary check + * is done) + * @param output + * is a text output without binary characters + * @param expectedConversion + * is the expected converted output without binary characters + * @throws Exception + */ + private void testCheckout(EolStreamType streamTypeText, + EolStreamType streamTypeWithBinaryCheck, String output, + String expectedConversion) throws Exception { + ByteArrayOutputStream b; + byte[] outputBytes = output.getBytes(UTF_8); + byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8); + + // test using output text and assuming it was declared TEXT + b = new ByteArrayOutputStream(); + try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b, + streamTypeText)) { + out.write(outputBytes); + } + assertArrayEquals(expectedConversionBytes, b.toByteArray()); + + // test using ouput text and assuming it was declared AUTO, using binary + // detection + b = new ByteArrayOutputStream(); + try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b, + streamTypeWithBinaryCheck)) { + out.write(outputBytes); + } + assertArrayEquals(expectedConversionBytes, b.toByteArray()); + + // now pollute output text with some binary bytes + outputBytes = extendWithBinaryData(outputBytes); + expectedConversionBytes = extendWithBinaryData(expectedConversionBytes); + + // again, test using output text and assuming it was declared TEXT + b = new ByteArrayOutputStream(); + try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b, + streamTypeText)) { + out.write(outputBytes); + } + assertArrayEquals(expectedConversionBytes, b.toByteArray()); + + // again, test using ouput text and assuming it was declared AUTO, using + // binary + // detection + b = new ByteArrayOutputStream(); + try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b, + streamTypeWithBinaryCheck)) { + out.write(outputBytes); + } + // expect no conversion + assertArrayEquals(outputBytes, b.toByteArray()); + } + + @Test + public void testCheckinDirect() throws Exception { + testCheckin(DIRECT, DIRECT, "", ""); + testCheckin(DIRECT, DIRECT, "\r", "\r"); + testCheckin(DIRECT, DIRECT, "\n", "\n"); + + testCheckin(DIRECT, DIRECT, "\r\n", "\r\n"); + testCheckin(DIRECT, DIRECT, "\n\r", "\n\r"); + + testCheckin(DIRECT, DIRECT, "\n\r\n", "\n\r\n"); + testCheckin(DIRECT, DIRECT, "\r\n\r", "\r\n\r"); + + testCheckin(DIRECT, DIRECT, "a\nb\n", "a\nb\n"); + testCheckin(DIRECT, DIRECT, "a\rb\r", "a\rb\r"); + testCheckin(DIRECT, DIRECT, "a\n\rb\n\r", "a\n\rb\n\r"); + testCheckin(DIRECT, DIRECT, "a\r\nb\r\n", "a\r\nb\r\n"); + } + + @Test + public void testCheckinLF() throws Exception { + testCheckin(TEXT_LF, AUTO_LF, "", ""); + testCheckin(TEXT_LF, AUTO_LF, "\r", "\r"); + testCheckin(TEXT_LF, AUTO_LF, "\n", "\n"); + + testCheckin(TEXT_LF, AUTO_LF, "\r\n", "\n"); + testCheckin(TEXT_LF, AUTO_LF, "\n\r", "\n\r"); + + testCheckin(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n"); + testCheckin(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r"); + + testCheckin(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n"); + testCheckin(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r"); + testCheckin(TEXT_LF, AUTO_LF, "a\n\rb\n\r", "a\n\rb\n\r"); + testCheckin(TEXT_LF, AUTO_LF, "a\r\nb\r\n", "a\nb\n"); + } + + @Test + public void testCheckinCRLF() throws Exception { + testCheckin(TEXT_CRLF, AUTO_CRLF, "", ""); + testCheckin(TEXT_CRLF, AUTO_CRLF, "\r", "\r"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n"); + + testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r"); + + testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r\n", "\r\n\r\n"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n\r", "\r\n\r"); + + testCheckin(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckin(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n"); + } + + /** + * Test stream type detection based on stream content. + *

        + * Tests three things with the input text: + *

        + * 1) conversion if input was declared as text + *

        + * 2) conversion if input was declared as potentially text (AUTO_...) and is + * in fact text + *

        + * 3) conversion if modified input (now with binary characters) was declared + * as potentially text but now contains binary characters + *

        + * + * @param streamTypeText + * is the enum meaning that the input is definitely text (no + * binary check at all) + * @param streamTypeWithBinaryCheck + * is the enum meaning that the input may be text (binary check + * is done) + * @param input + * is a text input without binary characters + * @param expectedConversion + * is the expected converted input without binary characters + * @throws Exception + */ + private void testCheckin(EolStreamType streamTypeText, + EolStreamType streamTypeWithBinaryCheck, String input, + String expectedConversion) throws Exception { + byte[] inputBytes = input.getBytes(UTF_8); + byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8); + + // test using input text and assuming it was declared TEXT + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), + streamTypeText)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + } + + // test using input text and assuming it was declared AUTO, using binary + // detection + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), + streamTypeWithBinaryCheck)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + } + + // now pollute input text with some binary bytes + inputBytes = extendWithBinaryData(inputBytes); + expectedConversionBytes = extendWithBinaryData(expectedConversionBytes); + + // again, test using input text and assuming it was declared TEXT + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), streamTypeText)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + } + + // again, test using input text and assuming it was declared AUTO, using + // binary + // detection + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), + streamTypeWithBinaryCheck)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + // expect no conversion + assertArrayEquals(inputBytes, Arrays.copyOf(b, len)); + } + } + + private byte[] extendWithBinaryData(byte[] data) throws Exception { + int n = 3; + byte[] dataEx = new byte[data.length + n]; + System.arraycopy(data, 0, dataEx, 0, data.length); + for (int i = 0; i < n; i++) { + dataEx[data.length + i] = (byte) i; + } + return dataEx; + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2017 David Pursehouse + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.eclipse.jgit.api.ResetCommand.ResetType; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.submodule.SubmoduleStatus; +import org.eclipse.jgit.submodule.SubmoduleStatusType; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.junit.Before; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +@RunWith(Theories.class) +public class FetchAndPullCommandsRecurseSubmodulesTest extends RepositoryTestCase { + @DataPoints + public static boolean[] useFetch = { true, false }; + + private Git git; + + private Git git2; + + private Git sub1Git; + + private Git sub2Git; + + private RevCommit commit1; + + private RevCommit commit2; + + private ObjectId submodule1Head; + + private ObjectId submodule2Head; + + private final RefSpec REFSPEC = new RefSpec("refs/heads/master"); + + private final String REMOTE = "origin"; + + private final String PATH = "sub"; + + @Before + public void setUpSubmodules() throws Exception { + git = new Git(db); + + // Create submodule 1 + File submodule1 = createTempDirectory( + "testCloneRepositoryWithNestedSubmodules1"); + sub1Git = Git.init().setDirectory(submodule1).call(); + assertNotNull(sub1Git); + Repository sub1 = sub1Git.getRepository(); + assertNotNull(sub1); + addRepoToClose(sub1); + + String file = "file.txt"; + + write(new File(sub1.getWorkTree(), file), "content"); + sub1Git.add().addFilepattern(file).call(); + RevCommit commit = sub1Git.commit().setMessage("create file").call(); + assertNotNull(commit); + + // Create submodule 2 + File submodule2 = createTempDirectory( + "testCloneRepositoryWithNestedSubmodules2"); + sub2Git = Git.init().setDirectory(submodule2).call(); + assertNotNull(sub2Git); + Repository sub2 = sub2Git.getRepository(); + assertNotNull(sub2); + addRepoToClose(sub2); + + write(new File(sub2.getWorkTree(), file), "content"); + sub2Git.add().addFilepattern(file).call(); + RevCommit sub2Head = sub2Git.commit().setMessage("create file").call(); + assertNotNull(sub2Head); + + // Add submodule 2 to submodule 1 + Repository r2 = sub1Git.submoduleAdd().setPath(PATH) + .setURI(sub2.getDirectory().toURI().toString()).call(); + assertNotNull(r2); + addRepoToClose(r2); + RevCommit sub1Head = sub1Git.commit().setAll(true) + .setMessage("Adding submodule").call(); + assertNotNull(sub1Head); + + // Add submodule 1 to default repository + Repository r1 = git.submoduleAdd().setPath(PATH) + .setURI(sub1.getDirectory().toURI().toString()).call(); + assertNotNull(r1); + addRepoToClose(r1); + assertNotNull(git.commit().setAll(true).setMessage("Adding submodule") + .call()); + + // Clone default repository and include submodules + File directory = createTempDirectory( + "testCloneRepositoryWithNestedSubmodules"); + CloneCommand clone = Git.cloneRepository(); + clone.setDirectory(directory); + clone.setCloneSubmodules(true); + clone.setURI(git.getRepository().getDirectory().toURI().toString()); + git2 = clone.call(); + addRepoToClose(git2.getRepository()); + assertNotNull(git2); + + // Record current FETCH_HEAD of submodules + try (SubmoduleWalk walk = SubmoduleWalk + .forIndex(git2.getRepository())) { + assertTrue(walk.next()); + Repository r = walk.getRepository(); + submodule1Head = r.resolve(Constants.FETCH_HEAD); + + try (SubmoduleWalk walk2 = SubmoduleWalk.forIndex(r)) { + assertTrue(walk2.next()); + submodule2Head = walk2.getRepository() + .resolve(Constants.FETCH_HEAD); + } + } + + // Commit in submodule 1 + JGitTestUtil.writeTrashFile(r1, "f1.txt", "test"); + sub1Git.add().addFilepattern("f1.txt").call(); + commit1 = sub1Git.commit().setMessage("new commit").call(); + + // Commit in submodule 2 + JGitTestUtil.writeTrashFile(r2, "f2.txt", "test"); + sub2Git.add().addFilepattern("f2.txt").call(); + commit2 = sub2Git.commit().setMessage("new commit").call(); + } + + @Theory + public void shouldNotFetchSubmodulesWhenNo(boolean fetch) throws Exception { + FetchResult result = execute(FetchRecurseSubmodulesMode.NO, fetch); + assertTrue(result.submoduleResults().isEmpty()); + assertSubmoduleFetchHeads(submodule1Head, submodule2Head); + } + + @Theory + public void shouldFetchSubmodulesWhenYes(boolean fetch) throws Exception { + FetchResult result = execute(FetchRecurseSubmodulesMode.YES, fetch); + assertTrue(result.submoduleResults().containsKey("sub")); + FetchResult subResult = result.submoduleResults().get("sub"); + assertTrue(subResult.submoduleResults().containsKey("sub")); + assertSubmoduleFetchHeads(commit1, commit2); + } + + @Theory + public void shouldFetchSubmodulesWhenOnDemandAndRevisionChanged( + boolean fetch) throws Exception { + RevCommit update = updateSubmoduleRevision(); + FetchResult result = execute(FetchRecurseSubmodulesMode.ON_DEMAND, + fetch); + + // The first submodule should have been updated + assertTrue(result.submoduleResults().containsKey("sub")); + FetchResult subResult = result.submoduleResults().get("sub"); + + // The second submodule should not get updated + assertTrue(subResult.submoduleResults().isEmpty()); + assertSubmoduleFetchHeads(commit1, submodule2Head); + + // After fetch the parent repo's fetch head should be the commit + // that updated the submodule. + assertEquals(update, + git2.getRepository().resolve(Constants.FETCH_HEAD)); + } + + @Theory + public void shouldNotFetchSubmodulesWhenOnDemandAndRevisionNotChanged( + boolean fetch) throws Exception { + FetchResult result = execute(FetchRecurseSubmodulesMode.ON_DEMAND, + fetch); + assertTrue(result.submoduleResults().isEmpty()); + assertSubmoduleFetchHeads(submodule1Head, submodule2Head); + } + + @Theory + public void shouldNotFetchSubmodulesWhenSubmoduleConfigurationSetToNo( + boolean fetch) throws Exception { + StoredConfig config = git2.getRepository().getConfig(); + config.setEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION, PATH, + ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, + FetchRecurseSubmodulesMode.NO); + config.save(); + updateSubmoduleRevision(); + FetchResult result = execute(null, fetch); + assertTrue(result.submoduleResults().isEmpty()); + assertSubmoduleFetchHeads(submodule1Head, submodule2Head); + } + + @Theory + public void shouldFetchSubmodulesWhenSubmoduleConfigurationSetToYes( + boolean fetch) throws Exception { + StoredConfig config = git2.getRepository().getConfig(); + config.setEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION, PATH, + ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, + FetchRecurseSubmodulesMode.YES); + config.save(); + FetchResult result = execute(null, fetch); + assertTrue(result.submoduleResults().containsKey("sub")); + FetchResult subResult = result.submoduleResults().get("sub"); + assertTrue(subResult.submoduleResults().containsKey("sub")); + assertSubmoduleFetchHeads(commit1, commit2); + } + + @Theory + public void shouldNotFetchSubmodulesWhenFetchConfigurationSetToNo( + boolean fetch) throws Exception { + StoredConfig config = git2.getRepository().getConfig(); + config.setEnum(ConfigConstants.CONFIG_FETCH_SECTION, null, + ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, + FetchRecurseSubmodulesMode.NO); + config.save(); + updateSubmoduleRevision(); + FetchResult result = execute(null, fetch); + assertTrue(result.submoduleResults().isEmpty()); + assertSubmoduleFetchHeads(submodule1Head, submodule2Head); + } + + @Theory + public void shouldFetchSubmodulesWhenFetchConfigurationSetToYes( + boolean fetch) throws Exception { + StoredConfig config = git2.getRepository().getConfig(); + config.setEnum(ConfigConstants.CONFIG_FETCH_SECTION, null, + ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, + FetchRecurseSubmodulesMode.YES); + config.save(); + FetchResult result = execute(null, fetch); + assertTrue(result.submoduleResults().containsKey("sub")); + FetchResult subResult = result.submoduleResults().get("sub"); + assertTrue(subResult.submoduleResults().containsKey("sub")); + assertSubmoduleFetchHeads(commit1, commit2); + } + + private RevCommit updateSubmoduleRevision() throws Exception { + // Fetch the submodule in the original git and reset it to + // the commit that was created + try (SubmoduleWalk w = SubmoduleWalk.forIndex(git.getRepository())) { + assertTrue(w.next()); + try (Git g = new Git(w.getRepository())) { + g.fetch().setRemote(REMOTE).setRefSpecs(REFSPEC).call(); + g.reset().setMode(ResetType.HARD).setRef(commit1.name()).call(); + } + } + + // Submodule index Id should be same as before, but head Id should be + // updated to the new commit, and status should be "checked out". + SubmoduleStatus subStatus = git.submoduleStatus().call().get("sub"); + assertEquals(submodule1Head, subStatus.getIndexId()); + assertEquals(commit1, subStatus.getHeadId()); + assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, subStatus.getType()); + + // Add and commit the submodule status + git.add().addFilepattern("sub").call(); + RevCommit update = git.commit().setMessage("update sub").call(); + + // Both submodule index and head should now be at the new commit, and + // the status should be "initialized". + subStatus = git.submoduleStatus().call().get("sub"); + assertEquals(commit1, subStatus.getIndexId()); + assertEquals(commit1, subStatus.getHeadId()); + assertEquals(SubmoduleStatusType.INITIALIZED, subStatus.getType()); + + return update; + } + + private FetchResult execute(FetchRecurseSubmodulesMode mode, boolean fetch) + throws Exception { + FetchResult result; + + if (fetch) { + result = git2.fetch().setRemote(REMOTE).setRefSpecs(REFSPEC) + .setRecurseSubmodules(mode).call(); + } else { + // For the purposes of this test we don't need to care about the + // pull result, or the result of pull with merge. We are only + // interested in checking whether or not the submodules were updated + // as expected. Setting to rebase makes it easier to assert about + // the state of the parent repository head, i.e. we know it should + // be at the submodule update commit, and don't need to consider a + // merge commit created by the pull. + result = git2.pull().setRemote(REMOTE).setRebase(true) + .setRecurseSubmodules(mode).call().getFetchResult(); + } + assertNotNull(result); + return result; + } + + private void assertSubmoduleFetchHeads(ObjectId expectedHead1, + ObjectId expectedHead2) throws Exception { + try (SubmoduleWalk walk = SubmoduleWalk + .forIndex(git2.getRepository())) { + assertTrue(walk.next()); + Repository r = walk.getRepository(); + ObjectId newHead1 = r.resolve(Constants.FETCH_HEAD); + ObjectId newHead2; + try (SubmoduleWalk walk2 = SubmoduleWalk.forIndex(r)) { + assertTrue(walk2.next()); + newHead2 = walk2.getRepository().resolve(Constants.FETCH_HEAD); + } + + assertEquals(expectedHead1, newHead1); + assertEquals(expectedHead2, newHead2); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,12 +45,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.io.IOException; -import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -74,7 +72,7 @@ private Git remoteGit; @Before - public void setupRemoteRepository() throws IOException, URISyntaxException { + public void setupRemoteRepository() throws Exception { git = new Git(db); // create other repository @@ -91,16 +89,14 @@ } @Test - public void testFetch() throws JGitInternalException, IOException, - GitAPIException { + public void testFetch() throws Exception { // create some refs via commits and tag RevCommit commit = remoteGit.commit().setMessage("initial commit").call(); Ref tagRef = remoteGit.tag().setName("tag").call(); - RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); - git.fetch().setRemote("test").setRefSpecs(spec) - .call(); + git.fetch().setRemote("test") + .setRefSpecs("refs/heads/master:refs/heads/x").call(); assertEquals(commit.getId(), db.resolve(commit.getId().getName() + "^{commit}")); @@ -109,12 +105,97 @@ } @Test + public void fetchAddsBranches() throws Exception { + final String branch1 = "b1"; + final String branch2 = "b2"; + final String remoteBranch1 = "test/" + branch1; + final String remoteBranch2 = "test/" + branch2; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call(); + remoteGit.commit().setMessage("commit").call(); + Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call(); + + String spec = "refs/heads/*:refs/remotes/test/*"; + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + } + + @Test + public void fetchDoesntDeleteBranches() throws Exception { + final String branch1 = "b1"; + final String branch2 = "b2"; + final String remoteBranch1 = "test/" + branch1; + final String remoteBranch2 = "test/" + branch2; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call(); + remoteGit.commit().setMessage("commit").call(); + Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call(); + + String spec = "refs/heads/*:refs/remotes/test/*"; + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + + remoteGit.branchDelete().setBranchNames(branch1).call(); + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + } + + @Test + public void fetchUpdatesBranches() throws Exception { + final String branch1 = "b1"; + final String branch2 = "b2"; + final String remoteBranch1 = "test/" + branch1; + final String remoteBranch2 = "test/" + branch2; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call(); + remoteGit.commit().setMessage("commit").call(); + Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call(); + + String spec = "refs/heads/*:refs/remotes/test/*"; + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + + remoteGit.commit().setMessage("commit").call(); + branchRef2 = remoteGit.branchCreate().setName(branch2).setForce(true).call(); + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + } + + @Test + public void fetchPrunesBranches() throws Exception { + final String branch1 = "b1"; + final String branch2 = "b2"; + final String remoteBranch1 = "test/" + branch1; + final String remoteBranch2 = "test/" + branch2; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call(); + remoteGit.commit().setMessage("commit").call(); + Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call(); + + String spec = "refs/heads/*:refs/remotes/test/*"; + git.fetch().setRemote("test").setRefSpecs(spec).call(); + assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + + remoteGit.branchDelete().setBranchNames(branch1).call(); + git.fetch().setRemote("test").setRefSpecs(spec) + .setRemoveDeletedRefs(true).call(); + assertNull(db.resolve(remoteBranch1)); + assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2)); + } + + @Test public void fetchShouldAutoFollowTag() throws Exception { remoteGit.commit().setMessage("commit").call(); Ref tagRef = remoteGit.tag().setName("foo").call(); - RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*"); - git.fetch().setRemote("test").setRefSpecs(spec) + git.fetch().setRemote("test") + .setRefSpecs("refs/heads/*:refs/remotes/origin/*") .setTagOpt(TagOpt.AUTO_FOLLOW).call(); assertEquals(tagRef.getObjectId(), db.resolve("foo")); @@ -125,8 +206,8 @@ remoteGit.commit().setMessage("commit").call(); Ref tagRef = remoteGit.tag().setName("foo").call(); remoteGit.commit().setMessage("commit2").call(); - RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*"); - git.fetch().setRemote("test").setRefSpecs(spec) + git.fetch().setRemote("test") + .setRefSpecs("refs/heads/*:refs/remotes/origin/*") .setTagOpt(TagOpt.AUTO_FOLLOW).call(); assertEquals(tagRef.getObjectId(), db.resolve("foo")); } @@ -137,9 +218,8 @@ remoteGit.checkout().setName("other").setCreateBranch(true).call(); remoteGit.commit().setMessage("commit2").call(); remoteGit.tag().setName("foo").call(); - RefSpec spec = new RefSpec( - "refs/heads/master:refs/remotes/origin/master"); - git.fetch().setRemote("test").setRefSpecs(spec) + git.fetch().setRemote("test") + .setRefSpecs("refs/heads/master:refs/remotes/origin/master") .setTagOpt(TagOpt.AUTO_FOLLOW).call(); assertNull(db.resolve("foo")); } @@ -151,7 +231,7 @@ Ref tagRef = remoteGit.tag().setName(tagName).call(); ObjectId originalId = tagRef.getObjectId(); - RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*"); + String spec = "refs/heads/*:refs/remotes/origin/*"; git.fetch().setRemote("test").setRefSpecs(spec) .setTagOpt(TagOpt.AUTO_FOLLOW).call(); assertEquals(originalId, db.resolve(tagName)); @@ -177,7 +257,7 @@ remoteGit.commit().setMessage("commit").call(); Ref tagRef1 = remoteGit.tag().setName(tagName).call(); - RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*"); + String spec = "refs/heads/*:refs/remotes/origin/*"; git.fetch().setRemote("test").setRefSpecs(spec) .setTagOpt(TagOpt.AUTO_FOLLOW).call(); assertEquals(tagRef1.getObjectId(), db.resolve(tagName)); @@ -193,4 +273,75 @@ assertEquals(RefUpdate.Result.FORCED, update.getResult()); assertEquals(tagRef2.getObjectId(), db.resolve(tagName)); } + + @Test + public void fetchAddRefsWithDuplicateRefspec() throws Exception { + final String branchName = "branch"; + final String remoteBranchName = "test/" + branchName; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef = remoteGit.branchCreate().setName(branchName).call(); + + final String spec1 = "+refs/heads/*:refs/remotes/test/*"; + final String spec2 = "refs/heads/*:refs/remotes/test/*"; + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + remoteConfig.addFetchRefSpec(new RefSpec(spec1)); + remoteConfig.addFetchRefSpec(new RefSpec(spec2)); + remoteConfig.update(config); + + git.fetch().setRemote("test").setRefSpecs(spec1).call(); + assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName)); + } + + @Test + public void fetchPruneRefsWithDuplicateRefspec() + throws Exception { + final String branchName = "branch"; + final String remoteBranchName = "test/" + branchName; + remoteGit.commit().setMessage("commit").call(); + Ref branchRef = remoteGit.branchCreate().setName(branchName).call(); + + final String spec1 = "+refs/heads/*:refs/remotes/test/*"; + final String spec2 = "refs/heads/*:refs/remotes/test/*"; + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + remoteConfig.addFetchRefSpec(new RefSpec(spec1)); + remoteConfig.addFetchRefSpec(new RefSpec(spec2)); + remoteConfig.update(config); + + git.fetch().setRemote("test").setRefSpecs(spec1).call(); + assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName)); + + remoteGit.branchDelete().setBranchNames(branchName).call(); + git.fetch().setRemote("test").setRefSpecs(spec1) + .setRemoveDeletedRefs(true).call(); + assertNull(db.resolve(remoteBranchName)); + } + + @Test + public void fetchUpdateRefsWithDuplicateRefspec() throws Exception { + final String tagName = "foo"; + remoteGit.commit().setMessage("commit").call(); + Ref tagRef1 = remoteGit.tag().setName(tagName).call(); + List refSpecs = new ArrayList<>(); + refSpecs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*")); + // Updating tags via the RefSpecs and setting TagOpt.FETCH_TAGS (or + // AUTO_FOLLOW) will result internally in *two* updates for the same + // ref. + git.fetch().setRemote("test").setRefSpecs(refSpecs) + .setTagOpt(TagOpt.AUTO_FOLLOW).call(); + assertEquals(tagRef1.getObjectId(), db.resolve(tagName)); + + remoteGit.commit().setMessage("commit 2").call(); + Ref tagRef2 = remoteGit.tag().setName(tagName).setForceUpdate(true) + .call(); + FetchResult result = git.fetch().setRemote("test").setRefSpecs(refSpecs) + .setTagOpt(TagOpt.FETCH_TAGS).call(); + assertEquals(2, result.getTrackingRefUpdates().size()); + TrackingRefUpdate update = result + .getTrackingRefUpdate(Constants.R_TAGS + tagName); + assertEquals(RefUpdate.Result.FORCED, update.getResult()); + assertEquals(tagRef2.getObjectId(), db.resolve(tagName)); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,7 +55,6 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FileUtils; -import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -66,11 +65,12 @@ @Before public void setUp() throws Exception { super.setUp(); - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - writeTrashFile("Test.txt", "Hello world"); - git.add().addFilepattern("Test.txt").call(); - git.commit().setMessage("Initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + writeTrashFile("Test.txt", "Hello world"); + git.add().addFilepattern("Test.txt").call(); + git.commit().setMessage("Initial commit").call(); + } bareRepo = Git.cloneRepository().setBare(true) .setURI(db.getDirectory().toURI().toString()) @@ -79,14 +79,6 @@ addRepoToClose(bareRepo); } - @Override - @After - public void tearDown() throws Exception { - db.close(); - bareRepo.close(); - super.tearDown(); - } - @Test public void testWrap() throws JGitInternalException, GitAPIException { Git git = Git.wrap(db); @@ -140,7 +132,6 @@ public void testClose() throws IOException, JGitInternalException, GitAPIException { File workTree = db.getWorkTree(); - db.close(); Git git = Git.open(workTree); git.gc().setExpire(null).call(); git.checkout().setName(git.getRepository().resolve("HEAD^").getName()) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,8 @@ import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -60,22 +62,35 @@ private long lastt = t; + private Git git; + private void measure(String name) { long c = System.currentTimeMillis(); System.out.println(name + ", dt=" + (c - lastt) / 1000.0 + "s"); lastt = c; } + @Before + public void before() { + git = new Git(db); + } + + @After + public void after() { + if (git != null) { + git.close(); + } + } + @Ignore("Test takes way too long (~10 minutes) to be part of the standard suite") @Test public void testAddHugeFile() throws Exception { measure("Commencing test"); File file = new File(db.getWorkTree(), "a.txt"); - RandomAccessFile rf = new RandomAccessFile(file, "rw"); - rf.setLength(4429185024L); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.setLength(4429185024L); + } measure("Created file"); - Git git = new Git(db); git.add().addFilepattern("a.txt").call(); measure("Added file"); @@ -94,9 +109,9 @@ assertEquals(0, status.getUntracked().size()); // Does not change anything, but modified timestamp - rf = new RandomAccessFile(file, "rw"); - rf.write(0); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.write(0); + } status = git.status().call(); measure("Status after non-modifying update"); @@ -110,9 +125,9 @@ assertEquals(0, status.getUntracked().size()); // Change something - rf = new RandomAccessFile(file, "rw"); - rf.write('a'); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.write('a'); + } status = git.status().call(); measure("Status after modifying update"); @@ -126,10 +141,10 @@ assertEquals(0, status.getUntracked().size()); // Truncate mod 4G and re-establish equality - rf = new RandomAccessFile(file, "rw"); - rf.setLength(134217728L); - rf.write(0); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.setLength(134217728L); + rf.write(0); + } status = git.status().call(); measure("Status after truncating update"); @@ -143,9 +158,9 @@ assertEquals(0, status.getUntracked().size()); // Change something - rf = new RandomAccessFile(file, "rw"); - rf.write('a'); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.write('a'); + } status = git.status().call(); measure("Status after modifying and truncating update"); @@ -159,10 +174,10 @@ assertEquals(0, status.getUntracked().size()); // Truncate to entry length becomes negative int - rf = new RandomAccessFile(file, "rw"); - rf.setLength(3429185024L); - rf.write(0); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.setLength(3429185024L); + rf.write(0); + } git.add().addFilepattern("a.txt").call(); measure("Added truncated file"); @@ -182,9 +197,9 @@ assertEquals(0, status.getUntracked().size()); // Change something - rf = new RandomAccessFile(file, "rw"); - rf.write('a'); - rf.close(); + try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) { + rf.write('a'); + } status = git.status().call(); measure("Status after modifying and truncating update"); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,14 +69,14 @@ } @Test - public void testInitRepository() throws IOException, JGitInternalException, - GitAPIException { + public void testInitRepository() + throws IOException, JGitInternalException, GitAPIException { File directory = createTempDirectory("testInitRepository"); InitCommand command = new InitCommand(); command.setDirectory(directory); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); + try (Git git = command.call()) { + assertNotNull(git.getRepository()); + } } @Test @@ -89,9 +89,9 @@ assertTrue(directory.listFiles().length > 0); InitCommand command = new InitCommand(); command.setDirectory(directory); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); + try (Git git = command.call()) { + assertNotNull(git.getRepository()); + } } @Test @@ -101,10 +101,11 @@ InitCommand command = new InitCommand(); command.setDirectory(directory); command.setBare(true); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); - assertTrue(repository.isBare()); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertTrue(repository.isBare()); + } } // non-bare repos where gitDir and directory is set. Same as @@ -117,11 +118,12 @@ InitCommand command = new InitCommand(); command.setDirectory(wt); command.setGitDir(gitDir); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); - assertEqualsFile(wt, repository.getWorkTree()); - assertEqualsFile(gitDir, repository.getDirectory()); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertEqualsFile(wt, repository.getWorkTree()); + assertEqualsFile(gitDir, repository.getDirectory()); + } } // non-bare repos where only gitDir is set. Same as @@ -135,12 +137,13 @@ File gitDir = createTempDirectory("testInitRepository/.git"); InitCommand command = new InitCommand(); command.setGitDir(gitDir); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); - assertEqualsFile(gitDir, repository.getDirectory()); - assertEqualsFile(new File(reader.getProperty("user.dir")), - repository.getWorkTree()); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertEqualsFile(gitDir, repository.getDirectory()); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getWorkTree()); + } } // Bare repos where gitDir and directory is set will only work if gitDir and @@ -169,13 +172,14 @@ .getAbsolutePath()); InitCommand command = new InitCommand(); command.setBare(false); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); - assertEqualsFile(new File(reader.getProperty("user.dir"), ".git"), - repository.getDirectory()); - assertEqualsFile(new File(reader.getProperty("user.dir")), - repository.getWorkTree()); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertEqualsFile(new File(reader.getProperty("user.dir"), ".git"), + repository.getDirectory()); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getWorkTree()); + } } // If neither directory nor gitDir is set in a bare repo make sure @@ -189,12 +193,13 @@ .getAbsolutePath()); InitCommand command = new InitCommand(); command.setBare(true); - Repository repository = command.call().getRepository(); - addRepoToClose(repository); - assertNotNull(repository); - assertEqualsFile(new File(reader.getProperty("user.dir")), - repository.getDirectory()); - assertNull(repository.getWorkTree()); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getDirectory()); + assertNull(repository.getWorkTree()); + } } // In a non-bare repo when directory and gitDir is set then they shouldn't diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,14 +53,16 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.filter.RevFilter; import org.junit.Test; public class LogCommandTest extends RepositoryTestCase { @Test public void logAllCommits() throws Exception { - List commits = new ArrayList(); + List commits = new ArrayList<>(); Git git = Git.wrap(db); writeTrashFile("Test.txt", "Hello world"); @@ -92,7 +94,7 @@ @Test public void logAllCommitsWithTag() throws Exception { - List commits = new ArrayList(); + List commits = new ArrayList<>(); Git git = Git.wrap(db); writeTrashFile("Test.txt", "Hello world"); @@ -121,7 +123,7 @@ } private List createCommits(Git git) throws Exception { - List commits = new ArrayList(); + List commits = new ArrayList<>(); writeTrashFile("Test.txt", "Hello world"); git.add().addFilepattern("Test.txt").call(); commits.add(git.commit().setMessage("commit#1").call()); @@ -210,4 +212,81 @@ assertEquals("commit#2", commit.getShortMessage()); assertFalse(log.hasNext()); } -} \ No newline at end of file + + @Test + public void logOnlyMergeCommits() throws Exception { + setCommitsAndMerge(); + Git git = Git.wrap(db); + + Iterable commits = git.log().all().call(); + Iterator i = commits.iterator(); + RevCommit commit = i.next(); + assertEquals("merge s0 with m1", commit.getFullMessage()); + commit = i.next(); + assertEquals("s0", commit.getFullMessage()); + commit = i.next(); + assertEquals("m1", commit.getFullMessage()); + commit = i.next(); + assertEquals("m0", commit.getFullMessage()); + assertFalse(i.hasNext()); + + commits = git.log().setRevFilter(RevFilter.ONLY_MERGES).call(); + i = commits.iterator(); + commit = i.next(); + assertEquals("merge s0 with m1", commit.getFullMessage()); + assertFalse(i.hasNext()); + } + + @Test + public void logNoMergeCommits() throws Exception { + setCommitsAndMerge(); + Git git = Git.wrap(db); + + Iterable commits = git.log().all().call(); + Iterator i = commits.iterator(); + RevCommit commit = i.next(); + assertEquals("merge s0 with m1", commit.getFullMessage()); + commit = i.next(); + assertEquals("s0", commit.getFullMessage()); + commit = i.next(); + assertEquals("m1", commit.getFullMessage()); + commit = i.next(); + assertEquals("m0", commit.getFullMessage()); + assertFalse(i.hasNext()); + + commits = git.log().setRevFilter(RevFilter.NO_MERGES).call(); + i = commits.iterator(); + commit = i.next(); + assertEquals("m1", commit.getFullMessage()); + commit = i.next(); + assertEquals("s0", commit.getFullMessage()); + commit = i.next(); + assertEquals("m0", commit.getFullMessage()); + assertFalse(i.hasNext()); + } + + private void setCommitsAndMerge() throws Exception { + Git git = Git.wrap(db); + writeTrashFile("file1", "1\n2\n3\n4\n"); + git.add().addFilepattern("file1").call(); + RevCommit masterCommit0 = git.commit().setMessage("m0").call(); + + createBranch(masterCommit0, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("file2", "1\n2\n3\n4\n5\n6\n7\n8\n"); + git.add().addFilepattern("file2").call(); + RevCommit c = git.commit().setMessage("s0").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("file3", "1\n2\n"); + git.add().addFilepattern("file3").call(); + git.commit().setMessage("m1").call(); + + git.merge().include(c.getId()) + .setStrategy(MergeStrategy.RESOLVE) + .setMessage("merge s0 with m1").call(); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,6 +58,7 @@ private Git git; + @Override public void setUp() throws Exception { super.setUp(); git = new Git(db); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,14 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import java.io.File; import java.util.Iterator; @@ -94,11 +97,12 @@ @Test public void testMergeInItself() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); - MergeResult result = git.merge().include(db.getRef(Constants.HEAD)).call(); - assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); + MergeResult result = git.merge().include(db.exactRef(Constants.HEAD)).call(); + assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); + } // no reflog entry written by merge assertEquals("commit (initial): initial commit", db @@ -110,14 +114,15 @@ @Test public void testAlreadyUpToDate() throws Exception { - Git git = new Git(db); - RevCommit first = git.commit().setMessage("initial commit").call(); - createBranch(first, "refs/heads/branch1"); - - RevCommit second = git.commit().setMessage("second commit").call(); - MergeResult result = git.merge().include(db.getRef("refs/heads/branch1")).call(); - assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); - assertEquals(second, result.getNewHead()); + try (Git git = new Git(db)) { + RevCommit first = git.commit().setMessage("initial commit").call(); + createBranch(first, "refs/heads/branch1"); + + RevCommit second = git.commit().setMessage("second commit").call(); + MergeResult result = git.merge().include(db.exactRef("refs/heads/branch1")).call(); + assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); + assertEquals(second, result.getNewHead()); + } // no reflog entry written by merge assertEquals("commit: second commit", db .getReflogReader(Constants.HEAD).getLastEntry().getComment()); @@ -127,18 +132,19 @@ @Test public void testFastForward() throws Exception { - Git git = new Git(db); - RevCommit first = git.commit().setMessage("initial commit").call(); - createBranch(first, "refs/heads/branch1"); + try (Git git = new Git(db)) { + RevCommit first = git.commit().setMessage("initial commit").call(); + createBranch(first, "refs/heads/branch1"); - RevCommit second = git.commit().setMessage("second commit").call(); + RevCommit second = git.commit().setMessage("second commit").call(); - checkoutBranch("refs/heads/branch1"); + checkoutBranch("refs/heads/branch1"); - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call(); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); - assertEquals(second, result.getNewHead()); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); + assertEquals(second, result.getNewHead()); + } assertEquals("merge refs/heads/master: Fast-forward", db.getReflogReader(Constants.HEAD).getLastEntry().getComment()); assertEquals("merge refs/heads/master: Fast-forward", @@ -147,20 +153,21 @@ @Test public void testFastForwardNoCommit() throws Exception { - Git git = new Git(db); - RevCommit first = git.commit().setMessage("initial commit").call(); - createBranch(first, "refs/heads/branch1"); + try (Git git = new Git(db)) { + RevCommit first = git.commit().setMessage("initial commit").call(); + createBranch(first, "refs/heads/branch1"); - RevCommit second = git.commit().setMessage("second commit").call(); + RevCommit second = git.commit().setMessage("second commit").call(); - checkoutBranch("refs/heads/branch1"); + checkoutBranch("refs/heads/branch1"); - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)) - .setCommit(false).call(); + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)) + .setCommit(false).call(); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD, - result.getMergeStatus()); - assertEquals(second, result.getNewHead()); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD, + result.getMergeStatus()); + assertEquals(second, result.getNewHead()); + } assertEquals("merge refs/heads/master: Fast-forward", db .getReflogReader(Constants.HEAD).getLastEntry().getComment()); assertEquals("merge refs/heads/master: Fast-forward", db @@ -169,29 +176,29 @@ @Test public void testFastForwardWithFiles() throws Exception { - Git git = new Git(db); - - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - createBranch(first, "refs/heads/branch1"); - - writeTrashFile("file2", "file2"); - git.add().addFilepattern("file2").call(); - RevCommit second = git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - checkoutBranch("refs/heads/branch1"); - assertFalse(new File(db.getWorkTree(), "file2").exists()); - - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); - assertEquals(second, result.getNewHead()); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + createBranch(first, "refs/heads/branch1"); + + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + RevCommit second = git.commit().setMessage("second commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + checkoutBranch("refs/heads/branch1"); + assertFalse(new File(db.getWorkTree(), "file2").exists()); + + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); + assertEquals(second, result.getNewHead()); + } assertEquals("merge refs/heads/master: Fast-forward", db.getReflogReader(Constants.HEAD).getLastEntry().getComment()); assertEquals("merge refs/heads/master: Fast-forward", @@ -200,56 +207,56 @@ @Test public void testMultipleHeads() throws Exception { - Git git = new Git(db); - - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - createBranch(first, "refs/heads/branch1"); - - writeTrashFile("file2", "file2"); - git.add().addFilepattern("file2").call(); - RevCommit second = git.commit().setMessage("second commit").call(); - - writeTrashFile("file3", "file3"); - git.add().addFilepattern("file3").call(); - git.commit().setMessage("third commit").call(); - - checkoutBranch("refs/heads/branch1"); - assertFalse(new File(db.getWorkTree(), "file2").exists()); - assertFalse(new File(db.getWorkTree(), "file3").exists()); - - MergeCommand merge = git.merge(); - merge.include(second.getId()); - merge.include(db.getRef(Constants.MASTER)); - try { - merge.call(); - fail("Expected exception not thrown when merging multiple heads"); - } catch (InvalidMergeHeadsException e) { - // expected this exception + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + createBranch(first, "refs/heads/branch1"); + + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + RevCommit second = git.commit().setMessage("second commit").call(); + + writeTrashFile("file3", "file3"); + git.add().addFilepattern("file3").call(); + git.commit().setMessage("third commit").call(); + + checkoutBranch("refs/heads/branch1"); + assertFalse(new File(db.getWorkTree(), "file2").exists()); + assertFalse(new File(db.getWorkTree(), "file3").exists()); + + MergeCommand merge = git.merge(); + merge.include(second.getId()); + merge.include(db.exactRef(R_HEADS + MASTER)); + try { + merge.call(); + fail("Expected exception not thrown when merging multiple heads"); + } catch (InvalidMergeHeadsException e) { + // expected this exception + } } } @Theory public void testMergeSuccessAllStrategies(MergeStrategy mergeStrategy) throws Exception { - Git git = new Git(db); - - RevCommit first = git.commit().setMessage("first").call(); - createBranch(first, "refs/heads/side"); - - writeTrashFile("a", "a"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("second").call(); - - checkoutBranch("refs/heads/side"); - writeTrashFile("b", "b"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("third").call(); - - MergeResult result = git.merge().setStrategy(mergeStrategy) - .include(db.getRef(Constants.MASTER)).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + try (Git git = new Git(db)) { + RevCommit first = git.commit().setMessage("first").call(); + createBranch(first, "refs/heads/side"); + + writeTrashFile("a", "a"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("second").call(); + + checkoutBranch("refs/heads/side"); + writeTrashFile("b", "b"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("third").call(); + + MergeResult result = git.merge().setStrategy(mergeStrategy) + .include(db.exactRef(R_HEADS + MASTER)).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + } assertEquals( "merge refs/heads/master: Merge made by " + mergeStrategy.getName() + ".", @@ -263,604 +270,604 @@ @Theory public void testMergeSuccessAllStrategiesNoCommit( MergeStrategy mergeStrategy) throws Exception { - Git git = new Git(db); - - RevCommit first = git.commit().setMessage("first").call(); - createBranch(first, "refs/heads/side"); - - writeTrashFile("a", "a"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("second").call(); - - checkoutBranch("refs/heads/side"); - writeTrashFile("b", "b"); - git.add().addFilepattern("b").call(); - RevCommit thirdCommit = git.commit().setMessage("third").call(); - - MergeResult result = git.merge().setStrategy(mergeStrategy) - .setCommit(false) - .include(db.getRef(Constants.MASTER)).call(); - assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); - assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(), - thirdCommit.getId()); + try (Git git = new Git(db)) { + RevCommit first = git.commit().setMessage("first").call(); + createBranch(first, "refs/heads/side"); + + writeTrashFile("a", "a"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("second").call(); + + checkoutBranch("refs/heads/side"); + writeTrashFile("b", "b"); + git.add().addFilepattern("b").call(); + RevCommit thirdCommit = git.commit().setMessage("third").call(); + + MergeResult result = git.merge().setStrategy(mergeStrategy) + .setCommit(false) + .include(db.exactRef(R_HEADS + MASTER)).call(); + assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); + assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(), + thirdCommit.getId()); + } } @Test public void testContentMerge() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "1\na(side)\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - - writeTrashFile("a", "1\na(main)\n3\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - assertEquals( - "1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n", - read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1\na(side)\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + + writeTrashFile("a", "1\na(main)\n3\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + assertEquals( + "1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n", + read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); - assertEquals(1, result.getConflicts().size()); - assertEquals(3, result.getConflicts().get("a")[0].length); + assertEquals(1, result.getConflicts().size()); + assertEquals(3, result.getConflicts().get("a")[0].length); - assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + } } @Test public void testMergeTag() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("a", "a"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("b", "b"); + git.add().addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + Ref tag = git.tag().setAnnotated(true).setMessage("my tag 01") + .setName("tag01").setObjectId(secondCommit).call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("a", "a2"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); - writeTrashFile("a", "a"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("b", "b"); - git.add().addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - Ref tag = git.tag().setAnnotated(true).setMessage("my tag 01") - .setName("tag01").setObjectId(secondCommit).call(); - - checkoutBranch("refs/heads/master"); - - writeTrashFile("a", "a2"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(tag).setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + MergeResult result = git.merge().include(tag).setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + } } @Test public void testMergeMessage() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); - writeTrashFile("a", "1\na(side)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("side").call(); + writeTrashFile("a", "1\na(side)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("side").call(); - checkoutBranch("refs/heads/master"); + checkoutBranch("refs/heads/master"); - writeTrashFile("a", "1\na(main)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); - git.merge().include(sideBranch) - .setStrategy(MergeStrategy.RESOLVE).call(); + git.merge().include(sideBranch) + .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals("Merge branch 'side'\n\nConflicts:\n\ta\n", - db.readMergeCommitMsg()); + assertEquals("Merge branch 'side'\n\nConflicts:\n\ta\n", + db.readMergeCommitMsg()); + } } @Test public void testMergeNonVersionedPaths() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "1\na(side)\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - - writeTrashFile("a", "1\na(main)\n3\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - git.commit().setMessage("main").call(); - - writeTrashFile("d", "1\nd\n3\n"); - assertTrue(new File(db.getWorkTree(), "e").mkdir()); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - assertEquals( - "1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n", - read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); - assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); - File dir = new File(db.getWorkTree(), "e"); - assertTrue(dir.isDirectory()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1\na(side)\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + + writeTrashFile("a", "1\na(main)\n3\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + git.commit().setMessage("main").call(); + + writeTrashFile("d", "1\nd\n3\n"); + assertTrue(new File(db.getWorkTree(), "e").mkdir()); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + assertEquals( + "1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n", + read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); + assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + File dir = new File(db.getWorkTree(), "e"); + assertTrue(dir.isDirectory()); - assertEquals(1, result.getConflicts().size()); - assertEquals(3, result.getConflicts().get("a")[0].length); + assertEquals(1, result.getConflicts().size()); + assertEquals(3, result.getConflicts().get("a")[0].length); - assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + } } @Test public void testMultipleCreations() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - checkoutBranch("refs/heads/master"); - - writeTrashFile("b", "1\nb(main)\n3\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("b", "1\nb(main)\n3\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + } } @Test public void testMultipleCreationsSameContent() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("b", "1\nb(1)\n3\n"); - git.add().addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - checkoutBranch("refs/heads/master"); - - writeTrashFile("b", "1\nb(1)\n3\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("merge " + secondCommit.getId().getName() - + ": Merge made by resolve.", db - .getReflogReader(Constants.HEAD) - .getLastEntry().getComment()); - assertEquals("merge " + secondCommit.getId().getName() - + ": Merge made by resolve.", db - .getReflogReader(db.getBranch()) - .getLastEntry().getComment()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("b", "1\nb(1)\n3\n"); + git.add().addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("b", "1\nb(1)\n3\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("merge " + secondCommit.getId().getName() + + ": Merge made by resolve.", db + .getReflogReader(Constants.HEAD) + .getLastEntry().getComment()); + assertEquals("merge " + secondCommit.getId().getName() + + ": Merge made by resolve.", db + .getReflogReader(db.getBranch()) + .getLastEntry().getComment()); + } } @Test public void testSuccessfulContentMerge() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "1(side)\na\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - - writeTrashFile("a", "1\na\n3(main)\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - RevCommit thirdCommit = git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - - assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), - "a"))); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), - "c/c/c"))); - - assertEquals(null, result.getConflicts()); - - assertEquals(2, result.getMergedCommits().length); - assertEquals(thirdCommit, result.getMergedCommits()[0]); - assertEquals(secondCommit, result.getMergedCommits()[1]); - - Iterator it = git.log().call().iterator(); - RevCommit newHead = it.next(); - assertEquals(newHead, result.getNewHead()); - assertEquals(2, newHead.getParentCount()); - assertEquals(thirdCommit, newHead.getParent(0)); - assertEquals(secondCommit, newHead.getParent(1)); - assertEquals( - "Merge commit '3fa334456d236a92db020289fe0bf481d91777b4'", - newHead.getFullMessage()); - // @TODO fix me - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); - // test index state + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1(side)\na\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + + writeTrashFile("a", "1\na\n3(main)\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + RevCommit thirdCommit = git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + + assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), + "a"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), + "c/c/c"))); + + assertEquals(null, result.getConflicts()); + + assertEquals(2, result.getMergedCommits().length); + assertEquals(thirdCommit, result.getMergedCommits()[0]); + assertEquals(secondCommit, result.getMergedCommits()[1]); + + Iterator it = git.log().call().iterator(); + RevCommit newHead = it.next(); + assertEquals(newHead, result.getNewHead()); + assertEquals(2, newHead.getParentCount()); + assertEquals(thirdCommit, newHead.getParent(0)); + assertEquals(secondCommit, newHead.getParent(1)); + assertEquals( + "Merge commit '3fa334456d236a92db020289fe0bf481d91777b4'", + newHead.getFullMessage()); + // @TODO fix me + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + // test index state + } } @Test public void testSuccessfulContentMergeNoCommit() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "1(side)\na\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - - writeTrashFile("a", "1\na\n3(main)\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - RevCommit thirdCommit = git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setCommit(false) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); - assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(), - thirdCommit.getId()); - - assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), - "a"))); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); - - assertEquals(null, result.getConflicts()); - - assertEquals(2, result.getMergedCommits().length); - assertEquals(thirdCommit, result.getMergedCommits()[0]); - assertEquals(secondCommit, result.getMergedCommits()[1]); - assertNull(result.getNewHead()); - assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1(side)\na\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + + writeTrashFile("a", "1\na\n3(main)\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + RevCommit thirdCommit = git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setCommit(false) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); + assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(), + thirdCommit.getId()); + + assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), + "a"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); + + assertEquals(null, result.getConflicts()); + + assertEquals(2, result.getMergedCommits().length); + assertEquals(thirdCommit, result.getMergedCommits()[0]); + assertEquals(secondCommit, result.getMergedCommits()[1]); + assertNull(result.getNewHead()); + assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState()); + } } @Test public void testSuccessfulContentMergeAndDirtyworkingTree() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("d", "1\nd\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").addFilepattern("d").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1(side)\na\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + + writeTrashFile("a", "1\na\n3(main)\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + RevCommit thirdCommit = git.commit().setMessage("main").call(); + + writeTrashFile("d", "--- dirty ---"); + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + + assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), + "a"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), + "c/c/c"))); + assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "d"))); + + assertEquals(null, result.getConflicts()); + + assertEquals(2, result.getMergedCommits().length); + assertEquals(thirdCommit, result.getMergedCommits()[0]); + assertEquals(secondCommit, result.getMergedCommits()[1]); + + Iterator it = git.log().call().iterator(); + RevCommit newHead = it.next(); + assertEquals(newHead, result.getNewHead()); + assertEquals(2, newHead.getParentCount()); + assertEquals(thirdCommit, newHead.getParent(0)); + assertEquals(secondCommit, newHead.getParent(1)); + assertEquals( + "Merge commit '064d54d98a4cdb0fed1802a21c656bfda67fe879'", + newHead.getFullMessage()); - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("d", "1\nd\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").addFilepattern("d").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("a", "1(side)\na\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - - writeTrashFile("a", "1\na\n3(main)\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - RevCommit thirdCommit = git.commit().setMessage("main").call(); - - writeTrashFile("d", "--- dirty ---"); - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - - assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), - "a"))); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), - "c/c/c"))); - assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "d"))); - - assertEquals(null, result.getConflicts()); - - assertEquals(2, result.getMergedCommits().length); - assertEquals(thirdCommit, result.getMergedCommits()[0]); - assertEquals(secondCommit, result.getMergedCommits()[1]); - - Iterator it = git.log().call().iterator(); - RevCommit newHead = it.next(); - assertEquals(newHead, result.getNewHead()); - assertEquals(2, newHead.getParentCount()); - assertEquals(thirdCommit, newHead.getParent(0)); - assertEquals(secondCommit, newHead.getParent(1)); - assertEquals( - "Merge commit '064d54d98a4cdb0fed1802a21c656bfda67fe879'", - newHead.getFullMessage()); - - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } } @Test public void testSingleDeletion() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("d", "1\nd\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").addFilepattern("d").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - assertTrue(new File(db.getWorkTree(), "b").delete()); - git.add().addFilepattern("b").setUpdate(true).call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertFalse(new File(db.getWorkTree(), "b").exists()); - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "b").exists()); - - writeTrashFile("a", "1\na\n3(main)\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - RevCommit thirdCommit = git.commit().setMessage("main").call(); - - // We are merging a deletion into our branch - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - - assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a"))); - assertFalse(new File(db.getWorkTree(), "b").exists()); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); - assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); - - // Do the opposite, be on a branch where we have deleted a file and - // merge in a old commit where this file was not deleted - checkoutBranch("refs/heads/side"); - assertFalse(new File(db.getWorkTree(), "b").exists()); - - result = git.merge().include(thirdCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - - assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a"))); - assertFalse(new File(db.getWorkTree(), "b").exists()); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); - assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("d", "1\nd\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").addFilepattern("d").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + assertTrue(new File(db.getWorkTree(), "b").delete()); + git.add().addFilepattern("b").setUpdate(true).call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertFalse(new File(db.getWorkTree(), "b").exists()); + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "b").exists()); + + writeTrashFile("a", "1\na\n3(main)\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + RevCommit thirdCommit = git.commit().setMessage("main").call(); + + // We are merging a deletion into our branch + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + + assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a"))); + assertFalse(new File(db.getWorkTree(), "b").exists()); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); + assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + + // Do the opposite, be on a branch where we have deleted a file and + // merge in a old commit where this file was not deleted + checkoutBranch("refs/heads/side"); + assertFalse(new File(db.getWorkTree(), "b").exists()); + + result = git.merge().include(thirdCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + + assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a"))); + assertFalse(new File(db.getWorkTree(), "b").exists()); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); + assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + } } @Test public void testMultipleDeletions() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - assertTrue(new File(db.getWorkTree(), "a").delete()); - git.add().addFilepattern("a").setUpdate(true).call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertFalse(new File(db.getWorkTree(), "a").exists()); - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "a").exists()); - - assertTrue(new File(db.getWorkTree(), "a").delete()); - git.add().addFilepattern("a").setUpdate(true).call(); - git.commit().setMessage("main").call(); - - // We are merging a deletion into our branch - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + assertTrue(new File(db.getWorkTree(), "a").delete()); + git.add().addFilepattern("a").setUpdate(true).call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertFalse(new File(db.getWorkTree(), "a").exists()); + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "a").exists()); + + assertTrue(new File(db.getWorkTree(), "a").delete()); + git.add().addFilepattern("a").setUpdate(true).call(); + git.commit().setMessage("main").call(); + + // We are merging a deletion into our branch + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + } } @Test public void testDeletionAndConflict() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - writeTrashFile("d", "1\nd\n3\n"); - writeTrashFile("c/c/c", "1\nc\n3\n"); - git.add().addFilepattern("a").addFilepattern("b") - .addFilepattern("c/c/c").addFilepattern("d").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - assertTrue(new File(db.getWorkTree(), "b").delete()); - writeTrashFile("a", "1\na\n3(side)\n"); - git.add().addFilepattern("b").setUpdate(true).call(); - git.add().addFilepattern("a").setUpdate(true).call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - assertFalse(new File(db.getWorkTree(), "b").exists()); - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "b").exists()); - - writeTrashFile("a", "1\na\n3(main)\n"); - writeTrashFile("c/c/c", "1\nc(main)\n3\n"); - git.add().addFilepattern("a").addFilepattern("c/c/c").call(); - git.commit().setMessage("main").call(); - - // We are merging a deletion into our branch - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - assertEquals( - "1\na\n<<<<<<< HEAD\n3(main)\n=======\n3(side)\n>>>>>>> 54ffed45d62d252715fc20e41da92d44c48fb0ff\n", - read(new File(db.getWorkTree(), "a"))); - assertFalse(new File(db.getWorkTree(), "b").exists()); - assertEquals("1\nc(main)\n3\n", - read(new File(db.getWorkTree(), "c/c/c"))); - assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + writeTrashFile("d", "1\nd\n3\n"); + writeTrashFile("c/c/c", "1\nc\n3\n"); + git.add().addFilepattern("a").addFilepattern("b") + .addFilepattern("c/c/c").addFilepattern("d").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + assertTrue(new File(db.getWorkTree(), "b").delete()); + writeTrashFile("a", "1\na\n3(side)\n"); + git.add().addFilepattern("b").setUpdate(true).call(); + git.add().addFilepattern("a").setUpdate(true).call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + assertFalse(new File(db.getWorkTree(), "b").exists()); + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "b").exists()); + + writeTrashFile("a", "1\na\n3(main)\n"); + writeTrashFile("c/c/c", "1\nc(main)\n3\n"); + git.add().addFilepattern("a").addFilepattern("c/c/c").call(); + git.commit().setMessage("main").call(); + + // We are merging a deletion into our branch + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + assertEquals( + "1\na\n<<<<<<< HEAD\n3(main)\n=======\n3(side)\n>>>>>>> 54ffed45d62d252715fc20e41da92d44c48fb0ff\n", + read(new File(db.getWorkTree(), "a"))); + assertFalse(new File(db.getWorkTree(), "b").exists()); + assertEquals("1\nc(main)\n3\n", + read(new File(db.getWorkTree(), "c/c/c"))); + assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); + } } @Test public void testDeletionOnMasterConflict() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - // create side branch and modify "a" - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - writeTrashFile("a", "1\na(side)\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - // delete a on master to generate conflict - checkoutBranch("refs/heads/master"); - git.rm().addFilepattern("a").call(); - git.commit().setMessage("main").call(); - - // merge side with master - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - // result should be 'a' conflicting with workspace content from side - assertTrue(new File(db.getWorkTree(), "a").exists()); - assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + // create side branch and modify "a" + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + writeTrashFile("a", "1\na(side)\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + // delete a on master to generate conflict + checkoutBranch("refs/heads/master"); + git.rm().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + // merge side with master + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + // result should be 'a' conflicting with workspace content from side + assertTrue(new File(db.getWorkTree(), "a").exists()); + assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + } } @Test public void testDeletionOnSideConflict() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - // create side branch and delete "a" - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - git.rm().addFilepattern("a").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - // update a on master to generate conflict - checkoutBranch("refs/heads/master"); - writeTrashFile("a", "1\na(main)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); - - // merge side with master - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - assertTrue(new File(db.getWorkTree(), "a").exists()); - assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + // create side branch and delete "a" + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + git.rm().addFilepattern("a").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + // update a on master to generate conflict + checkoutBranch("refs/heads/master"); + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + // merge side with master + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + assertTrue(new File(db.getWorkTree(), "a").exists()); + assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals(1, result.getConflicts().size()); - assertEquals(3, result.getConflicts().get("a")[0].length); + assertEquals(1, result.getConflicts().size()); + assertEquals(3, result.getConflicts().get("a")[0].length); + } } @Test @@ -868,262 +875,262 @@ // this test is essentially the same as testDeletionOnSideConflict, // however if once rename support is added this test should result in a // successful merge instead of a conflict - Git git = new Git(db); - - writeTrashFile("x", "add x"); - git.add().addFilepattern("x").call(); - RevCommit initial = git.commit().setMessage("add x").call(); - - createBranch(initial, "refs/heads/d1"); - createBranch(initial, "refs/heads/d2"); - - // rename x to y on d1 - checkoutBranch("refs/heads/d1"); - new File(db.getWorkTree(), "x") - .renameTo(new File(db.getWorkTree(), "y")); - git.rm().addFilepattern("x").call(); - git.add().addFilepattern("y").call(); - RevCommit d1Commit = git.commit().setMessage("d1 rename x -> y").call(); - - checkoutBranch("refs/heads/d2"); - writeTrashFile("x", "d2 change"); - git.add().addFilepattern("x").call(); - RevCommit d2Commit = git.commit().setMessage("d2 change in x").call(); - - checkoutBranch("refs/heads/master"); - MergeResult d1Merge = git.merge().include(d1Commit).call(); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD, - d1Merge.getMergeStatus()); - - MergeResult d2Merge = git.merge().include(d2Commit).call(); - assertEquals(MergeResult.MergeStatus.CONFLICTING, - d2Merge.getMergeStatus()); - assertEquals(1, d2Merge.getConflicts().size()); - assertEquals(3, d2Merge.getConflicts().get("x")[0].length); + try (Git git = new Git(db)) { + writeTrashFile("x", "add x"); + git.add().addFilepattern("x").call(); + RevCommit initial = git.commit().setMessage("add x").call(); + + createBranch(initial, "refs/heads/d1"); + createBranch(initial, "refs/heads/d2"); + + // rename x to y on d1 + checkoutBranch("refs/heads/d1"); + new File(db.getWorkTree(), "x") + .renameTo(new File(db.getWorkTree(), "y")); + git.rm().addFilepattern("x").call(); + git.add().addFilepattern("y").call(); + RevCommit d1Commit = git.commit().setMessage("d1 rename x -> y").call(); + + checkoutBranch("refs/heads/d2"); + writeTrashFile("x", "d2 change"); + git.add().addFilepattern("x").call(); + RevCommit d2Commit = git.commit().setMessage("d2 change in x").call(); + + checkoutBranch("refs/heads/master"); + MergeResult d1Merge = git.merge().include(d1Commit).call(); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD, + d1Merge.getMergeStatus()); + + MergeResult d2Merge = git.merge().include(d2Commit).call(); + assertEquals(MergeResult.MergeStatus.CONFLICTING, + d2Merge.getMergeStatus()); + assertEquals(1, d2Merge.getConflicts().size()); + assertEquals(3, d2Merge.getConflicts().get("x")[0].length); + } } @Test public void testMergeFailingWithDirtyWorkingTree() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); - writeTrashFile("a", "1(side)\na\n3\n"); - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); + writeTrashFile("a", "1(side)\na\n3\n"); + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); - assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); - checkoutBranch("refs/heads/master"); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b"))); + checkoutBranch("refs/heads/master"); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - writeTrashFile("a", "1\na\n3(main)\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); + writeTrashFile("a", "1\na\n3(main)\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); - writeTrashFile("a", "--- dirty ---"); - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); + writeTrashFile("a", "--- dirty ---"); + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.FAILED, result.getMergeStatus()); + assertEquals(MergeStatus.FAILED, result.getMergeStatus()); - assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals(null, result.getConflicts()); + assertEquals(null, result.getConflicts()); - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } } @Test public void testMergeConflictFileFolder() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("c/c/c", "1\nc(side)\n3\n"); - writeTrashFile("d", "1\nd(side)\n3\n"); - git.add().addFilepattern("c/c/c").addFilepattern("d").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - checkoutBranch("refs/heads/master"); - - writeTrashFile("c", "1\nc(main)\n3\n"); - writeTrashFile("d/d/d", "1\nd(main)\n3\n"); - git.add().addFilepattern("c").addFilepattern("d/d/d").call(); - git.commit().setMessage("main").call(); + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); + writeTrashFile("c/c/c", "1\nc(side)\n3\n"); + writeTrashFile("d", "1\nd(side)\n3\n"); + git.add().addFilepattern("c/c/c").addFilepattern("d").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + checkoutBranch("refs/heads/master"); + + writeTrashFile("c", "1\nc(main)\n3\n"); + writeTrashFile("d/d/d", "1\nd(main)\n3\n"); + git.add().addFilepattern("c").addFilepattern("d/d/d").call(); + git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals("1\na\n3\n", read(new File(db.getWorkTree(), "a"))); - assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); - assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), "c"))); - assertEquals("1\nd(main)\n3\n", read(new File(db.getWorkTree(), "d/d/d"))); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - assertEquals(null, result.getConflicts()); + assertEquals("1\na\n3\n", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), "c"))); + assertEquals("1\nd(main)\n3\n", read(new File(db.getWorkTree(), "d/d/d"))); - assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + assertEquals(null, result.getConflicts()); + + assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + } } @Test public void testSuccessfulMergeFailsDueToDirtyIndex() throws Exception { - Git git = new Git(db); - - File fileA = writeTrashFile("a", "a"); - RevCommit initialCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - // modify file a - write(fileA, "a(side)"); - writeTrashFile("b", "b"); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - checkoutBranch("refs/heads/master"); - writeTrashFile("c", "c"); - addAllAndCommit(git); - - // modify and add file a - write(fileA, "a(modified)"); - git.add().addFilepattern("a").call(); - // do not commit - - // get current index state - String indexState = indexState(CONTENT); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); + try (Git git = new Git(db)) { + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify and add file a + write(fileA, "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX, - indexState, fileA); + checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX, + indexState, fileA); + } } @Test public void testConflictingMergeFailsDueToDirtyIndex() throws Exception { - Git git = new Git(db); - - File fileA = writeTrashFile("a", "a"); - RevCommit initialCommit = addAllAndCommit(git); + try (Git git = new Git(db)) { + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + // modify file a - this will cause a conflict during merge + write(fileA, "a(master)"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify and add file a + write(fileA, "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - // modify file a - write(fileA, "a(side)"); - writeTrashFile("b", "b"); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - checkoutBranch("refs/heads/master"); - // modify file a - this will cause a conflict during merge - write(fileA, "a(master)"); - writeTrashFile("c", "c"); - addAllAndCommit(git); - - // modify and add file a - write(fileA, "a(modified)"); - git.add().addFilepattern("a").call(); - // do not commit - - // get current index state - String indexState = indexState(CONTENT); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - - checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX, - indexState, fileA); + checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX, + indexState, fileA); + } } @Test public void testSuccessfulMergeFailsDueToDirtyWorktree() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify file a + write(fileA, "a(modified)"); + // do not add and commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - File fileA = writeTrashFile("a", "a"); - RevCommit initialCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - // modify file a - write(fileA, "a(side)"); - writeTrashFile("b", "b"); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - checkoutBranch("refs/heads/master"); - writeTrashFile("c", "c"); - addAllAndCommit(git); - - // modify file a - write(fileA, "a(modified)"); - // do not add and commit - - // get current index state - String indexState = indexState(CONTENT); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - - checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE, - indexState, fileA); + checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE, + indexState, fileA); + } } @Test public void testConflictingMergeFailsDueToDirtyWorktree() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + // modify file a - this will cause a conflict during merge + write(fileA, "a(master)"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify file a + write(fileA, "a(modified)"); + // do not add and commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); - File fileA = writeTrashFile("a", "a"); - RevCommit initialCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - // modify file a - write(fileA, "a(side)"); - writeTrashFile("b", "b"); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - checkoutBranch("refs/heads/master"); - // modify file a - this will cause a conflict during merge - write(fileA, "a(master)"); - writeTrashFile("c", "c"); - addAllAndCommit(git); - - // modify file a - write(fileA, "a(modified)"); - // do not add and commit - - // get current index state - String indexState = indexState(CONTENT); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - - checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE, - indexState, fileA); + checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE, + indexState, fileA); + } } @Test @@ -1141,29 +1148,30 @@ file = new File(folder2, "file2.txt"); write(file, "folder2--file2.txt"); - Git git = new Git(db); - git.add().addFilepattern(folder1.getName()) - .addFilepattern(folder2.getName()).call(); - RevCommit commit1 = git.commit().setMessage("adding folders").call(); - - recursiveDelete(folder1); - recursiveDelete(folder2); - git.rm().addFilepattern("folder1/file1.txt") - .addFilepattern("folder1/file2.txt") - .addFilepattern("folder2/file1.txt") - .addFilepattern("folder2/file2.txt").call(); - RevCommit commit2 = git.commit() - .setMessage("removing folders on 'branch'").call(); - - git.checkout().setName(commit1.name()).call(); - - MergeResult result = git.merge().include(commit2.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD, - result.getMergeStatus()); - assertEquals(commit2, result.getNewHead()); - assertFalse(folder1.exists()); - assertFalse(folder2.exists()); + try (Git git = new Git(db)) { + git.add().addFilepattern(folder1.getName()) + .addFilepattern(folder2.getName()).call(); + RevCommit commit1 = git.commit().setMessage("adding folders").call(); + + recursiveDelete(folder1); + recursiveDelete(folder2); + git.rm().addFilepattern("folder1/file1.txt") + .addFilepattern("folder1/file2.txt") + .addFilepattern("folder2/file1.txt") + .addFilepattern("folder2/file2.txt").call(); + RevCommit commit2 = git.commit() + .setMessage("removing folders on 'branch'").call(); + + git.checkout().setName(commit1.name()).call(); + + MergeResult result = git.merge().include(commit2.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD, + result.getMergeStatus()); + assertEquals(commit2, result.getNewHead()); + assertFalse(folder1.exists()); + assertFalse(folder2.exists()); + } } @Test @@ -1181,365 +1189,374 @@ file = new File(folder2, "file2.txt"); write(file, "folder2--file2.txt"); - Git git = new Git(db); - git.add().addFilepattern(folder1.getName()) - .addFilepattern(folder2.getName()).call(); - RevCommit base = git.commit().setMessage("adding folders").call(); - - recursiveDelete(folder1); - recursiveDelete(folder2); - git.rm().addFilepattern("folder1/file1.txt") - .addFilepattern("folder1/file2.txt") - .addFilepattern("folder2/file1.txt") - .addFilepattern("folder2/file2.txt").call(); - RevCommit other = git.commit() - .setMessage("removing folders on 'branch'").call(); - - git.checkout().setName(base.name()).call(); - - file = new File(folder2, "file3.txt"); - write(file, "folder2--file3.txt"); - - git.add().addFilepattern(folder2.getName()).call(); - git.commit().setMessage("adding another file").call(); - - MergeResult result = git.merge().include(other.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - - assertEquals(MergeResult.MergeStatus.MERGED, - result.getMergeStatus()); - assertFalse(folder1.exists()); + try (Git git = new Git(db)) { + git.add().addFilepattern(folder1.getName()) + .addFilepattern(folder2.getName()).call(); + RevCommit base = git.commit().setMessage("adding folders").call(); + + recursiveDelete(folder1); + recursiveDelete(folder2); + git.rm().addFilepattern("folder1/file1.txt") + .addFilepattern("folder1/file2.txt") + .addFilepattern("folder2/file1.txt") + .addFilepattern("folder2/file2.txt").call(); + RevCommit other = git.commit() + .setMessage("removing folders on 'branch'").call(); + + git.checkout().setName(base.name()).call(); + + file = new File(folder2, "file3.txt"); + write(file, "folder2--file3.txt"); + + git.add().addFilepattern(folder2.getName()).call(); + git.commit().setMessage("adding another file").call(); + + MergeResult result = git.merge().include(other.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + + assertEquals(MergeResult.MergeStatus.MERGED, + result.getMergeStatus()); + assertFalse(folder1.exists()); + } } @Test public void testFileModeMerge() throws Exception { - if (!FS.DETECTED.supportsExecute()) - return; // Only Java6 - Git git = new Git(db); - - writeTrashFile("mergeableMode", "a"); - setExecutable(git, "mergeableMode", false); - writeTrashFile("conflictingModeWithBase", "a"); - setExecutable(git, "conflictingModeWithBase", false); - RevCommit initialCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - setExecutable(git, "mergeableMode", true); - writeTrashFile("conflictingModeNoBase", "b"); - setExecutable(git, "conflictingModeNoBase", true); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side2"); - checkoutBranch("refs/heads/side2"); - setExecutable(git, "mergeableMode", false); - assertFalse(new File(git.getRepository().getWorkTree(), - "conflictingModeNoBase").exists()); - writeTrashFile("conflictingModeNoBase", "b"); - setExecutable(git, "conflictingModeNoBase", false); - addAllAndCommit(git); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - assertTrue(canExecute(git, "mergeableMode")); - assertFalse(canExecute(git, "conflictingModeNoBase")); + assumeTrue(FS.DETECTED.supportsExecute()); + try (Git git = new Git(db)) { + writeTrashFile("mergeableMode", "a"); + setExecutable(git, "mergeableMode", false); + writeTrashFile("conflictingModeWithBase", "a"); + setExecutable(git, "conflictingModeWithBase", false); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + setExecutable(git, "mergeableMode", true); + writeTrashFile("conflictingModeNoBase", "b"); + setExecutable(git, "conflictingModeNoBase", true); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side2"); + checkoutBranch("refs/heads/side2"); + setExecutable(git, "mergeableMode", false); + assertFalse(new File(git.getRepository().getWorkTree(), + "conflictingModeNoBase").exists()); + writeTrashFile("conflictingModeNoBase", "b"); + setExecutable(git, "conflictingModeNoBase", false); + addAllAndCommit(git); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + assertTrue(canExecute(git, "mergeableMode")); + assertFalse(canExecute(git, "conflictingModeNoBase")); + } } @Test public void testFileModeMergeWithDirtyWorkTree() throws Exception { - if (!FS.DETECTED.supportsExecute()) - return; // Only Java6 (or set x bit in index) + assumeTrue(FS.DETECTED.supportsExecute()); - Git git = new Git(db); - - writeTrashFile("mergeableButDirty", "a"); - setExecutable(git, "mergeableButDirty", false); - RevCommit initialCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - setExecutable(git, "mergeableButDirty", true); - RevCommit sideCommit = addAllAndCommit(git); - - // switch branch - createBranch(initialCommit, "refs/heads/side2"); - checkoutBranch("refs/heads/side2"); - setExecutable(git, "mergeableButDirty", false); - addAllAndCommit(git); - - writeTrashFile("mergeableButDirty", "b"); - - // merge - MergeResult result = git.merge().include(sideCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.FAILED, result.getMergeStatus()); - assertFalse(canExecute(git, "mergeableButDirty")); + try (Git git = new Git(db)) { + writeTrashFile("mergeableButDirty", "a"); + setExecutable(git, "mergeableButDirty", false); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + setExecutable(git, "mergeableButDirty", true); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side2"); + checkoutBranch("refs/heads/side2"); + setExecutable(git, "mergeableButDirty", false); + addAllAndCommit(git); + + writeTrashFile("mergeableButDirty", "b"); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.FAILED, result.getMergeStatus()); + assertFalse(canExecute(git, "mergeableButDirty")); + } } @Test public void testSquashFastForward() throws Exception { - Git git = new Git(db); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + createBranch(first, "refs/heads/branch1"); + checkoutBranch("refs/heads/branch1"); + + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + RevCommit second = git.commit().setMessage("second commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + writeTrashFile("file3", "file3"); + git.add().addFilepattern("file3").call(); + RevCommit third = git.commit().setMessage("third commit").call(); + assertTrue(new File(db.getWorkTree(), "file3").exists()); + + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertFalse(new File(db.getWorkTree(), "file2").exists()); + assertFalse(new File(db.getWorkTree(), "file3").exists()); + + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertTrue(new File(db.getWorkTree(), "file3").exists()); + assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, + result.getMergeStatus()); + assertEquals(first, result.getNewHead()); // HEAD didn't move + assertEquals(first, db.resolve(Constants.HEAD + "^{commit}")); + + assertEquals( + "Squashed commit of the following:\n\ncommit " + + third.getName() + + "\nAuthor: " + + third.getAuthorIdent().getName() + + " <" + + third.getAuthorIdent().getEmailAddress() + + ">\nDate: " + + dateFormatter.formatDate(third + .getAuthorIdent()) + + "\n\n\tthird commit\n\ncommit " + + second.getName() + + "\nAuthor: " + + second.getAuthorIdent().getName() + + " <" + + second.getAuthorIdent().getEmailAddress() + + ">\nDate: " + + dateFormatter.formatDate(second + .getAuthorIdent()) + "\n\n\tsecond commit\n", + db.readSquashCommitMsg()); + assertNull(db.readMergeCommitMsg()); - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - createBranch(first, "refs/heads/branch1"); - checkoutBranch("refs/heads/branch1"); - - writeTrashFile("file2", "file2"); - git.add().addFilepattern("file2").call(); - RevCommit second = git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - writeTrashFile("file3", "file3"); - git.add().addFilepattern("file3").call(); - RevCommit third = git.commit().setMessage("third commit").call(); - assertTrue(new File(db.getWorkTree(), "file3").exists()); - - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertFalse(new File(db.getWorkTree(), "file2").exists()); - assertFalse(new File(db.getWorkTree(), "file3").exists()); - - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertTrue(new File(db.getWorkTree(), "file3").exists()); - assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, - result.getMergeStatus()); - assertEquals(first, result.getNewHead()); // HEAD didn't move - assertEquals(first, db.resolve(Constants.HEAD + "^{commit}")); - - assertEquals( - "Squashed commit of the following:\n\ncommit " - + third.getName() - + "\nAuthor: " - + third.getAuthorIdent().getName() - + " <" - + third.getAuthorIdent().getEmailAddress() - + ">\nDate: " - + dateFormatter.formatDate(third - .getAuthorIdent()) - + "\n\n\tthird commit\n\ncommit " - + second.getName() - + "\nAuthor: " - + second.getAuthorIdent().getName() - + " <" - + second.getAuthorIdent().getEmailAddress() - + ">\nDate: " - + dateFormatter.formatDate(second - .getAuthorIdent()) + "\n\n\tsecond commit\n", - db.readSquashCommitMsg()); - assertNull(db.readMergeCommitMsg()); - - Status stat = git.status().call(); - assertEquals(Sets.of("file2", "file3"), stat.getAdded()); + Status stat = git.status().call(); + assertEquals(Sets.of("file2", "file3"), stat.getAdded()); + } } @Test public void testSquashMerge() throws Exception { - Git git = new Git(db); - - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - createBranch(first, "refs/heads/branch1"); - - writeTrashFile("file2", "file2"); - git.add().addFilepattern("file2").call(); - RevCommit second = git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - checkoutBranch("refs/heads/branch1"); - - writeTrashFile("file3", "file3"); - git.add().addFilepattern("file3").call(); - RevCommit third = git.commit().setMessage("third commit").call(); - assertTrue(new File(db.getWorkTree(), "file3").exists()); - - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertFalse(new File(db.getWorkTree(), "file3").exists()); - - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertTrue(new File(db.getWorkTree(), "file3").exists()); - assertEquals(MergeResult.MergeStatus.MERGED_SQUASHED, - result.getMergeStatus()); - assertEquals(second, result.getNewHead()); // HEAD didn't move - assertEquals(second, db.resolve(Constants.HEAD + "^{commit}")); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + createBranch(first, "refs/heads/branch1"); + + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + RevCommit second = git.commit().setMessage("second commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + checkoutBranch("refs/heads/branch1"); + + writeTrashFile("file3", "file3"); + git.add().addFilepattern("file3").call(); + RevCommit third = git.commit().setMessage("third commit").call(); + assertTrue(new File(db.getWorkTree(), "file3").exists()); + + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertFalse(new File(db.getWorkTree(), "file3").exists()); + + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertTrue(new File(db.getWorkTree(), "file3").exists()); + assertEquals(MergeResult.MergeStatus.MERGED_SQUASHED, + result.getMergeStatus()); + assertEquals(second, result.getNewHead()); // HEAD didn't move + assertEquals(second, db.resolve(Constants.HEAD + "^{commit}")); + + assertEquals( + "Squashed commit of the following:\n\ncommit " + + third.getName() + + "\nAuthor: " + + third.getAuthorIdent().getName() + + " <" + + third.getAuthorIdent().getEmailAddress() + + ">\nDate: " + + dateFormatter.formatDate(third + .getAuthorIdent()) + "\n\n\tthird commit\n", + db.readSquashCommitMsg()); + assertNull(db.readMergeCommitMsg()); - assertEquals( - "Squashed commit of the following:\n\ncommit " - + third.getName() - + "\nAuthor: " - + third.getAuthorIdent().getName() - + " <" - + third.getAuthorIdent().getEmailAddress() - + ">\nDate: " - + dateFormatter.formatDate(third - .getAuthorIdent()) + "\n\n\tthird commit\n", - db.readSquashCommitMsg()); - assertNull(db.readMergeCommitMsg()); - - Status stat = git.status().call(); - assertEquals(Sets.of("file3"), stat.getAdded()); + Status stat = git.status().call(); + assertEquals(Sets.of("file3"), stat.getAdded()); + } } @Test public void testSquashMergeConflict() throws Exception { - Git git = new Git(db); - - writeTrashFile("file1", "file1"); - git.add().addFilepattern("file1").call(); - RevCommit first = git.commit().setMessage("initial commit").call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - createBranch(first, "refs/heads/branch1"); - - writeTrashFile("file2", "master"); - git.add().addFilepattern("file2").call(); - RevCommit second = git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - checkoutBranch("refs/heads/branch1"); - - writeTrashFile("file2", "branch"); - git.add().addFilepattern("file2").call(); - RevCommit third = git.commit().setMessage("third commit").call(); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - checkoutBranch("refs/heads/master"); - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); - - assertTrue(new File(db.getWorkTree(), "file1").exists()); - assertTrue(new File(db.getWorkTree(), "file2").exists()); - assertEquals(MergeResult.MergeStatus.CONFLICTING, - result.getMergeStatus()); - assertNull(result.getNewHead()); - assertEquals(second, db.resolve(Constants.HEAD + "^{commit}")); + try (Git git = new Git(db)) { + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + createBranch(first, "refs/heads/branch1"); + + writeTrashFile("file2", "master"); + git.add().addFilepattern("file2").call(); + RevCommit second = git.commit().setMessage("second commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + checkoutBranch("refs/heads/branch1"); + + writeTrashFile("file2", "branch"); + git.add().addFilepattern("file2").call(); + RevCommit third = git.commit().setMessage("third commit").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + checkoutBranch("refs/heads/master"); + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); + + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + assertEquals(MergeResult.MergeStatus.CONFLICTING, + result.getMergeStatus()); + assertNull(result.getNewHead()); + assertEquals(second, db.resolve(Constants.HEAD + "^{commit}")); + + assertEquals( + "Squashed commit of the following:\n\ncommit " + + third.getName() + + "\nAuthor: " + + third.getAuthorIdent().getName() + + " <" + + third.getAuthorIdent().getEmailAddress() + + ">\nDate: " + + dateFormatter.formatDate(third + .getAuthorIdent()) + "\n\n\tthird commit\n", + db.readSquashCommitMsg()); + assertEquals("\nConflicts:\n\tfile2\n", db.readMergeCommitMsg()); - assertEquals( - "Squashed commit of the following:\n\ncommit " - + third.getName() - + "\nAuthor: " - + third.getAuthorIdent().getName() - + " <" - + third.getAuthorIdent().getEmailAddress() - + ">\nDate: " - + dateFormatter.formatDate(third - .getAuthorIdent()) + "\n\n\tthird commit\n", - db.readSquashCommitMsg()); - assertEquals("\nConflicts:\n\tfile2\n", db.readMergeCommitMsg()); - - Status stat = git.status().call(); - assertEquals(Sets.of("file2"), stat.getConflicting()); + Status stat = git.status().call(); + assertEquals(Sets.of("file2"), stat.getConflicting()); + } } @Test public void testFastForwardOnly() throws Exception { - Git git = new Git(db); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - createBranch(initialCommit, "refs/heads/branch1"); - git.commit().setMessage("second commit").call(); - checkoutBranch("refs/heads/branch1"); - - MergeCommand merge = git.merge(); - merge.setFastForward(FastForwardMode.FF_ONLY); - merge.include(db.getRef(Constants.MASTER)); - MergeResult result = merge.call(); + try (Git git = new Git(db)) { + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + createBranch(initialCommit, "refs/heads/branch1"); + git.commit().setMessage("second commit").call(); + checkoutBranch("refs/heads/branch1"); + + MergeCommand merge = git.merge(); + merge.setFastForward(FastForwardMode.FF_ONLY); + merge.include(db.exactRef(R_HEADS + MASTER)); + MergeResult result = merge.call(); - assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus()); + assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus()); + } } @Test public void testNoFastForward() throws Exception { - Git git = new Git(db); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - createBranch(initialCommit, "refs/heads/branch1"); - git.commit().setMessage("second commit").call(); - checkoutBranch("refs/heads/branch1"); - - MergeCommand merge = git.merge(); - merge.setFastForward(FastForwardMode.NO_FF); - merge.include(db.getRef(Constants.MASTER)); - MergeResult result = merge.call(); + try (Git git = new Git(db)) { + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + createBranch(initialCommit, "refs/heads/branch1"); + git.commit().setMessage("second commit").call(); + checkoutBranch("refs/heads/branch1"); + + MergeCommand merge = git.merge(); + merge.setFastForward(FastForwardMode.NO_FF); + merge.include(db.exactRef(R_HEADS + MASTER)); + MergeResult result = merge.call(); - assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + } } @Test public void testNoFastForwardNoCommit() throws Exception { // given - Git git = new Git(db); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - createBranch(initialCommit, "refs/heads/branch1"); - RevCommit secondCommit = git.commit().setMessage("second commit") - .call(); - checkoutBranch("refs/heads/branch1"); - - // when - MergeCommand merge = git.merge(); - merge.setFastForward(FastForwardMode.NO_FF); - merge.include(db.getRef(Constants.MASTER)); - merge.setCommit(false); - MergeResult result = merge.call(); - - // then - assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); - assertEquals(2, result.getMergedCommits().length); - assertEquals(initialCommit, result.getMergedCommits()[0]); - assertEquals(secondCommit, result.getMergedCommits()[1]); - assertNull(result.getNewHead()); - assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState()); + try (Git git = new Git(db)) { + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + createBranch(initialCommit, "refs/heads/branch1"); + RevCommit secondCommit = git.commit().setMessage("second commit") + .call(); + checkoutBranch("refs/heads/branch1"); + + // when + MergeCommand merge = git.merge(); + merge.setFastForward(FastForwardMode.NO_FF); + merge.include(db.exactRef(R_HEADS + MASTER)); + merge.setCommit(false); + MergeResult result = merge.call(); + + // then + assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); + assertEquals(2, result.getMergedCommits().length); + assertEquals(initialCommit, result.getMergedCommits()[0]); + assertEquals(secondCommit, result.getMergedCommits()[1]); + assertNull(result.getNewHead()); + assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState()); + } } @Test public void testFastForwardOnlyNotPossible() throws Exception { - Git git = new Git(db); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - createBranch(initialCommit, "refs/heads/branch1"); - git.commit().setMessage("second commit").call(); - checkoutBranch("refs/heads/branch1"); - writeTrashFile("file1", "branch1"); - git.add().addFilepattern("file").call(); - git.commit().setMessage("second commit on branch1").call(); - MergeCommand merge = git.merge(); - merge.setFastForward(FastForwardMode.FF_ONLY); - merge.include(db.getRef(Constants.MASTER)); - MergeResult result = merge.call(); + try (Git git = new Git(db)) { + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + createBranch(initialCommit, "refs/heads/branch1"); + git.commit().setMessage("second commit").call(); + checkoutBranch("refs/heads/branch1"); + writeTrashFile("file1", "branch1"); + git.add().addFilepattern("file").call(); + git.commit().setMessage("second commit on branch1").call(); + MergeCommand merge = git.merge(); + merge.setFastForward(FastForwardMode.FF_ONLY); + merge.include(db.exactRef(R_HEADS + MASTER)); + MergeResult result = merge.call(); - assertEquals(MergeStatus.ABORTED, result.getMergeStatus()); + assertEquals(MergeStatus.ABORTED, result.getMergeStatus()); + } } @Test public void testRecursiveMergeWithConflict() throws Exception { - TestRepository db_t = new TestRepository(db); + TestRepository db_t = new TestRepository<>(db); BranchBuilder master = db_t.branch("master"); RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n") .message("m0").create(); @@ -1569,65 +1586,65 @@ @Test public void testMergeWithMessageOption() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("side").call(); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("side").call(); - checkoutBranch("refs/heads/master"); + checkoutBranch("refs/heads/master"); - writeTrashFile("c", "1\nc\n3\n"); - git.add().addFilepattern("c").call(); - git.commit().setMessage("main").call(); + writeTrashFile("c", "1\nc\n3\n"); + git.add().addFilepattern("c").call(); + git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); - git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) - .setMessage("user message").call(); + git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) + .setMessage("user message").call(); - assertNull(db.readMergeCommitMsg()); + assertNull(db.readMergeCommitMsg()); - Iterator it = git.log().call().iterator(); - RevCommit newHead = it.next(); - assertEquals("user message", newHead.getFullMessage()); + Iterator it = git.log().call().iterator(); + RevCommit newHead = it.next(); + assertEquals("user message", newHead.getFullMessage()); + } } @Test public void testMergeConflictWithMessageOption() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); - writeTrashFile("a", "1\na(side)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("side").call(); + writeTrashFile("a", "1\na(side)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("side").call(); - checkoutBranch("refs/heads/master"); + checkoutBranch("refs/heads/master"); - writeTrashFile("a", "1\na(main)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); - git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) - .setMessage("user message").call(); + git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) + .setMessage("user message").call(); - assertEquals("user message\n\nConflicts:\n\ta\n", - db.readMergeCommitMsg()); + assertEquals("user message\n\nConflicts:\n\ta\n", + db.readMergeCommitMsg()); + } } private static void setExecutable(Git git, String path, boolean executable) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,7 +64,7 @@ @Before public void setUp() throws Exception { super.setUp(); - tr = new TestRepository(db); + tr = new TestRepository<>(db); git = new Git(db); } @@ -95,9 +95,9 @@ tr.update("refs/heads/master", c); tr.update("refs/tags/tag", c); assertOneResult("master", - git.nameRev().addRef(db.getRef("refs/heads/master")), c); + git.nameRev().addRef(db.exactRef("refs/heads/master")), c); assertOneResult("tag", - git.nameRev().addRef(db.getRef("refs/tags/tag")), c); + git.nameRev().addRef(db.exactRef("refs/tags/tag")), c); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,7 +42,9 @@ */ package org.eclipse.jgit.api; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; + import java.util.List; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -86,7 +88,7 @@ git.notesAdd().setObjectId(commit2).setMessage("data").call(); Note note = git.notesShow().setObjectId(commit2).call(); String content = new String(db.open(note.getData()).getCachedBytes(), - "UTF-8"); + UTF_8); assertEquals(content, "data"); git.notesRemove().setObjectId(commit2).call(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,10 +43,12 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import org.eclipse.jgit.api.CheckoutCommand.Stage; import org.eclipse.jgit.api.errors.JGitInternalException; @@ -59,6 +61,9 @@ import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -73,6 +78,8 @@ private static final String FILE3 = "Test3.txt"; + private static final String LINK = "link"; + Git git; RevCommit initialCommit; @@ -99,6 +106,64 @@ } @Test + public void testUpdateSymLink() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, FILE1); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("3", read(path.toFile())); + + writeLink(LINK, FILE2); + assertEquals("c", read(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("3", read(path.toFile())); + } + + @Test + public void testUpdateBrokenSymLinkToDirectory() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, "f"); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("f", FileUtils.readSymLink(path.toFile())); + assertTrue(path.toFile().exists()); + + writeLink(LINK, "link_to_nowhere"); + assertFalse(path.toFile().exists()); + assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("f", FileUtils.readSymLink(path.toFile())); + } + + @Test + public void testUpdateBrokenSymLink() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, FILE1); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("3", read(path.toFile())); + assertEquals(FILE1, FileUtils.readSymLink(path.toFile())); + + writeLink(LINK, "link_to_nowhere"); + assertFalse(path.toFile().exists()); + assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("3", read(path.toFile())); + } + + @Test public void testUpdateWorkingDirectory() throws Exception { CheckoutCommand co = git.checkout(); File written = writeTrashFile(FILE1, ""); @@ -206,6 +271,7 @@ } } + @Test public void testCheckoutMixedNewlines() throws Exception { // "git config core.autocrlf true" StoredConfig config = git.getRepository().getConfig(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,7 @@ */ package org.eclipse.jgit.api; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -58,6 +59,7 @@ import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -140,11 +142,12 @@ ObjectId[] mergedCommits = mergeResult.getMergedCommits(); assertEquals(targetCommit.getId(), mergedCommits[0]); assertEquals(sourceCommit.getId(), mergedCommits[1]); - RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult - .getNewHead()); - String message = "Merge branch 'master' of " - + db.getWorkTree().getAbsolutePath(); - assertEquals(message, mergeCommit.getShortMessage()); + try (RevWalk rw = new RevWalk(dbTarget)) { + RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead()); + String message = "Merge branch 'master' of " + + db.getWorkTree().getAbsolutePath(); + assertEquals(message, mergeCommit.getShortMessage()); + } } @Test @@ -184,6 +187,40 @@ } @Test + public void testPullWithUntrackedStash() throws Exception { + target.pull().call(); + + // change the source file + writeToFile(sourceFile, "Source change"); + source.add().addFilepattern("SomeFile.txt").call(); + source.commit().setMessage("Source change in remote").call(); + + // write untracked file + writeToFile(new File(dbTarget.getWorkTree(), "untracked.txt"), + "untracked"); + RevCommit stash = target.stashCreate().setIndexMessage("message here") + .setIncludeUntracked(true).call(); + assertNotNull(stash); + assertTrue(target.status().call().isClean()); + + // pull from source + assertTrue(target.pull().call().isSuccessful()); + assertEquals("[SomeFile.txt, mode:100644, content:Source change]", + indexState(dbTarget, CONTENT)); + assertFalse(JGitTestUtil.check(dbTarget, "untracked.txt")); + assertEquals("Source change", + JGitTestUtil.read(dbTarget, "SomeFile.txt")); + + // apply the stash + target.stashApply().setStashRef(stash.getName()).call(); + assertEquals("[SomeFile.txt, mode:100644, content:Source change]", + indexState(dbTarget, CONTENT)); + assertEquals("untracked", JGitTestUtil.read(dbTarget, "untracked.txt")); + assertEquals("Source change", + JGitTestUtil.read(dbTarget, "SomeFile.txt")); + } + + @Test public void testPullLocalConflict() throws Exception { target.branchCreate().setName("basedOnMaster").setStartPoint( "refs/heads/master").setUpstreamMode(SetupUpstreamMode.TRACK) @@ -259,11 +296,12 @@ ObjectId[] mergedCommits = mergeResult.getMergedCommits(); assertEquals(targetCommit.getId(), mergedCommits[0]); assertEquals(sourceCommit.getId(), mergedCommits[1]); - RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult - .getNewHead()); - String message = "Merge branch 'other' of " - + db.getWorkTree().getAbsolutePath(); - assertEquals(message, mergeCommit.getShortMessage()); + try (RevWalk rw = new RevWalk(dbTarget)) { + RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead()); + String message = "Merge branch 'other' of " + + db.getWorkTree().getAbsolutePath(); + assertEquals(message, mergeCommit.getShortMessage()); + } } @Test @@ -293,11 +331,12 @@ ObjectId[] mergedCommits = mergeResult.getMergedCommits(); assertEquals(targetCommit.getId(), mergedCommits[0]); assertEquals(sourceCommit.getId(), mergedCommits[1]); - RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult - .getNewHead()); - String message = "Merge branch 'other' of " - + db.getWorkTree().getAbsolutePath() + " into other"; - assertEquals(message, mergeCommit.getShortMessage()); + try (RevWalk rw = new RevWalk(dbTarget)) { + RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead()); + String message = "Merge branch 'other' of " + + db.getWorkTree().getAbsolutePath() + " into other"; + assertEquals(message, mergeCommit.getShortMessage()); + } } private enum TestPullMode { @@ -308,6 +347,7 @@ /** global rebase config should be respected */ public void testPullWithRebasePreserve1Config() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "preserve"); @@ -322,6 +362,7 @@ /** the branch-local config should win over the global config */ public void testPullWithRebasePreserveConfig2() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "false"); @@ -337,6 +378,7 @@ /** the branch-local config should be respected */ public void testPullWithRebasePreserveConfig3() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("branch", "master", "rebase", "preserve"); @@ -351,6 +393,7 @@ /** global rebase config should be respected */ public void testPullWithRebaseConfig1() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "true"); @@ -365,6 +408,7 @@ /** the branch-local config should win over the global config */ public void testPullWithRebaseConfig2() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "preserve"); @@ -380,6 +424,7 @@ /** the branch-local config should be respected */ public void testPullWithRebaseConfig3() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("branch", "master", "rebase", "true"); @@ -394,6 +439,7 @@ /** without config it should merge */ public void testPullWithoutConfig() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { return target.pull().call(); } @@ -405,6 +451,7 @@ /** the branch local config should win over the global config */ public void testPullWithMergeConfig() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "true"); @@ -420,6 +467,7 @@ /** the branch local config should win over the global config */ public void testPullWithMergeConfig2() throws Exception { Callable setup = new Callable() { + @Override public PullResult call() throws Exception { StoredConfig config = dbTarget.getConfig(); config.setString("pull", null, "rebase", "false"); @@ -543,34 +591,23 @@ private static void writeToFile(File actFile, String string) throws IOException { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(actFile); - fos.write(string.getBytes("UTF-8")); - fos.close(); - } finally { - if (fos != null) - fos.close(); + try (FileOutputStream fos = new FileOutputStream(actFile)) { + fos.write(string.getBytes(UTF_8)); } } private static void assertFileContentsEqual(File actFile, String string) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - FileInputStream fis = null; byte[] buffer = new byte[100]; - try { - fis = new FileInputStream(actFile); + try (FileInputStream fis = new FileInputStream(actFile)) { int read = fis.read(buffer); while (read > 0) { bos.write(buffer, 0, read); read = fis.read(buffer); } - String content = new String(bos.toByteArray(), "UTF-8"); + String content = new String(bos.toByteArray(), UTF_8); assertEquals(string, content); - } finally { - if (fis != null) - fis.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,7 @@ */ package org.eclipse.jgit.api; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -61,6 +62,7 @@ import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.StoredConfig; @@ -148,6 +150,57 @@ } @Test + public void testPullFastForwardDetachedHead() throws Exception { + Repository repository = source.getRepository(); + writeToFile(sourceFile, "2nd commit"); + source.add().addFilepattern("SomeFile.txt").call(); + source.commit().setMessage("2nd commit").call(); + + try (RevWalk revWalk = new RevWalk(repository)) { + // git checkout HEAD^ + String initialBranch = repository.getBranch(); + Ref initialRef = repository.findRef(Constants.HEAD); + RevCommit initialCommit = revWalk + .parseCommit(initialRef.getObjectId()); + assertEquals("this test need linear history", 1, + initialCommit.getParentCount()); + source.checkout().setName(initialCommit.getParent(0).getName()) + .call(); + assertFalse("expected detached HEAD", + repository.getFullBranch().startsWith(Constants.R_HEADS)); + + // change and commit another file + File otherFile = new File(sourceFile.getParentFile(), + System.currentTimeMillis() + ".tst"); + writeToFile(otherFile, "other 2nd commit"); + source.add().addFilepattern(otherFile.getName()).call(); + RevCommit newCommit = source.commit().setMessage("other 2nd commit") + .call(); + + // git pull --rebase initialBranch + source.pull().setRebase(true).setRemote(".") + .setRemoteBranchName(initialBranch) + .call(); + + assertEquals(RepositoryState.SAFE, + source.getRepository().getRepositoryState()); + Ref head = source.getRepository().findRef(Constants.HEAD); + RevCommit headCommit = revWalk.parseCommit(head.getObjectId()); + + // HEAD^ == initialCommit, no merge commit + assertEquals(1, headCommit.getParentCount()); + assertEquals(initialCommit, headCommit.getParent(0)); + + // both contributions for both commits are available + assertFileContentsEqual(sourceFile, "2nd commit"); + assertFileContentsEqual(otherFile, "other 2nd commit"); + // HEAD has same message as rebased commit + assertEquals(newCommit.getShortMessage(), + headCommit.getShortMessage()); + } + } + + @Test public void testPullConflict() throws Exception { PullResult res = target.pull().call(); // nothing to update since we don't have different data yet @@ -273,26 +326,27 @@ // Get the HEAD and HEAD~1 commits Repository targetRepo = target.getRepository(); - RevWalk revWalk = new RevWalk(targetRepo); - ObjectId headId = targetRepo.resolve(Constants.HEAD); - RevCommit root = revWalk.parseCommit(headId); - revWalk.markStart(root); - // HEAD - RevCommit head = revWalk.next(); - // HEAD~1 - RevCommit beforeHead = revWalk.next(); - - // verify the commit message on the HEAD commit - assertEquals(TARGET_COMMIT_MESSAGE, head.getFullMessage()); - // verify the commit just before HEAD - assertEquals(SOURCE_COMMIT_MESSAGE, beforeHead.getFullMessage()); - - // verify file states - assertFileContentsEqual(sourceFile, SOURCE_FILE_CONTENTS); - assertFileContentsEqual(newFile, NEW_FILE_CONTENTS); - // verify repository state - assertEquals(RepositoryState.SAFE, target - .getRepository().getRepositoryState()); + try (RevWalk revWalk = new RevWalk(targetRepo)) { + ObjectId headId = targetRepo.resolve(Constants.HEAD); + RevCommit root = revWalk.parseCommit(headId); + revWalk.markStart(root); + // HEAD + RevCommit head = revWalk.next(); + // HEAD~1 + RevCommit beforeHead = revWalk.next(); + + // verify the commit message on the HEAD commit + assertEquals(TARGET_COMMIT_MESSAGE, head.getFullMessage()); + // verify the commit just before HEAD + assertEquals(SOURCE_COMMIT_MESSAGE, beforeHead.getFullMessage()); + + // verify file states + assertFileContentsEqual(sourceFile, SOURCE_FILE_CONTENTS); + assertFileContentsEqual(newFile, NEW_FILE_CONTENTS); + // verify repository state + assertEquals(RepositoryState.SAFE, target + .getRepository().getRepositoryState()); + } } @Override @@ -341,34 +395,23 @@ private static void writeToFile(File actFile, String string) throws IOException { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(actFile); - fos.write(string.getBytes("UTF-8")); - fos.close(); - } finally { - if (fos != null) - fos.close(); + try (FileOutputStream fos = new FileOutputStream(actFile)) { + fos.write(string.getBytes(UTF_8)); } } private static void assertFileContentsEqual(File actFile, String string) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - FileInputStream fis = null; byte[] buffer = new byte[100]; - try { - fis = new FileInputStream(actFile); + try (FileInputStream fis = new FileInputStream(actFile)) { int read = fis.read(buffer); while (read > 0) { bos.write(buffer, 0, read); read = fis.read(buffer); } - String content = new String(bos.toByteArray(), "UTF-8"); + String content = new String(bos.toByteArray(), UTF_8); assertEquals(string, content); - } finally { - if (fis != null) - fis.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Properties; @@ -55,17 +56,23 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class PushCommandTest extends RepositoryTestCase { @@ -76,6 +83,11 @@ // create other repository Repository db2 = createWorkRepository(); + final StoredConfig config2 = db2.getConfig(); + + // this tests that this config can be parsed properly + config2.setString("fsck", "", "missingEmail", "ignore"); + config2.save(); // setup the first repository final StoredConfig config = db.getConfig(); @@ -85,75 +97,117 @@ remoteConfig.update(config); config.save(); - Git git1 = new Git(db); - // create some refs via commits and tag - RevCommit commit = git1.commit().setMessage("initial commit").call(); - Ref tagRef = git1.tag().setName("tag").call(); - - try { - db2.resolve(commit.getId().getName() + "^{commit}"); - fail("id shouldn't exist yet"); - } catch (MissingObjectException e) { - // we should get here + try (Git git1 = new Git(db)) { + // create some refs via commits and tag + RevCommit commit = git1.commit().setMessage("initial commit").call(); + Ref tagRef = git1.tag().setName("tag").call(); + + try { + db2.resolve(commit.getId().getName() + "^{commit}"); + fail("id shouldn't exist yet"); + } catch (MissingObjectException e) { + // we should get here + } + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec) + .call(); + + assertEquals(commit.getId(), + db2.resolve(commit.getId().getName() + "^{commit}")); + assertEquals(tagRef.getObjectId(), + db2.resolve(tagRef.getObjectId().getName())); } - - RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); - git1.push().setRemote("test").setRefSpecs(spec) - .call(); - - assertEquals(commit.getId(), - db2.resolve(commit.getId().getName() + "^{commit}")); - assertEquals(tagRef.getObjectId(), - db2.resolve(tagRef.getObjectId().getName())); } @Test - public void testTrackingUpdate() throws Exception { - Repository db2 = createBareRepository(); - - String remote = "origin"; - String branch = "refs/heads/master"; - String trackingBranch = "refs/remotes/" + remote + "/master"; - - Git git = new Git(db); - - RevCommit commit1 = git.commit().setMessage("Initial commit") - .call(); + public void testPrePushHook() throws JGitInternalException, IOException, + GitAPIException, URISyntaxException { - RefUpdate branchRefUpdate = db.updateRef(branch); - branchRefUpdate.setNewObjectId(commit1.getId()); - branchRefUpdate.update(); - - RefUpdate trackingBranchRefUpdate = db.updateRef(trackingBranch); - trackingBranchRefUpdate.setNewObjectId(commit1.getId()); - trackingBranchRefUpdate.update(); + // create other repository + Repository db2 = createWorkRepository(); + // setup the first repository final StoredConfig config = db.getConfig(); - RemoteConfig remoteConfig = new RemoteConfig(config, remote); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); URIish uri = new URIish(db2.getDirectory().toURI().toURL()); remoteConfig.addURI(uri); - remoteConfig.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" - + remote + "/*")); remoteConfig.update(config); config.save(); + File hookOutput = new File(getTemporaryDirectory(), "hookOutput"); + writeHookFile(PrePushHook.NAME, "#!/bin/sh\necho 1:$1, 2:$2, 3:$3 >\"" + + hookOutput.toPath() + "\"\ncat - >>\"" + hookOutput.toPath() + + "\"\nexit 0"); + + try (Git git1 = new Git(db)) { + // create some refs via commits and tag + RevCommit commit = git1.commit().setMessage("initial commit").call(); + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec).call(); + assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master " + + commit.getName() + " refs/heads/x " + + ObjectId.zeroId().name(), read(hookOutput)); + } + } + + private File writeHookFile(final String name, final String data) + throws IOException { + File path = new File(db.getWorkTree() + "/.git/hooks/", name); + JGitTestUtil.write(path, data); + FS.DETECTED.setExecute(path, true); + return path; + } - RevCommit commit2 = git.commit().setMessage("Commit to push").call(); - RefSpec spec = new RefSpec(branch + ":" + branch); - Iterable resultIterable = git.push().setRemote(remote) - .setRefSpecs(spec).call(); - - PushResult result = resultIterable.iterator().next(); - TrackingRefUpdate trackingRefUpdate = result - .getTrackingRefUpdate(trackingBranch); - - assertNotNull(trackingRefUpdate); - assertEquals(trackingBranch, trackingRefUpdate.getLocalName()); - assertEquals(branch, trackingRefUpdate.getRemoteName()); - assertEquals(commit2.getId(), trackingRefUpdate.getNewObjectId()); - assertEquals(commit2.getId(), db.resolve(trackingBranch)); - assertEquals(commit2.getId(), db2.resolve(branch)); + @Test + public void testTrackingUpdate() throws Exception { + Repository db2 = createBareRepository(); + + String remote = "origin"; + String branch = "refs/heads/master"; + String trackingBranch = "refs/remotes/" + remote + "/master"; + + try (Git git = new Git(db)) { + RevCommit commit1 = git.commit().setMessage("Initial commit") + .call(); + + RefUpdate branchRefUpdate = db.updateRef(branch); + branchRefUpdate.setNewObjectId(commit1.getId()); + branchRefUpdate.update(); + + RefUpdate trackingBranchRefUpdate = db.updateRef(trackingBranch); + trackingBranchRefUpdate.setNewObjectId(commit1.getId()); + trackingBranchRefUpdate.update(); + + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, remote); + URIish uri = new URIish(db2.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" + + remote + "/*")); + remoteConfig.update(config); + config.save(); + + + RevCommit commit2 = git.commit().setMessage("Commit to push").call(); + + RefSpec spec = new RefSpec(branch + ":" + branch); + Iterable resultIterable = git.push().setRemote(remote) + .setRefSpecs(spec).call(); + + PushResult result = resultIterable.iterator().next(); + TrackingRefUpdate trackingRefUpdate = result + .getTrackingRefUpdate(trackingBranch); + + assertNotNull(trackingRefUpdate); + assertEquals(trackingBranch, trackingRefUpdate.getLocalName()); + assertEquals(branch, trackingRefUpdate.getRemoteName()); + assertEquals(commit2.getId(), trackingRefUpdate.getNewObjectId()); + assertEquals(commit2.getId(), db.resolve(trackingBranch)); + assertEquals(commit2.getId(), db2.resolve(branch)); + } } /** @@ -163,40 +217,38 @@ */ @Test public void testPushRefUpdate() throws Exception { - Git git = new Git(db); - Git git2 = new Git(createBareRepository()); + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(git2.getRepository().getDirectory().toURI() + .toURL()); + remoteConfig.addURI(uri); + remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); - final StoredConfig config = git.getRepository().getConfig(); - RemoteConfig remoteConfig = new RemoteConfig(config, "test"); - URIish uri = new URIish(git2.getRepository().getDirectory().toURI() - .toURL()); - remoteConfig.addURI(uri); - remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*")); - remoteConfig.update(config); - config.save(); - - writeTrashFile("f", "content of f"); - git.add().addFilepattern("f").call(); - RevCommit commit = git.commit().setMessage("adding f").call(); - - assertEquals(null, git2.getRepository().resolve("refs/heads/master")); - git.push().setRemote("test").call(); - assertEquals(commit.getId(), - git2.getRepository().resolve("refs/heads/master")); - - git.branchCreate().setName("refs/heads/test").call(); - git.checkout().setName("refs/heads/test").call(); - - - for (int i = 0; i < 6; i++) { - writeTrashFile("f" + i, "content of f" + i); - git.add().addFilepattern("f" + i).call(); - commit = git.commit().setMessage("adding f" + i).call(); + assertEquals(null, git2.getRepository().resolve("refs/heads/master")); git.push().setRemote("test").call(); - git2.getRepository().getAllRefs(); - assertEquals("failed to update on attempt " + i, commit.getId(), - git2.getRepository().resolve("refs/heads/test")); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/master")); + + git.branchCreate().setName("refs/heads/test").call(); + git.checkout().setName("refs/heads/test").call(); + for (int i = 0; i < 6; i++) { + writeTrashFile("f" + i, "content of f" + i); + git.add().addFilepattern("f" + i).call(); + commit = git.commit().setMessage("adding f" + i).call(); + git.push().setRemote("test").call(); + git2.getRepository().getAllRefs(); + assertEquals("failed to update on attempt " + i, commit.getId(), + git2.getRepository().resolve("refs/heads/test")); + } } } @@ -207,28 +259,26 @@ */ @Test public void testPushWithRefSpecFromConfig() throws Exception { - Git git = new Git(db); - Git git2 = new Git(createBareRepository()); - - final StoredConfig config = git.getRepository().getConfig(); - RemoteConfig remoteConfig = new RemoteConfig(config, "test"); - URIish uri = new URIish(git2.getRepository().getDirectory().toURI() - .toURL()); - remoteConfig.addURI(uri); - remoteConfig.addPushRefSpec(new RefSpec("HEAD:refs/heads/newbranch")); - remoteConfig.update(config); - config.save(); - - writeTrashFile("f", "content of f"); - git.add().addFilepattern("f").call(); - RevCommit commit = git.commit().setMessage("adding f").call(); - - assertEquals(null, git2.getRepository().resolve("refs/heads/master")); - git.push().setRemote("test").call(); - assertEquals(commit.getId(), - git2.getRepository().resolve("refs/heads/newbranch")); - + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(git2.getRepository().getDirectory().toURI() + .toURL()); + remoteConfig.addURI(uri); + remoteConfig.addPushRefSpec(new RefSpec("HEAD:refs/heads/newbranch")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + assertEquals(null, git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").call(); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/newbranch")); + } } /** @@ -238,38 +288,37 @@ */ @Test public void testPushWithoutPushRefSpec() throws Exception { - Git git = new Git(db); - Git git2 = new Git(createBareRepository()); - - final StoredConfig config = git.getRepository().getConfig(); - RemoteConfig remoteConfig = new RemoteConfig(config, "test"); - URIish uri = new URIish(git2.getRepository().getDirectory().toURI() - .toURL()); - remoteConfig.addURI(uri); - remoteConfig.addFetchRefSpec(new RefSpec( - "+refs/heads/*:refs/remotes/origin/*")); - remoteConfig.update(config); - config.save(); - - writeTrashFile("f", "content of f"); - git.add().addFilepattern("f").call(); - RevCommit commit = git.commit().setMessage("adding f").call(); - - git.checkout().setName("not-pushed").setCreateBranch(true).call(); - git.checkout().setName("branchtopush").setCreateBranch(true).call(); - - assertEquals(null, - git2.getRepository().resolve("refs/heads/branchtopush")); - assertEquals(null, git2.getRepository() - .resolve("refs/heads/not-pushed")); - assertEquals(null, git2.getRepository().resolve("refs/heads/master")); - git.push().setRemote("test").call(); - assertEquals(commit.getId(), - git2.getRepository().resolve("refs/heads/branchtopush")); - assertEquals(null, git2.getRepository() - .resolve("refs/heads/not-pushed")); - assertEquals(null, git2.getRepository().resolve("refs/heads/master")); - + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(git2.getRepository().getDirectory().toURI() + .toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec(new RefSpec( + "+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, git2.getRepository() + .resolve("refs/heads/not-pushed")); + assertEquals(null, git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").call(); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, git2.getRepository() + .resolve("refs/heads/not-pushed")); + assertEquals(null, git2.getRepository().resolve("refs/heads/master")); + } } /** @@ -290,51 +339,103 @@ remoteConfig.update(config); config.save(); - Git git1 = new Git(db); - Git git2 = new Git(db2); + try (Git git1 = new Git(db); + Git git2 = new Git(db2)) { + // push master (with a new commit) to the remote + git1.commit().setMessage("initial commit").call(); - // push master (with a new commit) to the remote - git1.commit().setMessage("initial commit").call(); + RefSpec spec = new RefSpec("refs/heads/*:refs/heads/*"); + git1.push().setRemote("test").setRefSpecs(spec).call(); - RefSpec spec = new RefSpec("refs/heads/*:refs/heads/*"); - git1.push().setRemote("test").setRefSpecs(spec).call(); + // create an unrelated ref and a commit on our remote + git2.branchCreate().setName("refs/heads/other").call(); + git2.checkout().setName("refs/heads/other").call(); + + writeTrashFile("a", "content of a"); + git2.add().addFilepattern("a").call(); + RevCommit commit2 = git2.commit().setMessage("adding a").call(); + + // run a gc to ensure we have a bitmap index + Properties res = git1.gc().setExpire(null).call(); + assertEquals(7, res.size()); + + // create another commit so we have something else to push + writeTrashFile("b", "content of b"); + git1.add().addFilepattern("b").call(); + RevCommit commit3 = git1.commit().setMessage("adding b").call(); + + try { + // Re-run the push. Failure may happen here. + git1.push().setRemote("test").setRefSpecs(spec).call(); + } catch (TransportException e) { + assertTrue("should be caused by a MissingObjectException", e + .getCause().getCause() instanceof MissingObjectException); + fail("caught MissingObjectException for a change we don't have"); + } + + // Remote will have both a and b. Master will have only b + try { + db.resolve(commit2.getId().getName() + "^{commit}"); + fail("id shouldn't exist locally"); + } catch (MissingObjectException e) { + // we should get here + } + assertEquals(commit2.getId(), + db2.resolve(commit2.getId().getName() + "^{commit}")); + assertEquals(commit3.getId(), + db2.resolve(commit3.getId().getName() + "^{commit}")); + } + } - // create an unrelated ref and a commit on our remote - git2.branchCreate().setName("refs/heads/other").call(); - git2.checkout().setName("refs/heads/other").call(); - - writeTrashFile("a", "content of a"); - git2.add().addFilepattern("a").call(); - RevCommit commit2 = git2.commit().setMessage("adding a").call(); - - // run a gc to ensure we have a bitmap index - Properties res = git1.gc().setExpire(null).call(); - assertEquals(7, res.size()); - - // create another commit so we have something else to push - writeTrashFile("b", "content of b"); - git1.add().addFilepattern("b").call(); - RevCommit commit3 = git1.commit().setMessage("adding b").call(); + @Test + public void testPushWithLease() throws JGitInternalException, IOException, + GitAPIException, URISyntaxException { - try { - // Re-run the push. Failure may happen here. - git1.push().setRemote("test").setRefSpecs(spec).call(); - } catch (TransportException e) { - assertTrue("should be caused by a MissingObjectException", e - .getCause().getCause() instanceof MissingObjectException); - fail("caught MissingObjectException for a change we don't have"); - } + // create other repository + Repository db2 = createWorkRepository(); + + // setup the first repository + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(db2.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.update(config); + config.save(); - // Remote will have both a and b. Master will have only b - try { - db.resolve(commit2.getId().getName() + "^{commit}"); - fail("id shouldn't exist locally"); - } catch (MissingObjectException e) { - // we should get here + try (Git git1 = new Git(db)) { + // create one commit and push it + RevCommit commit = git1.commit().setMessage("initial commit").call(); + git1.branchCreate().setName("initial").call(); + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec) + .call(); + + assertEquals(commit.getId(), + db2.resolve(commit.getId().getName() + "^{commit}")); + //now try to force-push a new commit, with a good lease + + git1.commit().setMessage("second commit").call(); + Iterable results = + git1.push().setRemote("test").setRefSpecs(spec) + .setRefLeaseSpecs(new RefLeaseSpec("refs/heads/x", "initial")) + .call(); + for (PushResult result : results) { + RemoteRefUpdate update = result.getRemoteUpdate("refs/heads/x"); + assertEquals(update.getStatus(), RemoteRefUpdate.Status.OK); + } + + git1.commit().setMessage("third commit").call(); + //now try to force-push a new commit, with a bad lease + + results = + git1.push().setRemote("test").setRefSpecs(spec) + .setRefLeaseSpecs(new RefLeaseSpec("refs/heads/x", "initial")) + .call(); + for (PushResult result : results) { + RemoteRefUpdate update = result.getRemoteUpdate("refs/heads/x"); + assertEquals(update.getStatus(), RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED); + } } - assertEquals(commit2.getId(), - db2.resolve(commit2.getId().getName() + "^{commit}")); - assertEquals(commit3.getId(), - db2.resolve(commit3.getId().getName() + "^{commit}")); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,7 @@ */ package org.eclipse.jgit.api; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -288,13 +289,14 @@ RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); assertEquals(Status.OK, res.getStatus()); - RevWalk rw = new RevWalk(db); - rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); - assertDerivedFrom(rw.next(), e); - assertDerivedFrom(rw.next(), d); - assertDerivedFrom(rw.next(), c); - assertEquals(b, rw.next()); - assertEquals(a, rw.next()); + try (RevWalk rw = new RevWalk(db)) { + rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); + assertDerivedFrom(rw.next(), e); + assertDerivedFrom(rw.next(), d); + assertDerivedFrom(rw.next(), c); + assertEquals(b, rw.next()); + assertEquals(a, rw.next()); + } List headLog = db.getReflogReader(Constants.HEAD) .getReverseEntries(); @@ -354,8 +356,6 @@ */ private void doTestRebasePreservingMerges(boolean testConflict) throws Exception { - RevWalk rw = new RevWalk(db); - // create file1 on master writeTrashFile(FILE1, FILE1); git.add().addFilepattern(FILE1).call(); @@ -409,7 +409,9 @@ f = git.commit().setMessage("commit f").call(); } else { assertEquals(MergeStatus.MERGED, result.getMergeStatus()); - f = rw.parseCommit(result.getNewHead()); + try (RevWalk rw = new RevWalk(db)) { + f = rw.parseCommit(result.getNewHead()); + } } RebaseResult res = git.rebase().setUpstream("refs/heads/master") @@ -453,23 +455,25 @@ assertEquals("file2", read("file2")); assertEquals("more change", read("file3")); - rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); - RevCommit newF = rw.next(); - assertDerivedFrom(newF, f); - assertEquals(2, newF.getParentCount()); - RevCommit newD = rw.next(); - assertDerivedFrom(newD, d); - if (testConflict) - assertEquals("d new", readFile("conflict", newD)); - RevCommit newE = rw.next(); - assertDerivedFrom(newE, e); - if (testConflict) - assertEquals("e new", readFile("conflict", newE)); - assertEquals(newD, newF.getParent(0)); - assertEquals(newE, newF.getParent(1)); - assertDerivedFrom(rw.next(), c); - assertEquals(b, rw.next()); - assertEquals(a, rw.next()); + try (RevWalk rw = new RevWalk(db)) { + rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); + RevCommit newF = rw.next(); + assertDerivedFrom(newF, f); + assertEquals(2, newF.getParentCount()); + RevCommit newD = rw.next(); + assertDerivedFrom(newD, d); + if (testConflict) + assertEquals("d new", readFile("conflict", newD)); + RevCommit newE = rw.next(); + assertDerivedFrom(newE, e); + if (testConflict) + assertEquals("e new", readFile("conflict", newE)); + assertEquals(newD, newF.getParent(0)); + assertEquals(newE, newF.getParent(1)); + assertDerivedFrom(rw.next(), c); + assertEquals(b, rw.next()); + assertEquals(a, rw.next()); + } } private String readFile(String path, RevCommit commit) throws IOException { @@ -517,88 +521,89 @@ */ private void doTestRebasePreservingMergesWithUnrelatedSide( boolean testConflict) throws Exception { - RevWalk rw = new RevWalk(db); - rw.sort(RevSort.TOPO); - - writeTrashFile(FILE1, FILE1); - git.add().addFilepattern(FILE1).call(); - RevCommit a = git.commit().setMessage("commit a").call(); - - writeTrashFile("file2", "blah"); - git.add().addFilepattern("file2").call(); - RevCommit b = git.commit().setMessage("commit b").call(); - - // create a topic branch - createBranch(b, "refs/heads/topic"); - checkoutBranch("refs/heads/topic"); - - writeTrashFile("file3", "more changess"); - writeTrashFile(FILE1, "preparing conflict"); - git.add().addFilepattern("file3").addFilepattern(FILE1).call(); - RevCommit c = git.commit().setMessage("commit c").call(); + try (RevWalk rw = new RevWalk(db)) { + rw.sort(RevSort.TOPO); - createBranch(a, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - writeTrashFile("conflict", "e"); - writeTrashFile(FILE1, FILE1 + "\n" + "line 2"); - git.add().addFilepattern(".").call(); - RevCommit e = git.commit().setMessage("commit e").call(); - - // switch back to topic and merge in side, creating d - checkoutBranch("refs/heads/topic"); - MergeResult result = git.merge().include(e) - .setStrategy(MergeStrategy.RESOLVE).call(); + writeTrashFile(FILE1, FILE1); + git.add().addFilepattern(FILE1).call(); + RevCommit a = git.commit().setMessage("commit a").call(); + + writeTrashFile("file2", "blah"); + git.add().addFilepattern("file2").call(); + RevCommit b = git.commit().setMessage("commit b").call(); + + // create a topic branch + createBranch(b, "refs/heads/topic"); + checkoutBranch("refs/heads/topic"); + + writeTrashFile("file3", "more changess"); + writeTrashFile(FILE1, "preparing conflict"); + git.add().addFilepattern("file3").addFilepattern(FILE1).call(); + RevCommit c = git.commit().setMessage("commit c").call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - assertEquals(result.getConflicts().keySet(), - Collections.singleton(FILE1)); - writeTrashFile(FILE1, "merge resolution"); - git.add().addFilepattern(FILE1).call(); - RevCommit d = git.commit().setMessage("commit d").call(); - - RevCommit f = commitFile("file2", "new content two", "topic"); + createBranch(a, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + writeTrashFile("conflict", "e"); + writeTrashFile(FILE1, FILE1 + "\n" + "line 2"); + git.add().addFilepattern(".").call(); + RevCommit e = git.commit().setMessage("commit e").call(); + + // switch back to topic and merge in side, creating d + checkoutBranch("refs/heads/topic"); + MergeResult result = git.merge().include(e) + .setStrategy(MergeStrategy.RESOLVE).call(); - checkoutBranch("refs/heads/master"); - writeTrashFile("fileg", "fileg"); - if (testConflict) - writeTrashFile("conflict", "g"); - git.add().addFilepattern(".").call(); - RevCommit g = git.commit().setMessage("commit g").call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + assertEquals(result.getConflicts().keySet(), + Collections.singleton(FILE1)); + writeTrashFile(FILE1, "merge resolution"); + git.add().addFilepattern(FILE1).call(); + RevCommit d = git.commit().setMessage("commit d").call(); + + RevCommit f = commitFile("file2", "new content two", "topic"); + + checkoutBranch("refs/heads/master"); + writeTrashFile("fileg", "fileg"); + if (testConflict) + writeTrashFile("conflict", "g"); + git.add().addFilepattern(".").call(); + RevCommit g = git.commit().setMessage("commit g").call(); + + checkoutBranch("refs/heads/topic"); + RebaseResult res = git.rebase().setUpstream("refs/heads/master") + .setPreserveMerges(true).call(); + if (testConflict) { + assertEquals(Status.STOPPED, res.getStatus()); + assertEquals(Collections.singleton("conflict"), git.status().call() + .getConflicting()); + // resolve + writeTrashFile("conflict", "e"); + git.add().addFilepattern("conflict").call(); + res = git.rebase().setOperation(Operation.CONTINUE).call(); + } + assertEquals(Status.OK, res.getStatus()); - checkoutBranch("refs/heads/topic"); - RebaseResult res = git.rebase().setUpstream("refs/heads/master") - .setPreserveMerges(true).call(); - if (testConflict) { - assertEquals(Status.STOPPED, res.getStatus()); - assertEquals(Collections.singleton("conflict"), git.status().call() - .getConflicting()); - // resolve - writeTrashFile("conflict", "e"); - git.add().addFilepattern("conflict").call(); - res = git.rebase().setOperation(Operation.CONTINUE).call(); + assertEquals("merge resolution", read(FILE1)); + assertEquals("new content two", read("file2")); + assertEquals("more changess", read("file3")); + assertEquals("fileg", read("fileg")); + + rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); + RevCommit newF = rw.next(); + assertDerivedFrom(newF, f); + RevCommit newD = rw.next(); + assertDerivedFrom(newD, d); + assertEquals(2, newD.getParentCount()); + RevCommit newC = rw.next(); + assertDerivedFrom(newC, c); + RevCommit newE = rw.next(); + assertEquals(e, newE); + assertEquals(newC, newD.getParent(0)); + assertEquals(e, newD.getParent(1)); + assertEquals(g, rw.next()); + assertEquals(b, rw.next()); + assertEquals(a, rw.next()); } - assertEquals(Status.OK, res.getStatus()); - - assertEquals("merge resolution", read(FILE1)); - assertEquals("new content two", read("file2")); - assertEquals("more changess", read("file3")); - assertEquals("fileg", read("fileg")); - - rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic"))); - RevCommit newF = rw.next(); - assertDerivedFrom(newF, f); - RevCommit newD = rw.next(); - assertDerivedFrom(newD, d); - assertEquals(2, newD.getParentCount()); - RevCommit newC = rw.next(); - assertDerivedFrom(newC, c); - RevCommit newE = rw.next(); - assertEquals(e, newE); - assertEquals(newC, newD.getParent(0)); - assertEquals(e, newD.getParent(1)); - assertEquals(g, rw.next()); - assertEquals(b, rw.next()); - assertEquals(a, rw.next()); } @Test @@ -687,8 +692,10 @@ checkFile(theFile, "1master\n2\n3\ntopic\n"); // our old branch should be checked out again assertEquals("refs/heads/topic", db.getFullBranch()); - assertEquals(lastMasterChange, new RevWalk(db).parseCommit( - db.resolve(Constants.HEAD)).getParent(0)); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(lastMasterChange, rw.parseCommit( + db.resolve(Constants.HEAD)).getParent(0)); + } assertEquals(origHead, db.readOrigHead()); List headLog = db.getReflogReader(Constants.HEAD) .getReverseEntries(); @@ -737,8 +744,10 @@ RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); assertEquals(Status.OK, res.getStatus()); checkFile(theFile, "1master\n2\n3\ntopic\n"); - assertEquals(lastMasterChange, new RevWalk(db).parseCommit( - db.resolve(Constants.HEAD)).getParent(0)); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(lastMasterChange, rw.parseCommit( + db.resolve(Constants.HEAD)).getParent(0)); + } List headLog = db.getReflogReader(Constants.HEAD) .getReverseEntries(); @@ -785,8 +794,10 @@ // our old branch should be checked out again assertEquals("refs/heads/file3", db.getFullBranch()); - assertEquals(addFile2, new RevWalk(db).parseCommit( - db.resolve(Constants.HEAD)).getParent(0)); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(addFile2, rw.parseCommit( + db.resolve(Constants.HEAD)).getParent(0)); + } checkoutBranch("refs/heads/file2"); assertTrue(new File(db.getWorkTree(), FILE1).exists()); @@ -846,9 +857,10 @@ assertEquals(res.getStatus(), Status.ABORTED); assertEquals("refs/heads/topic", db.getFullBranch()); checkFile(FILE1, "1topic", "2", "3", "topic4"); - RevWalk rw = new RevWalk(db); - assertEquals(lastTopicCommit, rw - .parseCommit(db.resolve(Constants.HEAD))); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(lastTopicCommit, + rw.parseCommit(db.resolve(Constants.HEAD))); + } assertEquals(RepositoryState.SAFE, db.getRepositoryState()); // rebase- dir in .git must be deleted @@ -909,9 +921,10 @@ assertEquals(res.getStatus(), Status.ABORTED); assertEquals(lastTopicCommit.getName(), db.getFullBranch()); checkFile(FILE1, "1topic", "2", "3", "topic4"); - RevWalk rw = new RevWalk(db); - assertEquals(lastTopicCommit, - rw.parseCommit(db.resolve(Constants.HEAD))); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(lastTopicCommit, + rw.parseCommit(db.resolve(Constants.HEAD))); + } assertEquals(RepositoryState.SAFE, db.getRepositoryState()); // rebase- dir in .git must be deleted @@ -966,11 +979,12 @@ assertEquals(RepositoryState.SAFE, db.getRepositoryState()); ObjectId headId = db.resolve(Constants.HEAD); - RevWalk rw = new RevWalk(db); - RevCommit rc = rw.parseCommit(headId); - RevCommit parent = rw.parseCommit(rc.getParent(0)); - assertEquals("change file1 in topic\n\nThis is conflicting", parent - .getFullMessage()); + try (RevWalk rw = new RevWalk(db)) { + RevCommit rc = rw.parseCommit(headId); + RevCommit parent = rw.parseCommit(rc.getParent(0)); + assertEquals("change file1 in topic\n\nThis is conflicting", parent + .getFullMessage()); + } } @Test @@ -1017,9 +1031,10 @@ git.rebase().setOperation(Operation.SKIP).call(); ObjectId headId = db.resolve(Constants.HEAD); - RevWalk rw = new RevWalk(db); - RevCommit rc = rw.parseCommit(headId); - assertEquals("change file1 in master", rc.getFullMessage()); + try (RevWalk rw = new RevWalk(db)) { + RevCommit rc = rw.parseCommit(headId); + assertEquals("change file1 in master", rc.getFullMessage()); + } } @Test @@ -1308,10 +1323,11 @@ git.rebase().setOperation(Operation.SKIP).call(); ObjectId headId = db.resolve(Constants.HEAD); - RevWalk rw = new RevWalk(db); - RevCommit rc = rw.parseCommit(headId); - RevCommit parent = rw.parseCommit(rc.getParent(0)); - assertEquals("A different commit message", parent.getFullMessage()); + try (RevWalk rw = new RevWalk(db)) { + RevCommit rc = rw.parseCommit(headId); + RevCommit parent = rw.parseCommit(rc.getParent(0)); + assertEquals("A different commit message", parent.getFullMessage()); + } } private RevCommit writeFileAndCommit(String fileName, String commitMessage, @@ -1420,9 +1436,10 @@ res = git.rebase().setOperation(Operation.ABORT).call(); assertEquals(res.getStatus(), Status.ABORTED); assertEquals("refs/heads/topic", db.getFullBranch()); - RevWalk rw = new RevWalk(db); - assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD))); - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + try (RevWalk rw = new RevWalk(db)) { + assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD))); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } // rebase- dir in .git must be deleted assertFalse(new File(db.getDirectory(), "rebase-merge").exists()); @@ -1447,7 +1464,7 @@ assertEquals("GIT_AUTHOR_DATE='@123456789 -0100'", lines[2]); PersonIdent parsedIdent = git.rebase().parseAuthor( - convertedAuthor.getBytes("UTF-8")); + convertedAuthor.getBytes(UTF_8)); assertEquals(ident.getName(), parsedIdent.getName()); assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress()); // this is rounded to the last second @@ -1464,7 +1481,7 @@ assertEquals("GIT_AUTHOR_DATE='@123456789 +0930'", lines[2]); parsedIdent = git.rebase().parseAuthor( - convertedAuthor.getBytes("UTF-8")); + convertedAuthor.getBytes(UTF_8)); assertEquals(ident.getName(), parsedIdent.getName()); assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress()); assertEquals(123456789000L, parsedIdent.getWhen().getTime()); @@ -2086,9 +2103,8 @@ private int countPicks() throws IOException { int count = 0; File todoFile = getTodoFile(); - BufferedReader br = new BufferedReader(new InputStreamReader( - new FileInputStream(todoFile), "UTF-8")); - try { + try (BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(todoFile), UTF_8))) { String line = br.readLine(); while (line != null) { int firstBlank = line.indexOf(' '); @@ -2106,8 +2122,6 @@ line = br.readLine(); } return count; - } finally { - br.close(); } } @@ -2253,11 +2267,13 @@ RebaseResult res = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { steps.add(0, new RebaseTodoLine( "# Comment that should not be processed")); } + @Override public String modifyCommitMessage(String commit) { fail("modifyCommitMessage() was not expected to be called"); return commit; @@ -2268,6 +2284,7 @@ RebaseResult res2 = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { // delete RevCommit c4 @@ -2277,6 +2294,7 @@ } } + @Override public String modifyCommitMessage(String commit) { fail("modifyCommitMessage() was not expected to be called"); return commit; @@ -2286,14 +2304,15 @@ assertEquals(RebaseResult.Status.OK, res2.getStatus()); ObjectId headId = db.resolve(Constants.HEAD); - RevWalk rw = new RevWalk(db); - RevCommit rc = rw.parseCommit(headId); + try (RevWalk rw = new RevWalk(db)) { + RevCommit rc = rw.parseCommit(headId); - ObjectId head1Id = db.resolve(Constants.HEAD + "~1"); - RevCommit rc1 = rw.parseCommit(head1Id); + ObjectId head1Id = db.resolve(Constants.HEAD + "~1"); + RevCommit rc1 = rw.parseCommit(head1Id); - assertEquals(rc.getFullMessage(), c4.getFullMessage()); - assertEquals(rc1.getFullMessage(), c2.getFullMessage()); + assertEquals(rc.getFullMessage(), c4.getFullMessage()); + assertEquals(rc1.getFullMessage(), c2.getFullMessage()); + } } @Test @@ -2497,6 +2516,7 @@ RebaseResult res = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.REWORD); @@ -2505,6 +2525,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "rewritten commit message"; } @@ -2543,6 +2564,7 @@ RebaseResult res = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.EDIT); @@ -2551,6 +2573,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return ""; // not used } @@ -2607,6 +2630,7 @@ git.rebase().setUpstream("HEAD~3") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(1).setAction(Action.SQUASH); @@ -2615,6 +2639,7 @@ } } + @Override public String modifyCommitMessage(String commit) { final File messageSquashFile = new File(db .getDirectory(), "rebase-merge/message-squash"); @@ -2643,15 +2668,16 @@ } }).call(); - RevWalk walk = new RevWalk(db); - ObjectId headId = db.resolve(Constants.HEAD); - RevCommit headCommit = walk.parseCommit(headId); - assertEquals(headCommit.getFullMessage(), - "update file2 on master\nnew line"); - - ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); - RevCommit head1Commit = walk.parseCommit(head2Id); - assertEquals("changed", head1Commit.getFullMessage()); + try (RevWalk walk = new RevWalk(db)) { + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals(headCommit.getFullMessage(), + "update file2 on master\nnew line"); + + ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); + RevCommit head1Commit = walk.parseCommit(head2Id); + assertEquals("changed", head1Commit.getFullMessage()); + } } @Test @@ -2686,6 +2712,7 @@ git.rebase().setUpstream("HEAD~4") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(1).setAction(Action.SQUASH); @@ -2695,6 +2722,7 @@ } } + @Override public String modifyCommitMessage(String commit) { final File messageSquashFile = new File(db.getDirectory(), "rebase-merge/message-squash"); @@ -2722,17 +2750,18 @@ } }).call(); - RevWalk walk = new RevWalk(db); - ObjectId headId = db.resolve(Constants.HEAD); - RevCommit headCommit = walk.parseCommit(headId); - assertEquals(headCommit.getFullMessage(), - "update file2 on master\nnew line"); - - ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); - RevCommit head1Commit = walk.parseCommit(head2Id); - assertEquals( - "Add file1\nnew line\nAdd file2\nnew line\nupdated file1 on master\nnew line", - head1Commit.getFullMessage()); + try (RevWalk walk = new RevWalk(db)) { + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals(headCommit.getFullMessage(), + "update file2 on master\nnew line"); + + ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); + RevCommit head1Commit = walk.parseCommit(head2Id); + assertEquals( + "Add file1\nnew line\nAdd file2\nnew line\nupdated file1 on master\nnew line", + head1Commit.getFullMessage()); + } } @Test @@ -2767,6 +2796,7 @@ git.rebase().setUpstream("HEAD~4") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(1).setAction(Action.FIXUP); @@ -2776,6 +2806,7 @@ } } + @Override public String modifyCommitMessage(String commit) { final File messageSquashFile = new File(db .getDirectory(), "rebase-merge/message-squash"); @@ -2804,15 +2835,16 @@ } }).call(); - RevWalk walk = new RevWalk(db); - ObjectId headId = db.resolve(Constants.HEAD); - RevCommit headCommit = walk.parseCommit(headId); - assertEquals(headCommit.getFullMessage(), - "update file2 on master\nnew line"); - - ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); - RevCommit head1Commit = walk.parseCommit(head2Id); - assertEquals("changed", head1Commit.getFullMessage()); + try (RevWalk walk = new RevWalk(db)) { + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals(headCommit.getFullMessage(), + "update file2 on master\nnew line"); + + ObjectId head2Id = db.resolve(Constants.HEAD + "^1"); + RevCommit head1Commit = walk.parseCommit(head2Id); + assertEquals("changed", head1Commit.getFullMessage()); + } } @Test @@ -2841,6 +2873,7 @@ git.rebase().setUpstream("HEAD~3") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(1).setAction(Action.FIXUP); @@ -2849,22 +2882,24 @@ } } + @Override public String modifyCommitMessage(String commit) { fail("No callback to modify commit message expected for single fixup"); return commit; } }).call(); - RevWalk walk = new RevWalk(db); - ObjectId headId = db.resolve(Constants.HEAD); - RevCommit headCommit = walk.parseCommit(headId); - assertEquals("update file2 on master\nnew line", - headCommit.getFullMessage()); - - ObjectId head1Id = db.resolve(Constants.HEAD + "^1"); - RevCommit head1Commit = walk.parseCommit(head1Id); - assertEquals("Add file2\nnew line", - head1Commit.getFullMessage()); + try (RevWalk walk = new RevWalk(db)) { + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals("update file2 on master\nnew line", + headCommit.getFullMessage()); + + ObjectId head1Id = db.resolve(Constants.HEAD + "^1"); + RevCommit head1Commit = walk.parseCommit(head1Id); + assertEquals("Add file2\nnew line", + head1Commit.getFullMessage()); + } } @Test @@ -2889,6 +2924,7 @@ git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(1).setAction(Action.FIXUP); @@ -2897,17 +2933,19 @@ } } + @Override public String modifyCommitMessage(String commit) { fail("No callback to modify commit message expected for single fixup"); return commit; } }).call(); - RevWalk walk = new RevWalk(db); - ObjectId headId = db.resolve(Constants.HEAD); - RevCommit headCommit = walk.parseCommit(headId); - assertEquals("Add file2", - headCommit.getFullMessage()); + try (RevWalk walk = new RevWalk(db)) { + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals("Add file2", + headCommit.getFullMessage()); + } } @Test(expected = InvalidRebaseStepException.class) @@ -2928,6 +2966,7 @@ git.rebase().setUpstream("HEAD~1") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.FIXUP); @@ -2936,6 +2975,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } @@ -2960,6 +3000,7 @@ git.rebase().setUpstream("HEAD~1") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.SQUASH); @@ -2968,6 +3009,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } @@ -2991,6 +3033,7 @@ git.rebase().setUpstream("HEAD~1") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.EDIT); @@ -2999,6 +3042,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } @@ -3033,6 +3077,7 @@ RebaseResult result = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { steps.remove(0); try { @@ -3042,6 +3087,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } @@ -3075,6 +3121,7 @@ RebaseResult result = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { steps.remove(0); try { @@ -3084,6 +3131,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "rewritten commit message"; } @@ -3092,6 +3140,7 @@ git.add().addFilepattern(FILE1).call(); result = git.rebase().runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { steps.remove(0); try { @@ -3101,6 +3150,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "rewritten commit message"; } @@ -3138,6 +3188,7 @@ RebaseResult result = git.rebase().setUpstream("HEAD~3") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.PICK); @@ -3148,6 +3199,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "squashed message"; } @@ -3156,6 +3208,7 @@ git.add().addFilepattern(FILE1).call(); result = git.rebase().runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.PICK); @@ -3166,6 +3219,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "squashed message"; } @@ -3204,6 +3258,7 @@ RebaseResult result = git.rebase().setUpstream("HEAD~3") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.PICK); @@ -3214,6 +3269,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } @@ -3222,6 +3278,7 @@ git.add().addFilepattern(FILE1).call(); result = git.rebase().runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.PICK); @@ -3232,6 +3289,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return "commit"; } @@ -3275,6 +3333,7 @@ RebaseResult result = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { + @Override public void prepareSteps(List steps) { try { steps.get(0).setAction(Action.EDIT); @@ -3284,6 +3343,7 @@ } } + @Override public String modifyCommitMessage(String commit) { return commit; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.Test; + +public class RemoteAddCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testAdd() throws Exception { + // create another repository + Repository remoteRepository = createWorkRepository(); + URIish uri = new URIish( + remoteRepository.getDirectory().toURI().toURL()); + + // execute the command to add a new remote + RemoteAddCommand cmd = Git.wrap(db).remoteAdd(); + cmd.setName(REMOTE_NAME); + cmd.setUri(uri); + RemoteConfig remote = cmd.call(); + + // assert that the added remote represents the remote repository + assertEquals(REMOTE_NAME, remote.getName()); + assertArrayEquals(new URIish[] { uri }, remote.getURIs().toArray()); + assertEquals(1, remote.getFetchRefSpecs().size()); + assertEquals( + String.format("+refs/heads/*:refs/remotes/%s/*", REMOTE_NAME), + remote.getFetchRefSpecs().get(0).toString()); + + // assert that the added remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.junit.Test; + +public class RemoteDeleteCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testDelete() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to remove the remote + RemoteRemoveCommand cmd = Git.wrap(db).remoteRemove(); + cmd.setName(REMOTE_NAME); + RemoteConfig remote = cmd.call(); + + // assert that the removed remote is the initial remote + assertRemoteConfigEquals(remoteConfig, remote); + // assert that there are no remotes left + assertTrue(RemoteConfig.getAllRemoteConfigs(db.getConfig()).isEmpty()); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.junit.Test; + +public class RemoteListCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testList() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to list the remotes + List remotes = Git.wrap(db).remoteList().call(); + + // assert that there is only one remote + assertEquals(1, remotes.size()); + // assert that the available remote is the initial remote + assertRemoteConfigEquals(remoteConfig, remotes.get(0)); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.Test; + +public class RemoteSetUrlCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testSetUrl() throws Exception { + // setup an initial remote + setupRemote(); + + // execute the command to change the fetch url + RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl(); + cmd.setName(REMOTE_NAME); + URIish newUri = new URIish("git://test.com/test"); + cmd.setUri(newUri); + RemoteConfig remote = cmd.call(); + + // assert that the changed remote has the new fetch url + assertEquals(REMOTE_NAME, remote.getName()); + assertArrayEquals(new URIish[] { newUri }, remote.getURIs().toArray()); + + // assert that the changed remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + + @Test + public void testSetPushUrl() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to change the push url + RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl(); + cmd.setName(REMOTE_NAME); + URIish newUri = new URIish("git://test.com/test"); + cmd.setUri(newUri); + cmd.setPush(true); + RemoteConfig remote = cmd.call(); + + // assert that the changed remote has the old fetch url and the new push + // url + assertEquals(REMOTE_NAME, remote.getName()); + assertEquals(remoteConfig.getURIs(), remote.getURIs()); + assertArrayEquals(new URIish[] { newUri }, + remote.getPushURIs().toArray()); + + // assert that the changed remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,14 @@ package org.eclipse.jgit.api; 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.assertTrue; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.StoredConfig; @@ -66,6 +69,7 @@ private Git git; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -98,32 +102,40 @@ @Test public void renameBranchSingleConfigValue() throws Exception { StoredConfig config = git.getRepository().getConfig(); - config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true); + config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE); config.save(); String branch = "b1"; - assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true)); - assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - branch, ConfigConstants.CONFIG_KEY_REBASE, false)); + assertEquals(BranchRebaseMode.REBASE, + config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); + assertNull(config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, branch, + ConfigConstants.CONFIG_KEY_REBASE, null)); assertNotNull(git.branchRename().setNewName(branch).call()); config = git.getRepository().getConfig(); - assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, false)); - assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - branch, ConfigConstants.CONFIG_KEY_REBASE, false)); + assertNull(config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, null)); + assertEquals(BranchRebaseMode.REBASE, + config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, branch, + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); } @Test public void renameBranchExistingSection() throws Exception { String branch = "b1"; StoredConfig config = git.getRepository().getConfig(); - config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true); + config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE); config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, "a", "a"); config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, branch, "a", @@ -140,18 +152,22 @@ @Test public void renameBranchMultipleConfigValues() throws Exception { StoredConfig config = git.getRepository().getConfig(); - config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true); + config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE); config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, true); config.save(); String branch = "b1"; - assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true)); - assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - branch, ConfigConstants.CONFIG_KEY_REBASE, false)); + assertEquals(BranchRebaseMode.REBASE, + config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); + assertNull(config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, branch, + ConfigConstants.CONFIG_KEY_REBASE, null)); assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, true)); assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, @@ -160,10 +176,14 @@ assertNotNull(git.branchRename().setNewName(branch).call()); config = git.getRepository().getConfig(); - assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, false)); - assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, - branch, ConfigConstants.CONFIG_KEY_REBASE, false)); + assertNull(config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, + ConfigConstants.CONFIG_KEY_REBASE, null)); + assertEquals(BranchRebaseMode.REBASE, + config.getEnum(BranchRebaseMode.values(), + ConfigConstants.CONFIG_BRANCH_SECTION, branch, + ConfigConstants.CONFIG_KEY_REBASE, + BranchRebaseMode.NONE)); assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, false)); assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,6 +65,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; @@ -139,8 +140,8 @@ AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.HARD).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.HARD) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -156,6 +157,56 @@ } @Test + public void testHardResetReflogDisabled() throws Exception { + setupRepository(); + ObjectId prevHead = db.resolve(Constants.HEAD); + ResetCommand reset = git.reset(); + assertSameAsHead(reset.setMode(ResetType.HARD) + .setRef(initialCommit.getName()).disableRefLog(true).call()); + assertTrue("reflog should be disabled", reset.isReflogDisabled()); + // check if HEAD points to initial commit now + ObjectId head = db.resolve(Constants.HEAD); + assertEquals(initialCommit, head); + // check if files were removed + assertFalse(indexFile.exists()); + assertTrue(untrackedFile.exists()); + // fileInIndex must no longer be in HEAD and in the index + String fileInIndexPath = indexFile.getAbsolutePath(); + assertFalse(inHead(fileInIndexPath)); + assertFalse(inIndex(indexFile.getName())); + assertReflogDisabled(head); + assertEquals(prevHead, db.readOrigHead()); + } + + @Test + public void testHardResetWithConflicts_DoOverWriteUntrackedFile() + throws JGitInternalException, + AmbiguousObjectException, IOException, GitAPIException { + setupRepository(); + git.rm().setCached(true).addFilepattern("a.txt").call(); + assertTrue(new File(db.getWorkTree(), "a.txt").exists()); + git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD) + .call(); + assertTrue(new File(db.getWorkTree(), "a.txt").exists()); + assertEquals("content", read(new File(db.getWorkTree(), "a.txt"))); + } + + @Test + public void testHardResetWithConflicts_DoDeleteFileFolderConflicts() + throws JGitInternalException, + AmbiguousObjectException, IOException, GitAPIException { + setupRepository(); + writeTrashFile("d/c.txt", "x"); + git.add().addFilepattern("d/c.txt").call(); + FileUtils.delete(new File(db.getWorkTree(), "d"), FileUtils.RECURSIVE); + writeTrashFile("d", "y"); + + git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD) + .call(); + assertFalse(new File(db.getWorkTree(), "d").exists()); + } + + @Test public void testResetToNonexistingHEAD() throws JGitInternalException, AmbiguousObjectException, IOException, GitAPIException { @@ -176,8 +227,8 @@ AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.SOFT).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.SOFT) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -197,8 +248,8 @@ AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.MIXED).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -241,7 +292,8 @@ assertTrue(bEntry.getLength() > 0); assertTrue(bEntry.getLastModified() > 0); - git.reset().setMode(ResetType.MIXED).setRef(commit2.getName()).call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED) + .setRef(commit2.getName()).call()); cache = db.readDirCache(); @@ -280,7 +332,7 @@ + "[a.txt, mode:100644, stage:3]", indexState(0)); - git.reset().setMode(ResetType.MIXED).call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED).call()); assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]", indexState(0)); @@ -298,8 +350,8 @@ // 'a.txt' has already been modified in setupRepository // 'notAddedToIndex.txt' has been added to repository - git.reset().addPath(indexFile.getName()) - .addPath(untrackedFile.getName()).call(); + assertSameAsHead(git.reset().addPath(indexFile.getName()) + .addPath(untrackedFile.getName()).call()); DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS()) .getEntry(indexFile.getName()); @@ -329,7 +381,7 @@ git.add().addFilepattern(untrackedFile.getName()).call(); // 'dir/b.txt' has already been modified in setupRepository - git.reset().addPath("dir").call(); + assertSameAsHead(git.reset().addPath("dir").call()); DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS()) .getEntry("dir/b.txt"); @@ -358,9 +410,9 @@ // 'a.txt' has already been modified in setupRepository // 'notAddedToIndex.txt' has been added to repository // reset to the inital commit - git.reset().setRef(initialCommit.getName()) - .addPath(indexFile.getName()) - .addPath(untrackedFile.getName()).call(); + assertSameAsHead(git.reset().setRef(initialCommit.getName()) + .addPath(indexFile.getName()).addPath(untrackedFile.getName()) + .call()); // check that HEAD hasn't moved ObjectId head = db.resolve(Constants.HEAD); @@ -397,7 +449,7 @@ + "[b.txt, mode:100644]", indexState(0)); - git.reset().addPath(file).call(); + assertSameAsHead(git.reset().addPath(file).call()); assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]", indexState(0)); @@ -409,7 +461,7 @@ writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); // Should assume an empty tree, like in C Git 1.8.2 - git.reset().addPath("a.txt").call(); + assertSameAsHead(git.reset().addPath("a.txt").call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -421,7 +473,8 @@ git = new Git(db); writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); - git.reset().setRef("doesnotexist").addPath("a.txt").call(); + assertSameAsHead( + git.reset().setRef("doesnotexist").addPath("a.txt").call()); } @Test @@ -431,7 +484,7 @@ git.add().addFilepattern("a.txt").call(); writeTrashFile("a.txt", "modified"); // should use default mode MIXED - git.reset().call(); + assertSameAsHead(git.reset().call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -452,7 +505,7 @@ git.add().addFilepattern(untrackedFile.getName()).call(); - git.reset().setRef(tagName).setMode(HARD).call(); + assertSameAsHead(git.reset().setRef(tagName).setMode(HARD).call()); ObjectId head = db.resolve(Constants.HEAD); assertEquals(secondCommit, head); @@ -460,31 +513,34 @@ @Test public void testHardResetAfterSquashMerge() throws Exception { - Git g = new Git(db); + git = new Git(db); writeTrashFile("file1", "file1"); - g.add().addFilepattern("file1").call(); - RevCommit first = g.commit().setMessage("initial commit").call(); + git.add().addFilepattern("file1").call(); + RevCommit first = git.commit().setMessage("initial commit").call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); createBranch(first, "refs/heads/branch1"); checkoutBranch("refs/heads/branch1"); writeTrashFile("file2", "file2"); - g.add().addFilepattern("file2").call(); - g.commit().setMessage("second commit").call(); + git.add().addFilepattern("file2").call(); + git.commit().setMessage("second commit").call(); assertTrue(new File(db.getWorkTree(), "file2").exists()); checkoutBranch("refs/heads/master"); - MergeResult result = g.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, result.getMergeStatus()); assertNotNull(db.readSquashCommitMsg()); - g.reset().setMode(ResetType.HARD).setRef(first.getName()).call(); + assertSameAsHead(git.reset().setMode(ResetType.HARD) + .setRef(first.getName()).call()); assertNull(db.readSquashCommitMsg()); } @@ -495,7 +551,7 @@ File fileA = writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); // Should assume an empty tree, like in C Git 1.8.2 - git.reset().setMode(ResetType.HARD).call(); + assertSameAsHead(git.reset().setMode(ResetType.HARD).call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -528,6 +584,24 @@ .getName()); } + private void assertReflogDisabled(ObjectId head) + throws IOException { + // Check the reflog for HEAD + String actualHeadMessage = db.getReflogReader(Constants.HEAD) + .getLastEntry().getComment(); + String expectedHeadMessage = "commit: adding a.txt and dir/b.txt"; + assertEquals(expectedHeadMessage, actualHeadMessage); + assertEquals(head.getName(), db.getReflogReader(Constants.HEAD) + .getLastEntry().getOldId().getName()); + + // The reflog for master contains the same as the one for HEAD + String actualMasterMessage = db.getReflogReader("refs/heads/master") + .getLastEntry().getComment(); + String expectedMasterMessage = "commit: adding a.txt and dir/b.txt"; + assertEquals(expectedMasterMessage, actualMasterMessage); + assertEquals(head.getName(), db.getReflogReader(Constants.HEAD) + .getLastEntry().getOldId().getName()); + } /** * Checks if a file with the given path exists in the HEAD tree * @@ -556,4 +630,14 @@ return dc.getEntry(path) != null; } + /** + * Asserts that a certain ref is similar to repos HEAD. + * @param ref + * @throws IOException + */ + private void assertSameAsHead(Ref ref) throws IOException { + Ref headRef = db.exactRef(Constants.HEAD); + assertEquals(headRef.getName(), ref.getName()); + assertEquals(headRef.getObjectId(), ref.getObjectId()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -74,303 +74,309 @@ @Test public void testRevert() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - writeTrashFile("a", "first line\nsec. line\nthird line\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("create a").call(); - - writeTrashFile("b", "content\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("create b").call(); - - writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("enlarged a").call(); - - writeTrashFile("a", - "first line\nsecond line\nthird line\nfourth line\n"); - git.add().addFilepattern("a").call(); - RevCommit fixingA = git.commit().setMessage("fixed a").call(); - - writeTrashFile("b", "first line\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("fixed b").call(); - - git.revert().include(fixingA).call(); - - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); - - assertTrue(new File(db.getWorkTree(), "b").exists()); - checkFile(new File(db.getWorkTree(), "a"), - "first line\nsec. line\nthird line\nfourth line\n"); - Iterator history = git.log().call().iterator(); - RevCommit revertCommit = history.next(); - String expectedMessage = "Revert \"fixed a\"\n\n" - + "This reverts commit " + fixingA.getId().getName() + ".\n"; - assertEquals(expectedMessage, revertCommit.getFullMessage()); - assertEquals("fixed b", history.next().getFullMessage()); - assertEquals("fixed a", history.next().getFullMessage()); - assertEquals("enlarged a", history.next().getFullMessage()); - assertEquals("create b", history.next().getFullMessage()); - assertEquals("create a", history.next().getFullMessage()); - assertFalse(history.hasNext()); - - ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); - reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); + try (Git git = new Git(db)) { + writeTrashFile("a", "first line\nsec. line\nthird line\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("create a").call(); + + writeTrashFile("b", "content\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("create b").call(); + + writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("enlarged a").call(); + + writeTrashFile("a", + "first line\nsecond line\nthird line\nfourth line\n"); + git.add().addFilepattern("a").call(); + RevCommit fixingA = git.commit().setMessage("fixed a").call(); + + writeTrashFile("b", "first line\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("fixed b").call(); + + git.revert().include(fixingA).call(); + + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + + assertTrue(new File(db.getWorkTree(), "b").exists()); + checkFile(new File(db.getWorkTree(), "a"), + "first line\nsec. line\nthird line\nfourth line\n"); + Iterator history = git.log().call().iterator(); + RevCommit revertCommit = history.next(); + String expectedMessage = "Revert \"fixed a\"\n\n" + + "This reverts commit " + fixingA.getId().getName() + ".\n"; + assertEquals(expectedMessage, revertCommit.getFullMessage()); + assertEquals("fixed b", history.next().getFullMessage()); + assertEquals("fixed a", history.next().getFullMessage()); + assertEquals("enlarged a", history.next().getFullMessage()); + assertEquals("create b", history.next().getFullMessage()); + assertEquals("create a", history.next().getFullMessage()); + assertFalse(history.hasNext()); + + ReflogReader reader = db.getReflogReader(Constants.HEAD); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + reader = db.getReflogReader(db.getBranch()); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + } } @Test public void testRevertMultiple() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - writeTrashFile("a", "first\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add first").call(); - - writeTrashFile("a", "first\nsecond\n"); - git.add().addFilepattern("a").call(); - RevCommit secondCommit = git.commit().setMessage("add second").call(); - - writeTrashFile("a", "first\nsecond\nthird\n"); - git.add().addFilepattern("a").call(); - RevCommit thirdCommit = git.commit().setMessage("add third").call(); - - git.revert().include(thirdCommit).include(secondCommit).call(); - - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); - - checkFile(new File(db.getWorkTree(), "a"), "first\n"); - Iterator history = git.log().call().iterator(); - RevCommit revertCommit = history.next(); - String expectedMessage = "Revert \"add second\"\n\n" - + "This reverts commit " - + secondCommit.getId().getName() + ".\n"; - assertEquals(expectedMessage, revertCommit.getFullMessage()); - revertCommit = history.next(); - expectedMessage = "Revert \"add third\"\n\n" - + "This reverts commit " + thirdCommit.getId().getName() - + ".\n"; - assertEquals(expectedMessage, revertCommit.getFullMessage()); - assertEquals("add third", history.next().getFullMessage()); - assertEquals("add second", history.next().getFullMessage()); - assertEquals("add first", history.next().getFullMessage()); - assertFalse(history.hasNext()); - - ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); - reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); + try (Git git = new Git(db)) { + writeTrashFile("a", "first\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add first").call(); + + writeTrashFile("a", "first\nsecond\n"); + git.add().addFilepattern("a").call(); + RevCommit secondCommit = git.commit().setMessage("add second").call(); + + writeTrashFile("a", "first\nsecond\nthird\n"); + git.add().addFilepattern("a").call(); + RevCommit thirdCommit = git.commit().setMessage("add third").call(); + + git.revert().include(thirdCommit).include(secondCommit).call(); + + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + + checkFile(new File(db.getWorkTree(), "a"), "first\n"); + Iterator history = git.log().call().iterator(); + RevCommit revertCommit = history.next(); + String expectedMessage = "Revert \"add second\"\n\n" + + "This reverts commit " + + secondCommit.getId().getName() + ".\n"; + assertEquals(expectedMessage, revertCommit.getFullMessage()); + revertCommit = history.next(); + expectedMessage = "Revert \"add third\"\n\n" + + "This reverts commit " + thirdCommit.getId().getName() + + ".\n"; + assertEquals(expectedMessage, revertCommit.getFullMessage()); + assertEquals("add third", history.next().getFullMessage()); + assertEquals("add second", history.next().getFullMessage()); + assertEquals("add first", history.next().getFullMessage()); + assertFalse(history.hasNext()); + + ReflogReader reader = db.getReflogReader(Constants.HEAD); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + reader = db.getReflogReader(db.getBranch()); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + } } @Test public void testRevertMultipleWithFail() throws IOException, JGitInternalException, GitAPIException { - Git git = new Git(db); - - writeTrashFile("a", "first\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add first").call(); - - writeTrashFile("a", "first\nsecond\n"); - git.add().addFilepattern("a").call(); - RevCommit secondCommit = git.commit().setMessage("add second").call(); - - writeTrashFile("a", "first\nsecond\nthird\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add third").call(); - - writeTrashFile("a", "first\nsecond\nthird\nfourth\n"); - git.add().addFilepattern("a").call(); - RevCommit fourthCommit = git.commit().setMessage("add fourth").call(); - - git.revert().include(fourthCommit).include(secondCommit).call(); - - // not SAFE because it failed - assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); - - checkFile(new File(db.getWorkTree(), "a"), "first\n" - + "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n" - + ">>>>>>> " + secondCommit.getId().abbreviate(7).name() - + " add second\n"); - Iterator history = git.log().call().iterator(); - RevCommit revertCommit = history.next(); - String expectedMessage = "Revert \"add fourth\"\n\n" - + "This reverts commit " + fourthCommit.getId().getName() - + ".\n"; - assertEquals(expectedMessage, revertCommit.getFullMessage()); - assertEquals("add fourth", history.next().getFullMessage()); - assertEquals("add third", history.next().getFullMessage()); - assertEquals("add second", history.next().getFullMessage()); - assertEquals("add first", history.next().getFullMessage()); - assertFalse(history.hasNext()); - - ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); - reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment() - .startsWith("revert: Revert \"")); + try (Git git = new Git(db)) { + writeTrashFile("a", "first\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add first").call(); + + writeTrashFile("a", "first\nsecond\n"); + git.add().addFilepattern("a").call(); + RevCommit secondCommit = git.commit().setMessage("add second").call(); + + writeTrashFile("a", "first\nsecond\nthird\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add third").call(); + + writeTrashFile("a", "first\nsecond\nthird\nfourth\n"); + git.add().addFilepattern("a").call(); + RevCommit fourthCommit = git.commit().setMessage("add fourth").call(); + + git.revert().include(fourthCommit).include(secondCommit).call(); + + // not SAFE because it failed + assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); + + checkFile(new File(db.getWorkTree(), "a"), "first\n" + + "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n" + + ">>>>>>> " + secondCommit.getId().abbreviate(7).name() + + " add second\n"); + Iterator history = git.log().call().iterator(); + RevCommit revertCommit = history.next(); + String expectedMessage = "Revert \"add fourth\"\n\n" + + "This reverts commit " + fourthCommit.getId().getName() + + ".\n"; + assertEquals(expectedMessage, revertCommit.getFullMessage()); + assertEquals("add fourth", history.next().getFullMessage()); + assertEquals("add third", history.next().getFullMessage()); + assertEquals("add second", history.next().getFullMessage()); + assertEquals("add first", history.next().getFullMessage()); + assertFalse(history.hasNext()); + + ReflogReader reader = db.getReflogReader(Constants.HEAD); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + reader = db.getReflogReader(db.getBranch()); + assertTrue(reader.getLastEntry().getComment() + .startsWith("revert: Revert \"")); + } } @Test public void testRevertDirtyIndex() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareRevert(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - // modify and add file a - writeTrashFile("a", "a(modified)"); - git.add().addFilepattern("a").call(); - // do not commit - - doRevertAndCheckResult(git, sideCommit, - MergeFailureReason.DIRTY_INDEX); + // modify and add file a + writeTrashFile("a", "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit + + doRevertAndCheckResult(git, sideCommit, + MergeFailureReason.DIRTY_INDEX); + } } @Test public void testRevertDirtyWorktree() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareRevert(git); - - // modify file a - writeTrashFile("a", "a(modified)"); - // do not add and commit + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - doRevertAndCheckResult(git, sideCommit, - MergeFailureReason.DIRTY_WORKTREE); + // modify file a + writeTrashFile("a", "a(modified)"); + // do not add and commit + + doRevertAndCheckResult(git, sideCommit, + MergeFailureReason.DIRTY_WORKTREE); + } } @Test public void testRevertConflictResolution() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareRevert(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - RevertCommand revert = git.revert(); - RevCommit newHead = revert.include(sideCommit.getId()).call(); - assertNull(newHead); - MergeResult result = revert.getFailingResult(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists()); - assertEquals("Revert \"" + sideCommit.getShortMessage() - + "\"\n\nThis reverts commit " + sideCommit.getId().getName() - + ".\n\nConflicts:\n\ta\n", - db.readMergeCommitMsg()); - assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD) - .exists()); - assertEquals(sideCommit.getId(), db.readRevertHead()); - assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); - - // Resolve - writeTrashFile("a", "a"); - git.add().addFilepattern("a").call(); + RevertCommand revert = git.revert(); + RevCommit newHead = revert.include(sideCommit.getId()).call(); + assertNull(newHead); + MergeResult result = revert.getFailingResult(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists()); + assertEquals("Revert \"" + sideCommit.getShortMessage() + + "\"\n\nThis reverts commit " + sideCommit.getId().getName() + + ".\n\nConflicts:\n\ta\n", + db.readMergeCommitMsg()); + assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD) + .exists()); + assertEquals(sideCommit.getId(), db.readRevertHead()); + assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); + + // Resolve + writeTrashFile("a", "a"); + git.add().addFilepattern("a").call(); - assertEquals(RepositoryState.REVERTING_RESOLVED, - db.getRepositoryState()); + assertEquals(RepositoryState.REVERTING_RESOLVED, + db.getRepositoryState()); - git.commit().setOnly("a").setMessage("resolve").call(); + git.commit().setOnly("a").setMessage("resolve").call(); - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } } @Test public void testRevertkConflictReset() throws Exception { - Git git = new Git(db); - - RevCommit sideCommit = prepareRevert(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - RevertCommand revert = git.revert(); - RevCommit newHead = revert.include(sideCommit.getId()).call(); - assertNull(newHead); - MergeResult result = revert.getFailingResult(); - - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); - assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD) - .exists()); - - git.reset().setMode(ResetType.MIXED).setRef("HEAD").call(); - - assertEquals(RepositoryState.SAFE, db.getRepositoryState()); - assertFalse(new File(db.getDirectory(), Constants.REVERT_HEAD) - .exists()); + RevertCommand revert = git.revert(); + RevCommit newHead = revert.include(sideCommit.getId()).call(); + assertNull(newHead); + MergeResult result = revert.getFailingResult(); + + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + assertEquals(RepositoryState.REVERTING, db.getRepositoryState()); + assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD) + .exists()); + + git.reset().setMode(ResetType.MIXED).setRef("HEAD").call(); + + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + assertFalse(new File(db.getDirectory(), Constants.REVERT_HEAD) + .exists()); + } } @Test public void testRevertOverExecutableChangeOnNonExectuableFileSystem() throws Exception { - Git git = new Git(db); - File file = writeTrashFile("test.txt", "a"); - assertNotNull(git.add().addFilepattern("test.txt").call()); - assertNotNull(git.commit().setMessage("commit1").call()); - - assertNotNull(git.checkout().setCreateBranch(true).setName("a").call()); - - writeTrashFile("test.txt", "b"); - assertNotNull(git.add().addFilepattern("test.txt").call()); - RevCommit commit2 = git.commit().setMessage("commit2").call(); - assertNotNull(commit2); - - assertNotNull(git.checkout().setName(Constants.MASTER).call()); - - DirCache cache = db.lockDirCache(); - cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE); - cache.write(); - assertTrue(cache.commit()); - cache.unlock(); - - assertNotNull(git.commit().setMessage("commit3").call()); - - db.getFS().setExecute(file, false); - git.getRepository() - .getConfig() - .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_FILEMODE, false); - - RevertCommand revert = git.revert(); - RevCommit newHead = revert.include(commit2).call(); - assertNotNull(newHead); + try (Git git = new Git(db)) { + File file = writeTrashFile("test.txt", "a"); + assertNotNull(git.add().addFilepattern("test.txt").call()); + assertNotNull(git.commit().setMessage("commit1").call()); + + assertNotNull(git.checkout().setCreateBranch(true).setName("a").call()); + + writeTrashFile("test.txt", "b"); + assertNotNull(git.add().addFilepattern("test.txt").call()); + RevCommit commit2 = git.commit().setMessage("commit2").call(); + assertNotNull(commit2); + + assertNotNull(git.checkout().setName(Constants.MASTER).call()); + + DirCache cache = db.lockDirCache(); + cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE); + cache.write(); + assertTrue(cache.commit()); + cache.unlock(); + + assertNotNull(git.commit().setMessage("commit3").call()); + + db.getFS().setExecute(file, false); + git.getRepository() + .getConfig() + .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_FILEMODE, false); + + RevertCommand revert = git.revert(); + RevCommit newHead = revert.include(commit2).call(); + assertNotNull(newHead); + } } @Test public void testRevertConflictMarkers() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareRevert(git); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - RevertCommand revert = git.revert(); - RevCommit newHead = revert.include(sideCommit.getId()) - .call(); - assertNull(newHead); - MergeResult result = revert.getFailingResult(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - String expected = "<<<<<<< master\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n"; - checkFile(new File(db.getWorkTree(), "a"), expected); + RevertCommand revert = git.revert(); + RevCommit newHead = revert.include(sideCommit.getId()) + .call(); + assertNull(newHead); + MergeResult result = revert.getFailingResult(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + String expected = "<<<<<<< master\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n"; + checkFile(new File(db.getWorkTree(), "a"), expected); + } } @Test public void testRevertOurCommitName() throws Exception { - Git git = new Git(db); - RevCommit sideCommit = prepareRevert(git); - - RevertCommand revert = git.revert(); - RevCommit newHead = revert.include(sideCommit.getId()) - .setOurCommitName("custom name").call(); - assertNull(newHead); - MergeResult result = revert.getFailingResult(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + try (Git git = new Git(db)) { + RevCommit sideCommit = prepareRevert(git); - String expected = "<<<<<<< custom name\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n"; - checkFile(new File(db.getWorkTree(), "a"), expected); + RevertCommand revert = git.revert(); + RevCommit newHead = revert.include(sideCommit.getId()) + .setOurCommitName("custom name").call(); + assertNull(newHead); + MergeResult result = revert.getFailingResult(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + String expected = "<<<<<<< custom name\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n"; + checkFile(new File(db.getWorkTree(), "a"), expected); + } } private RevCommit prepareRevert(final Git git) throws Exception { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,12 +55,15 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.StashApplyFailureException; +import org.eclipse.jgit.events.ChangeRecorder; +import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.util.FileUtils; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -77,14 +80,31 @@ private File committedFile; + private ChangeRecorder recorder; + + private ListenerHandle handle; + + @Override @Before public void setUp() throws Exception { super.setUp(); git = Git.wrap(db); + recorder = new ChangeRecorder(); + handle = db.getListenerList().addWorkingTreeModifiedListener(recorder); committedFile = writeTrashFile(PATH, "content"); git.add().addFilepattern(PATH).call(); head = git.commit().setMessage("add file").call(); assertNotNull(head); + recorder.assertNoEvent(); + } + + @Override + @After + public void tearDown() throws Exception { + if (handle != null) { + handle.remove(); + } + super.tearDown(); } @Test @@ -94,10 +114,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertFalse(committedFile.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH }); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -120,11 +142,13 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertFalse(addedFile.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { addedPath }); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertTrue(addedFile.exists()); assertEquals("content2", read(addedFile)); + recorder.assertEvent(new String[] { addedPath }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getChanged().isEmpty()); @@ -141,14 +165,17 @@ @Test public void indexDelete() throws Exception { git.rm().addFilepattern("file.txt").call(); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" }); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertFalse(committedFile.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" }); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -169,10 +196,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertEquals("content2", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -192,16 +221,21 @@ File subfolderFile = writeTrashFile(path, "content"); git.add().addFilepattern(path).call(); head = git.commit().setMessage("add file").call(); + recorder.assertNoEvent(); writeTrashFile(path, "content2"); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(subfolderFile)); + recorder.assertEvent(new String[] { "d1/d2/f.txt" }, + ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertEquals("content2", read(subfolderFile)); + recorder.assertEvent(new String[] { "d1/d2/f.txt", "d1/d2", "d1" }, + ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -224,10 +258,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertEquals("content3", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -251,10 +287,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertEquals("content2", read(committedFile)); + recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -280,10 +318,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertFalse(added.exists()); + recorder.assertNoEvent(); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertEquals("content2", read(added)); + recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getChanged().isEmpty()); @@ -307,10 +347,12 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertEquals("content", read(committedFile)); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); assertFalse(committedFile.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH }); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -336,9 +378,13 @@ assertNotNull(stashed); assertTrue(committedFile.exists()); assertFalse(addedFile.exists()); + recorder.assertEvent(new String[] { PATH }, + new String[] { "file2.txt" }); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); + recorder.assertEvent(new String[] { "file2.txt" }, + new String[] { PATH }); Status status = git.status().call(); assertTrue(status.getChanged().isEmpty()); @@ -361,6 +407,7 @@ assertNotNull(stashed); assertEquals("content", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "content3"); @@ -371,6 +418,7 @@ // expected } assertEquals("content3", read(PATH)); + recorder.assertNoEvent(); } @Test @@ -390,10 +438,12 @@ assertEquals("content\nhead change\nmore content\n", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "content\nmore content\ncommitted change\n"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("committed change").call(); + recorder.assertNoEvent(); try { git.stashApply().call(); @@ -401,6 +451,7 @@ } catch (StashApplyFailureException e) { // expected } + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); Status status = new StatusCommand(db).call(); assertEquals(1, status.getConflicting().size()); assertEquals( @@ -425,12 +476,15 @@ writeTrashFile(PATH, "master content"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("even content").call(); + recorder.assertNoEvent(); git.checkout().setName(otherBranch).call(); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "otherBranch content"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("even more content").call(); + recorder.assertNoEvent(); writeTrashFile(path2, "content\nstashed change\nmore content\n"); @@ -441,12 +495,15 @@ assertEquals("otherBranch content", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY); git.checkout().setName("master").call(); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); git.stashApply().call(); assertEquals("content\nstashed change\nmore content\n", read(file2)); assertEquals("master content", read(committedFile)); + recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY); } @Test @@ -466,12 +523,15 @@ writeTrashFile(PATH, "master content"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("even content").call(); + recorder.assertNoEvent(); git.checkout().setName(otherBranch).call(); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "otherBranch content"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("even more content").call(); + recorder.assertNoEvent(); writeTrashFile(path2, "content\nstashed change in index\nmore content\n"); @@ -484,8 +544,10 @@ assertEquals("content\nmore content\n", read(file2)); assertEquals("otherBranch content", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY); git.checkout().setName("master").call(); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); git.stashApply().call(); assertEquals("content\nstashed change\nmore content\n", read(file2)); assertEquals( @@ -493,6 +555,7 @@ + "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]", indexState(CONTENT)); assertEquals("master content", read(committedFile)); + recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY); } @Test @@ -500,6 +563,7 @@ writeTrashFile(PATH, "content\nmore content\n"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("more content").call(); + recorder.assertNoEvent(); writeTrashFile(PATH, "content\nstashed change\nmore content\n"); @@ -507,15 +571,18 @@ assertNotNull(stashed); assertEquals("content\nmore content\n", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "content\nmore content\ncommitted change\n"); git.add().addFilepattern(PATH).call(); git.commit().setMessage("committed change").call(); + recorder.assertNoEvent(); git.stashApply().call(); assertEquals( "content\nstashed change\nmore content\ncommitted change\n", read(committedFile)); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); } @Test @@ -526,6 +593,7 @@ assertNotNull(stashed); assertEquals("content", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); writeTrashFile(PATH, "content3"); git.add().addFilepattern(PATH).call(); @@ -537,6 +605,7 @@ } catch (StashApplyFailureException e) { // expected } + recorder.assertNoEvent(); assertEquals("content2", read(PATH)); } @@ -548,6 +617,7 @@ assertNotNull(stashed); assertEquals("content", read(committedFile)); assertTrue(git.status().call().isClean()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); String path2 = "file2.txt"; writeTrashFile(path2, "content3"); @@ -556,6 +626,7 @@ ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getAdded().isEmpty()); @@ -582,12 +653,15 @@ RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); assertTrue(git.status().call().isClean()); + recorder.assertEvent(ChangeRecorder.EMPTY, + new String[] { subdir, path }); git.branchCreate().setName(otherBranch).call(); git.checkout().setName(otherBranch).call(); ObjectId unstashed = git.stashApply().call(); assertEquals(stashed, unstashed); + recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertTrue(status.getChanged().isEmpty()); @@ -642,12 +716,15 @@ git.commit().setMessage("x").call(); file.delete(); git.rm().addFilepattern("file").call(); + recorder.assertNoEvent(); git.stashCreate().call(); + recorder.assertEvent(new String[] { "file" }, ChangeRecorder.EMPTY); file.delete(); git.stashApply().setStashRef("stash@{0}").call(); assertFalse(file.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file" }); } @Test @@ -659,9 +736,11 @@ git.add().addFilepattern(PATH).call(); git.stashCreate().call(); assertTrue(untrackedFile.exists()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); git.stashApply().setStashRef("stash@{0}").call(); assertTrue(untrackedFile.exists()); + recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertEquals(1, status.getUntracked().size()); @@ -683,11 +762,14 @@ .call(); assertNotNull(stashedCommit); assertFalse(untrackedFile.exists()); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path }); + deleteTrashFile("a/b"); // checkout should create parent dirs git.stashApply().setStashRef("stash@{0}").call(); assertTrue(untrackedFile.exists()); assertEquals("content", read(path)); + recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY); Status status = git.status().call(); assertEquals(1, status.getUntracked().size()); @@ -705,6 +787,7 @@ String path = "untracked.txt"; writeTrashFile(path, "untracked"); git.stashCreate().setIncludeUntracked(true).call(); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path }); writeTrashFile(path, "committed"); head = git.commit().setMessage("add file").call(); @@ -718,6 +801,7 @@ assertEquals(e.getMessage(), JGitText.get().stashApplyConflict); } assertEquals("committed", read(path)); + recorder.assertNoEvent(); } @Test @@ -726,6 +810,7 @@ String path = "untracked.txt"; writeTrashFile(path, "untracked"); git.stashCreate().setIncludeUntracked(true).call(); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path }); writeTrashFile(path, "working-directory"); try { @@ -735,5 +820,25 @@ assertEquals(e.getMessage(), JGitText.get().stashApplyConflict); } assertEquals("working-directory", read(path)); + recorder.assertNoEvent(); + } + + @Test + public void untrackedAndTrackedChanges() throws Exception { + writeTrashFile(PATH, "changed"); + String path = "untracked.txt"; + writeTrashFile(path, "untracked"); + git.stashCreate().setIncludeUntracked(true).call(); + assertTrue(PATH + " should exist", check(PATH)); + assertEquals(PATH + " should have been reset", "content", read(PATH)); + assertFalse(path + " should not exist", check(path)); + recorder.assertEvent(new String[] { PATH }, new String[] { path }); + git.stashApply().setStashRef("stash@{0}").call(); + assertTrue(PATH + " should exist", check(PATH)); + assertEquals(PATH + " should have new content", "changed", read(PATH)); + assertTrue(path + " should exist", check(path)); + assertEquals(path + " should have new content", "untracked", + read(path)); + recorder.assertEvent(new String[] { PATH, path }, ChangeRecorder.EMPTY); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -82,6 +82,7 @@ private File untrackedFile; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -110,7 +111,7 @@ int parentCount) throws IOException { assertNotNull(commit); - Ref stashRef = db.getRef(Constants.R_STASH); + Ref stashRef = db.exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(commit, stashRef.getObjectId()); assertNotNull(commit.getAuthorIdent()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,6 +73,7 @@ private File committedFile; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -96,13 +97,13 @@ @Test public void dropWithInvalidLogIndex() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); - stashRef = git.getRepository().getRef(Constants.R_STASH); - assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + stashRef = git.getRepository().exactRef(Constants.R_STASH); + assertEquals(stashed, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); try { assertNull(git.stashDrop().setStashRef(100).call()); fail("Exception not thrown"); @@ -115,15 +116,15 @@ @Test public void dropSingleStashedCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); - stashRef = git.getRepository().getRef(Constants.R_STASH); - assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + stashRef = git.getRepository().exactRef(Constants.R_STASH); + assertEquals(stashed, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertNull(git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); ReflogReader reader = git.getRepository().getReflogReader( @@ -134,25 +135,25 @@ @Test public void dropAll() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertNull(git.stashDrop().setAll(true).call()); - assertNull(git.getRepository().getRef(Constants.R_STASH)); + assertNull(git.getRepository().exactRef(Constants.R_STASH)); ReflogReader reader = git.getRepository().getReflogReader( Constants.R_STASH); @@ -162,25 +163,25 @@ @Test public void dropFirstStashedCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(firstStash, git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(firstStash, stashRef.getObjectId()); @@ -196,33 +197,33 @@ @Test public void dropMiddleStashCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content4"); RevCommit thirdStash = git.stashCreate().call(); assertNotNull(thirdStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(thirdStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(thirdStash, git.stashDrop().setStashRef(1).call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); @@ -241,46 +242,46 @@ @Test public void dropBoundaryStashedCommits() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content4"); RevCommit thirdStash = git.stashCreate().call(); assertNotNull(thirdStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(thirdStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content5"); RevCommit fourthStash = git.stashCreate().call(); assertNotNull(fourthStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(fourthStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(fourthStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(thirdStash, git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); assertEquals(thirdStash, git.stashDrop().setStashRef(2).call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevCommit; @@ -94,9 +95,7 @@ git.add().addFilepattern("file.txt").call(); RevCommit commit = git.commit().setMessage("create file").call(); - RefUpdate update = db.updateRef(Constants.R_STASH); - update.setNewObjectId(commit); - assertEquals(Result.NEW, update.update()); + assertEquals(Result.NEW, newStashUpdate(commit).update()); StashListCommand command = git.stashList(); Collection stashed = command.call(); @@ -117,13 +116,8 @@ git.add().addFilepattern("file.txt").call(); RevCommit commit2 = git.commit().setMessage("edit file").call(); - RefUpdate create = db.updateRef(Constants.R_STASH); - create.setNewObjectId(commit1); - assertEquals(Result.NEW, create.update()); - - RefUpdate update = db.updateRef(Constants.R_STASH); - update.setNewObjectId(commit2); - assertEquals(Result.FAST_FORWARD, update.update()); + assertEquals(Result.NEW, newStashUpdate(commit1).update()); + assertEquals(Result.FAST_FORWARD, newStashUpdate(commit2).update()); StashListCommand command = git.stashList(); Collection stashed = command.call(); @@ -133,4 +127,11 @@ assertEquals(commit2, iter.next()); assertEquals(commit1, iter.next()); } + + private RefUpdate newStashUpdate(ObjectId newId) throws Exception { + RefUpdate ru = db.updateRef(Constants.R_STASH); + ru.setNewObjectId(newId); + ru.setForceRefLog(true); + return ru; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,109 +61,111 @@ @Test public void testEmptyStatus() throws NoWorkTreeException, GitAPIException { - Git git = new Git(db); - - Status stat = git.status().call(); - assertEquals(0, stat.getAdded().size()); - assertEquals(0, stat.getChanged().size()); - assertEquals(0, stat.getMissing().size()); - assertEquals(0, stat.getModified().size()); - assertEquals(0, stat.getRemoved().size()); - assertEquals(0, stat.getUntracked().size()); + try (Git git = new Git(db)) { + Status stat = git.status().call(); + assertEquals(0, stat.getAdded().size()); + assertEquals(0, stat.getChanged().size()); + assertEquals(0, stat.getMissing().size()); + assertEquals(0, stat.getModified().size()); + assertEquals(0, stat.getRemoved().size()); + assertEquals(0, stat.getUntracked().size()); + } } @Test public void testDifferentStates() throws IOException, NoFilepatternException, GitAPIException { - Git git = new Git(db); - writeTrashFile("a", "content of a"); - writeTrashFile("b", "content of b"); - writeTrashFile("c", "content of c"); - git.add().addFilepattern("a").addFilepattern("b").call(); - Status stat = git.status().call(); - assertEquals(Sets.of("a", "b"), stat.getAdded()); - assertEquals(0, stat.getChanged().size()); - assertEquals(0, stat.getMissing().size()); - assertEquals(0, stat.getModified().size()); - assertEquals(0, stat.getRemoved().size()); - assertEquals(Sets.of("c"), stat.getUntracked()); - git.commit().setMessage("initial").call(); - - writeTrashFile("a", "modified content of a"); - writeTrashFile("b", "modified content of b"); - writeTrashFile("d", "content of d"); - git.add().addFilepattern("a").addFilepattern("d").call(); - writeTrashFile("a", "again modified content of a"); - stat = git.status().call(); - assertEquals(Sets.of("d"), stat.getAdded()); - assertEquals(Sets.of("a"), stat.getChanged()); - assertEquals(0, stat.getMissing().size()); - assertEquals(Sets.of("b", "a"), stat.getModified()); - assertEquals(0, stat.getRemoved().size()); - assertEquals(Sets.of("c"), stat.getUntracked()); - git.add().addFilepattern(".").call(); - git.commit().setMessage("second").call(); - - stat = git.status().call(); - assertEquals(0, stat.getAdded().size()); - assertEquals(0, stat.getChanged().size()); - assertEquals(0, stat.getMissing().size()); - assertEquals(0, stat.getModified().size()); - assertEquals(0, stat.getRemoved().size()); - assertEquals(0, stat.getUntracked().size()); - - deleteTrashFile("a"); - assertFalse(new File(git.getRepository().getWorkTree(), "a").exists()); - git.add().addFilepattern("a").setUpdate(true).call(); - writeTrashFile("a", "recreated content of a"); - stat = git.status().call(); - assertEquals(0, stat.getAdded().size()); - assertEquals(0, stat.getChanged().size()); - assertEquals(0, stat.getMissing().size()); - assertEquals(0, stat.getModified().size()); - assertEquals(Sets.of("a"), stat.getRemoved()); - assertEquals(Sets.of("a"), stat.getUntracked()); - git.commit().setMessage("t").call(); - - writeTrashFile("sub/a", "sub-file"); - stat = git.status().call(); - assertEquals(1, stat.getUntrackedFolders().size()); - assertTrue(stat.getUntrackedFolders().contains("sub")); + try (Git git = new Git(db)) { + writeTrashFile("a", "content of a"); + writeTrashFile("b", "content of b"); + writeTrashFile("c", "content of c"); + git.add().addFilepattern("a").addFilepattern("b").call(); + Status stat = git.status().call(); + assertEquals(Sets.of("a", "b"), stat.getAdded()); + assertEquals(0, stat.getChanged().size()); + assertEquals(0, stat.getMissing().size()); + assertEquals(0, stat.getModified().size()); + assertEquals(0, stat.getRemoved().size()); + assertEquals(Sets.of("c"), stat.getUntracked()); + git.commit().setMessage("initial").call(); + + writeTrashFile("a", "modified content of a"); + writeTrashFile("b", "modified content of b"); + writeTrashFile("d", "content of d"); + git.add().addFilepattern("a").addFilepattern("d").call(); + writeTrashFile("a", "again modified content of a"); + stat = git.status().call(); + assertEquals(Sets.of("d"), stat.getAdded()); + assertEquals(Sets.of("a"), stat.getChanged()); + assertEquals(0, stat.getMissing().size()); + assertEquals(Sets.of("b", "a"), stat.getModified()); + assertEquals(0, stat.getRemoved().size()); + assertEquals(Sets.of("c"), stat.getUntracked()); + git.add().addFilepattern(".").call(); + git.commit().setMessage("second").call(); + + stat = git.status().call(); + assertEquals(0, stat.getAdded().size()); + assertEquals(0, stat.getChanged().size()); + assertEquals(0, stat.getMissing().size()); + assertEquals(0, stat.getModified().size()); + assertEquals(0, stat.getRemoved().size()); + assertEquals(0, stat.getUntracked().size()); + + deleteTrashFile("a"); + assertFalse(new File(git.getRepository().getWorkTree(), "a").exists()); + git.add().addFilepattern("a").setUpdate(true).call(); + writeTrashFile("a", "recreated content of a"); + stat = git.status().call(); + assertEquals(0, stat.getAdded().size()); + assertEquals(0, stat.getChanged().size()); + assertEquals(0, stat.getMissing().size()); + assertEquals(0, stat.getModified().size()); + assertEquals(Sets.of("a"), stat.getRemoved()); + assertEquals(Sets.of("a"), stat.getUntracked()); + git.commit().setMessage("t").call(); + + writeTrashFile("sub/a", "sub-file"); + stat = git.status().call(); + assertEquals(1, stat.getUntrackedFolders().size()); + assertTrue(stat.getUntrackedFolders().contains("sub")); + } } @Test public void testDifferentStatesWithPaths() throws IOException, NoFilepatternException, GitAPIException { - Git git = new Git(db); - writeTrashFile("a", "content of a"); - writeTrashFile("D/b", "content of b"); - writeTrashFile("D/c", "content of c"); - writeTrashFile("D/D/d", "content of d"); - git.add().addFilepattern(".").call(); - - writeTrashFile("a", "new content of a"); - writeTrashFile("D/b", "new content of b"); - writeTrashFile("D/D/d", "new content of d"); - - - // filter on an not existing path - Status stat = git.status().addPath("x").call(); - assertEquals(0, stat.getModified().size()); - - // filter on an existing file - stat = git.status().addPath("a").call(); - assertEquals(Sets.of("a"), stat.getModified()); - - // filter on an existing folder - stat = git.status().addPath("D").call(); - assertEquals(Sets.of("D/b", "D/D/d"), stat.getModified()); - - // filter on an existing folder and file - stat = git.status().addPath("D/D").addPath("a").call(); - assertEquals(Sets.of("a", "D/D/d"), stat.getModified()); - - // do not filter at all - stat = git.status().call(); - assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified()); + try (Git git = new Git(db)) { + writeTrashFile("a", "content of a"); + writeTrashFile("D/b", "content of b"); + writeTrashFile("D/c", "content of c"); + writeTrashFile("D/D/d", "content of d"); + git.add().addFilepattern(".").call(); + + writeTrashFile("a", "new content of a"); + writeTrashFile("D/b", "new content of b"); + writeTrashFile("D/D/d", "new content of d"); + + + // filter on an not existing path + Status stat = git.status().addPath("x").call(); + assertEquals(0, stat.getModified().size()); + + // filter on an existing file + stat = git.status().addPath("a").call(); + assertEquals(Sets.of("a"), stat.getModified()); + + // filter on an existing folder + stat = git.status().addPath("D").call(); + assertEquals(Sets.of("D/b", "D/D/d"), stat.getModified()); + + // filter on an existing folder and file + stat = git.status().addPath("D/D").addPath("a").call(); + assertEquals(Sets.of("a", "D/D/d"), stat.getModified()); + + // do not filter at all + stat = git.status().call(); + assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,171 +62,185 @@ @Test public void testTaggingOnHead() throws GitAPIException, IOException { - Git git = new Git(db); - RevCommit commit = git.commit().setMessage("initial commit").call(); - Ref tagRef = git.tag().setName("tag").call(); - assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId()); - RevWalk walk = new RevWalk(db); - assertEquals("tag", walk.parseTag(tagRef.getObjectId()).getTagName()); + try (Git git = new Git(db); + RevWalk walk = new RevWalk(db)) { + RevCommit commit = git.commit().setMessage("initial commit").call(); + Ref tagRef = git.tag().setName("tag").call(); + assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId()); + assertEquals("tag", walk.parseTag(tagRef.getObjectId()).getTagName()); + } } @Test public void testTagging() throws GitAPIException, JGitInternalException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - RevCommit commit = git.commit().setMessage("second commit").call(); - git.commit().setMessage("third commit").call(); - Ref tagRef = git.tag().setObjectId(commit).setName("tag").call(); - assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId()); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + RevCommit commit = git.commit().setMessage("second commit").call(); + git.commit().setMessage("third commit").call(); + Ref tagRef = git.tag().setObjectId(commit).setName("tag").call(); + assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId()); + } } @Test public void testUnannotatedTagging() throws GitAPIException, JGitInternalException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - RevCommit commit = git.commit().setMessage("second commit").call(); - git.commit().setMessage("third commit").call(); - Ref tagRef = git.tag().setObjectId(commit).setName("tag") - .setAnnotated(false).call(); - assertEquals(commit.getId(), tagRef.getObjectId()); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + RevCommit commit = git.commit().setMessage("second commit").call(); + git.commit().setMessage("third commit").call(); + Ref tagRef = git.tag().setObjectId(commit).setName("tag") + .setAnnotated(false).call(); + assertEquals(commit.getId(), tagRef.getObjectId()); + } } @Test public void testEmptyTagName() throws GitAPIException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - try { - // forget to tag name - git.tag().setMessage("some message").call(); - fail("We should have failed without a tag name"); - } catch (InvalidTagNameException e) { - // should hit here + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + try { + // forget to tag name + git.tag().setMessage("some message").call(); + fail("We should have failed without a tag name"); + } catch (InvalidTagNameException e) { + // should hit here + } } } @Test public void testInvalidTagName() throws GitAPIException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - try { - git.tag().setName("bad~tag~name").setMessage("some message").call(); - fail("We should have failed due to a bad tag name"); - } catch (InvalidTagNameException e) { - // should hit here + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + try { + git.tag().setName("bad~tag~name").setMessage("some message").call(); + fail("We should have failed due to a bad tag name"); + } catch (InvalidTagNameException e) { + // should hit here + } } } @Test public void testFailureOnSignedTags() throws GitAPIException { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - try { - git.tag().setSigned(true).setName("tag").call(); - fail("We should have failed with an UnsupportedOperationException due to signed tag"); - } catch (UnsupportedOperationException e) { - // should hit here + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + try { + git.tag().setSigned(true).setName("tag").call(); + fail("We should have failed with an UnsupportedOperationException due to signed tag"); + } catch (UnsupportedOperationException e) { + // should hit here + } } } @Test public void testDelete() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - Ref tagRef = git.tag().setName("tag").call(); - assertEquals(1, db.getTags().size()); - - List deleted = git.tagDelete().setTags(tagRef.getName()) - .call(); - assertEquals(1, deleted.size()); - assertEquals(tagRef.getName(), deleted.get(0)); - assertEquals(0, db.getTags().size()); - - Ref tagRef1 = git.tag().setName("tag1").call(); - Ref tagRef2 = git.tag().setName("tag2").call(); - assertEquals(2, db.getTags().size()); - deleted = git.tagDelete().setTags(tagRef1.getName(), tagRef2.getName()) - .call(); - assertEquals(2, deleted.size()); - assertEquals(0, db.getTags().size()); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + Ref tagRef = git.tag().setName("tag").call(); + assertEquals(1, db.getTags().size()); + + List deleted = git.tagDelete().setTags(tagRef.getName()) + .call(); + assertEquals(1, deleted.size()); + assertEquals(tagRef.getName(), deleted.get(0)); + assertEquals(0, db.getTags().size()); + + Ref tagRef1 = git.tag().setName("tag1").call(); + Ref tagRef2 = git.tag().setName("tag2").call(); + assertEquals(2, db.getTags().size()); + deleted = git.tagDelete().setTags(tagRef1.getName(), tagRef2.getName()) + .call(); + assertEquals(2, deleted.size()); + assertEquals(0, db.getTags().size()); + } } @Test public void testDeleteFullName() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); - Ref tagRef = git.tag().setName("tag").call(); - assertEquals(1, db.getTags().size()); - - List deleted = git.tagDelete() - .setTags(Repository.shortenRefName(tagRef.getName())).call(); - assertEquals(1, deleted.size()); - assertEquals(tagRef.getName(), deleted.get(0)); - assertEquals(0, db.getTags().size()); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); + Ref tagRef = git.tag().setName("tag").call(); + assertEquals(1, db.getTags().size()); + + List deleted = git.tagDelete() + .setTags(Repository.shortenRefName(tagRef.getName())).call(); + assertEquals(1, deleted.size()); + assertEquals(tagRef.getName(), deleted.get(0)); + assertEquals(0, db.getTags().size()); + } } @Test public void testDeleteEmptyTagNames() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); - List deleted = git.tagDelete().setTags().call(); - assertEquals(0, deleted.size()); + List deleted = git.tagDelete().setTags().call(); + assertEquals(0, deleted.size()); + } } @Test public void testDeleteNonExisting() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); - List deleted = git.tagDelete().setTags("tag").call(); - assertEquals(0, deleted.size()); + List deleted = git.tagDelete().setTags("tag").call(); + assertEquals(0, deleted.size()); + } } @Test public void testDeleteBadName() throws Exception { - Git git = new Git(db); - git.commit().setMessage("initial commit").call(); + try (Git git = new Git(db)) { + git.commit().setMessage("initial commit").call(); - List deleted = git.tagDelete().setTags("bad~tag~name") - .call(); - assertEquals(0, deleted.size()); + List deleted = git.tagDelete().setTags("bad~tag~name") + .call(); + assertEquals(0, deleted.size()); + } } @Test public void testShouldNotBlowUpIfThereAreNoTagsInRepository() throws Exception { - Git git = new Git(db); - git.add().addFilepattern("*").call(); - git.commit().setMessage("initial commit").call(); - List list = git.tagList().call(); - assertEquals(0, list.size()); + try (Git git = new Git(db)) { + git.add().addFilepattern("*").call(); + git.commit().setMessage("initial commit").call(); + List list = git.tagList().call(); + assertEquals(0, list.size()); + } } @Test public void testShouldNotBlowUpIfThereAreNoCommitsInRepository() throws Exception { - Git git = new Git(db); - List list = git.tagList().call(); - assertEquals(0, list.size()); + try (Git git = new Git(db)) { + List list = git.tagList().call(); + assertEquals(0, list.size()); + } } @Test public void testListAllTagsInRepositoryInOrder() throws Exception { - Git git = new Git(db); - git.add().addFilepattern("*").call(); - git.commit().setMessage("initial commit").call(); - - git.tag().setName("v3").call(); - git.tag().setName("v2").call(); - git.tag().setName("v10").call(); - - List list = git.tagList().call(); - - assertEquals(3, list.size()); - assertEquals("refs/tags/v10", list.get(0).getName()); - assertEquals("refs/tags/v2", list.get(1).getName()); - assertEquals("refs/tags/v3", list.get(2).getName()); + try (Git git = new Git(db)) { + git.add().addFilepattern("*").call(); + git.commit().setMessage("initial commit").call(); + + git.tag().setName("v3").call(); + git.tag().setName("v2").call(); + git.tag().setName("v10").call(); + + List list = git.tagList().call(); + + assertEquals(3, list.size()); + assertEquals("refs/tags/v10", list.get(0).getName()); + assertEquals("refs/tags/v2", list.get(1).getName()); + assertEquals("refs/tags/v3", list.get(2).getName()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2014, Obeo. - * 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.attributes; - -import static org.eclipse.jgit.attributes.Attribute.State.SET; -import static org.eclipse.jgit.attributes.Attribute.State.UNSET; -import static org.junit.Assert.assertEquals; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import org.junit.After; -import org.junit.Test; - -/** - * Test {@link AttributesNode} - */ -public class AttributeNodeTest { - - private static final Attribute A_SET_ATTR = new Attribute("A", SET); - - private static final Attribute A_UNSET_ATTR = new Attribute("A", UNSET); - - private static final Attribute B_SET_ATTR = new Attribute("B", SET); - - private static final Attribute B_UNSET_ATTR = new Attribute("B", UNSET); - - private static final Attribute C_VALUE_ATTR = new Attribute("C", "value"); - - private static final Attribute C_VALUE2_ATTR = new Attribute("C", "value2"); - - private InputStream is; - - @After - public void after() throws IOException { - if (is != null) - is.close(); - } - - @Test - public void testBasic() throws IOException { - String attributeFileContent = "*.type1 A -B C=value\n" - + "*.type2 -A B C=value2"; - - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, - asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); - assertAttribute("file.type2", node, - asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); - } - - @Test - public void testNegativePattern() throws IOException { - String attributeFileContent = "!*.type1 A -B C=value\n" - + "!*.type2 -A B C=value2"; - - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); - assertAttribute("file.type2", node, Collections. emptySet()); - } - - @Test - public void testEmptyNegativeAttributeKey() throws IOException { - String attributeFileContent = "*.type1 - \n" // - + "*.type2 - -A"; - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); - assertAttribute("file.type2", node, asSet(A_UNSET_ATTR)); - } - - @Test - public void testEmptyValueKey() throws IOException { - String attributeFileContent = "*.type1 = \n" // - + "*.type2 =value\n"// - + "*.type3 attr=\n"; - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); - assertAttribute("file.type2", node, Collections. emptySet()); - assertAttribute("file.type3", node, asSet(new Attribute("attr", ""))); - } - - @Test - public void testEmptyLine() throws IOException { - String attributeFileContent = "*.type1 A -B C=value\n" // - + "\n" // - + " \n" // - + "*.type2 -A B C=value2"; - - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, - asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); - assertAttribute("file.type2", node, - asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); - } - - @Test - public void testTabSeparator() throws IOException { - String attributeFileContent = "*.type1 \tA -B\tC=value\n" - + "*.type2\t -A\tB C=value2\n" // - + "*.type3 \t\t B\n" // - + "*.type3\t-A";// - - is = new ByteArrayInputStream(attributeFileContent.getBytes()); - AttributesNode node = new AttributesNode(); - node.parse(is); - assertAttribute("file.type1", node, - asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); - assertAttribute("file.type2", node, - asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); - assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR)); - } - - private void assertAttribute(String path, AttributesNode node, - Set attrs) { - HashMap attributes = new HashMap(); - node.getAttributes(path, false, attributes); - assertEquals(attrs, new HashSet(attributes.values())); - } - - static Set asSet(Attribute... attrs) { - Set result = new HashSet(); - for (Attribute attr : attrs) - result.add(attr); - return result; - } - -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,618 @@ +/* + * Copyright (C) 2015, 2017 Ivan Motsch + * 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.attributes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Test; + +/** + * Tests {@link AttributesHandler} + */ +public class AttributesHandlerTest extends RepositoryTestCase { + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + private TreeWalk walk; + + @Test + public void testExpandNonMacro1() throws Exception { + setupRepo(null, null, null, "*.txt text"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("text")); + endWalk(); + } + + @Test + public void testExpandNonMacro2() throws Exception { + setupRepo(null, null, null, "*.txt -text"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("-text")); + endWalk(); + } + + @Test + public void testExpandNonMacro3() throws Exception { + setupRepo(null, null, null, "*.txt !text"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("")); + endWalk(); + } + + @Test + public void testExpandNonMacro4() throws Exception { + setupRepo(null, null, null, "*.txt text=auto"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("text=auto")); + endWalk(); + } + + @Test + public void testExpandBuiltInMacro1() throws Exception { + setupRepo(null, null, null, "*.txt binary"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("binary -diff -merge -text")); + endWalk(); + } + + @Test + public void testExpandBuiltInMacro2() throws Exception { + setupRepo(null, null, null, "*.txt -binary"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("-binary diff merge text")); + endWalk(); + } + + @Test + public void testExpandBuiltInMacro3() throws Exception { + setupRepo(null, null, null, "*.txt !binary"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("")); + endWalk(); + } + + @Test + public void testCustomGlobalMacro1() throws Exception { + setupRepo( + "[attr]foo a -b !c d=e", null, null, "*.txt foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo a -b d=e")); + endWalk(); + } + + @Test + public void testCustomGlobalMacro2() throws Exception { + setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt -foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("-foo -a b d=e")); + endWalk(); + } + + @Test + public void testCustomGlobalMacro3() throws Exception { + setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt !foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("")); + endWalk(); + } + + @Test + public void testCustomGlobalMacro4() throws Exception { + setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt foo=bar"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo=bar a -b d=bar")); + endWalk(); + } + + @Test + public void testInfoOverridesGlobal() throws Exception { + setupRepo("[attr]foo bar1", + "[attr]foo bar2", null, "*.txt foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo bar2")); + endWalk(); + } + + @Test + public void testWorkDirRootOverridesGlobal() throws Exception { + setupRepo("[attr]foo bar1", + null, + "[attr]foo bar3", "*.txt foo"); + + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo bar3")); + endWalk(); + } + + @Test + public void testInfoOverridesWorkDirRoot() throws Exception { + setupRepo("[attr]foo bar1", + "[attr]foo bar2", "[attr]foo bar3", "*.txt foo"); + + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo bar2")); + endWalk(); + } + + @Test + public void testRecursiveMacro() throws Exception { + setupRepo( + "[attr]foo x bar -foo", + null, null, "*.txt foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo x bar")); + endWalk(); + } + + @Test + public void testCyclicMacros() throws Exception { + setupRepo( + "[attr]foo x -bar\n[attr]bar y -foo", null, null, "*.txt foo"); + + walk = beginWalk(); + assertIteration(D, "sub"); + assertIteration(F, "sub/.gitattributes"); + assertIteration(F, "sub/a.txt", attrs("foo x -bar -y")); + endWalk(); + } + + @Test + public void testRelativePaths() throws Exception { + setupRepo("sub/ global", "sub/** init", + "sub/** top_sub\n*.txt top", + "sub/** subsub\nsub/ subsub2\n*.txt foo"); + // The last sub/** is in sub/.gitattributes. It must not + // apply to any of the files here. It would match for a + // further subdirectory sub/sub. The sub/ rules must match + // only for directories. + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub", attrs("global")); + assertIteration(F, "sub/.gitattributes", attrs("init top_sub")); + assertIteration(F, "sub/a.txt", attrs("init foo top top_sub")); + endWalk(); + // All right, let's see that they *do* apply in sub/sub: + writeTrashFile("sub/sub/b.txt", "b"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub", attrs("global")); + assertIteration(F, "sub/.gitattributes", attrs("init top_sub")); + assertIteration(F, "sub/a.txt", attrs("init foo top top_sub")); + assertIteration(D, "sub/sub", attrs("init subsub2 top_sub global")); + assertIteration(F, "sub/sub/b.txt", + attrs("init foo subsub top top_sub")); + endWalk(); + } + + @Test + public void testNestedMatchNot() throws Exception { + setupRepo(null, null, "*.xml xml\n*.jar jar", null); + writeTrashFile("foo.xml/bar.jar", "b"); + writeTrashFile("foo.xml/bar.xml", "bx"); + writeTrashFile("sub/b.jar", "bj"); + writeTrashFile("sub/b.xml", "bx"); + // On foo.xml/bar.jar we must not have 'xml' + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo.xml", attrs("xml")); + assertIteration(F, "foo.xml/bar.jar", attrs("jar")); + assertIteration(F, "foo.xml/bar.xml", attrs("xml")); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(F, "sub/b.jar", attrs("jar")); + assertIteration(F, "sub/b.xml", attrs("xml")); + endWalk(); + } + + @Test + public void testNestedMatch() throws Exception { + // See also CGitAttributeTest.testNestedMatch() + setupRepo(null, null, "foo/ xml\nsub/foo/ sub\n*.jar jar", null); + writeTrashFile("foo/bar.jar", "b"); + writeTrashFile("foo/bar.xml", "bx"); + writeTrashFile("sub/b.jar", "bj"); + writeTrashFile("sub/b.xml", "bx"); + writeTrashFile("sub/foo/b.jar", "bf"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo", attrs("xml")); + assertIteration(F, "foo/bar.jar", attrs("jar")); + assertIteration(F, "foo/bar.xml"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(F, "sub/b.jar", attrs("jar")); + assertIteration(F, "sub/b.xml"); + assertIteration(D, "sub/foo", attrs("sub xml")); + assertIteration(F, "sub/foo/b.jar", attrs("jar")); + endWalk(); + } + + @Test + public void testNestedMatchRecursive() throws Exception { + setupRepo(null, null, "foo/** xml\n*.jar jar", null); + writeTrashFile("foo/bar.jar", "b"); + writeTrashFile("foo/bar.xml", "bx"); + writeTrashFile("sub/b.jar", "bj"); + writeTrashFile("sub/b.xml", "bx"); + writeTrashFile("sub/foo/b.jar", "bf"); + // On foo.xml/bar.jar we must not have 'xml' + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(F, "foo/bar.jar", attrs("jar xml")); + assertIteration(F, "foo/bar.xml", attrs("xml")); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(F, "sub/b.jar", attrs("jar")); + assertIteration(F, "sub/b.xml"); + assertIteration(D, "sub/foo"); + assertIteration(F, "sub/foo/b.jar", attrs("jar")); + endWalk(); + } + + @Test + public void testStarMatchOnSlashNot() throws Exception { + setupRepo(null, null, "s*xt bar", null); + writeTrashFile("sub/a.txt", "1"); + writeTrashFile("foo/sext", "2"); + writeTrashFile("foo/s.txt", "3"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(F, "foo/s.txt", attrs("bar")); + assertIteration(F, "foo/sext", attrs("bar")); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + endWalk(); + } + + @Test + public void testPrefixMatchNot() throws Exception { + setupRepo(null, null, "sub/new bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testComplexPathMatch() throws Exception { + setupRepo(null, null, "s[t-v]b/n[de]w bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("sub/ndw", "2"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(F, "sub/ndw", attrs("bar")); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testStarPathMatch() throws Exception { + setupRepo(null, null, "sub/new/* bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("sub/new/lower/foo.txt", "2"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new"); + assertIteration(F, "sub/new/foo.txt", attrs("bar")); + assertIteration(D, "sub/new/lower", attrs("bar")); + assertIteration(F, "sub/new/lower/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatchSubSimple() throws Exception { + setupRepo(null, null, "sub/new/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + writeTrashFile("sub/sub/new/foo.txt", "3"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new"); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + assertIteration(D, "sub/sub"); + assertIteration(D, "sub/sub/new"); + assertIteration(F, "sub/sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatchSubRecursive() throws Exception { + setupRepo(null, null, "**/sub/new/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new", attrs("bar")); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack() throws Exception { + setupRepo(null, null, "**/sub/new/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + writeTrashFile("sub/sub/new/foo.txt", "3"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new", attrs("bar")); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + assertIteration(D, "sub/sub"); + assertIteration(D, "sub/sub/new", attrs("bar")); + assertIteration(F, "sub/sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception { + setupRepo(null, null, "**/**/sub/new/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + writeTrashFile("sub/sub/new/foo.txt", "3"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new", attrs("bar")); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + assertIteration(D, "sub/sub"); + assertIteration(D, "sub/sub/new", attrs("bar")); + assertIteration(F, "sub/sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatchSubComplex() throws Exception { + setupRepo(null, null, "s[uv]b/n*/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new"); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + endWalk(); + } + + @Test + public void testDirectoryMatch() throws Exception { + setupRepo(null, null, "new/ bar", null); + writeTrashFile("sub/new/foo.txt", "1"); + writeTrashFile("foo/sub/new/foo.txt", "2"); + writeTrashFile("foo/new", "3"); + walk = beginWalk(); + assertIteration(F, ".gitattributes"); + assertIteration(D, "foo"); + assertIteration(F, "foo/new"); + assertIteration(D, "foo/sub"); + assertIteration(D, "foo/sub/new", attrs("bar")); + assertIteration(F, "foo/sub/new/foo.txt"); + assertIteration(D, "sub"); + assertIteration(F, "sub/a.txt"); + assertIteration(D, "sub/new", attrs("bar")); + assertIteration(F, "sub/new/foo.txt"); + endWalk(); + } + + private static Collection attrs(String s) { + return new AttributesRule("*", s).getAttributes(); + } + + private void assertIteration(FileMode type, String pathName) + throws IOException { + assertIteration(type, pathName, Collections. emptyList()); + } + + private void assertIteration(FileMode type, String pathName, + Collection expectedAttrs) throws IOException { + assertTrue("walk has entry", walk.next()); + assertEquals(pathName, walk.getPathString()); + assertEquals(type, walk.getFileMode(0)); + + if (expectedAttrs != null) { + assertEquals(new ArrayList<>(expectedAttrs), + new ArrayList<>(walk.getAttributes().getAll())); + } + + if (D.equals(type)) + walk.enterSubtree(); + } + + /** + * @param globalAttributesContent + * @param infoAttributesContent + * @param rootAttributesContent + * @param subDirAttributesContent + * @throws Exception + * Setup a repo with .gitattributes files and a test file + * sub/a.txt + */ + private void setupRepo( + String globalAttributesContent, + String infoAttributesContent, String rootAttributesContent, String subDirAttributesContent) + throws Exception { + FileBasedConfig config = db.getConfig(); + if (globalAttributesContent != null) { + File f = new File(db.getDirectory(), "global/attributes"); + write(f, globalAttributesContent); + config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE, + f.getAbsolutePath()); + + } + if (infoAttributesContent != null) { + File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES); + write(f, infoAttributesContent); + } + config.save(); + + if (rootAttributesContent != null) { + writeAttributesFile(Constants.DOT_GIT_ATTRIBUTES, + rootAttributesContent); + } + + if (subDirAttributesContent != null) { + writeAttributesFile("sub/" + Constants.DOT_GIT_ATTRIBUTES, + subDirAttributesContent); + } + + writeTrashFile("sub/a.txt", "a"); + } + + private void writeAttributesFile(String name, String... rules) + throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) + data.append(line + "\n"); + writeTrashFile(name, data.toString()); + } + + private TreeWalk beginWalk() { + TreeWalk newWalk = new TreeWalk(db); + newWalk.addTree(new FileTreeIterator(db)); + return newWalk; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -109,16 +109,16 @@ pattern = "/src/ne?"; assertMatched(pattern, "/src/new/"); assertMatched(pattern, "/src/new"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/new/a/a.c"); assertNotMatched(pattern, "/src/new.c"); //Test name-only fnmatcher matches pattern = "ne?"; assertMatched(pattern, "/src/new/"); assertMatched(pattern, "/src/new"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/new/a/a.c"); assertMatched(pattern, "/neb"); assertNotMatched(pattern, "/src/new.c"); } @@ -169,16 +169,16 @@ pattern = "/src/ne?"; assertMatched(pattern, "src/new/"); assertMatched(pattern, "src/new"); - assertMatched(pattern, "src/new/a.c"); - assertMatched(pattern, "src/new/a/a.c"); + assertNotMatched(pattern, "src/new/a.c"); + assertNotMatched(pattern, "src/new/a/a.c"); assertNotMatched(pattern, "src/new.c"); //Test name-only fnmatcher matches pattern = "ne?"; assertMatched(pattern, "src/new/"); assertMatched(pattern, "src/new"); - assertMatched(pattern, "src/new/a.c"); - assertMatched(pattern, "src/new/a/a.c"); + assertNotMatched(pattern, "src/new/a.c"); + assertNotMatched(pattern, "src/new/a/a.c"); assertMatched(pattern, "neb"); assertNotMatched(pattern, "src/new.c"); } @@ -197,35 +197,50 @@ pattern = "/src/new"; assertMatched(pattern, "/src/new/"); assertMatched(pattern, "/src/new"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/new/a/a.c"); assertNotMatched(pattern, "/src/new.c"); //Test child directory is matched, slash after name pattern = "/src/new/"; assertMatched(pattern, "/src/new/"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/new/a/a.c"); assertNotMatched(pattern, "/src/new"); assertNotMatched(pattern, "/src/new.c"); //Test directory is matched by name only pattern = "b1"; - assertMatched(pattern, "/src/new/a/b1/a.c"); + assertNotMatched(pattern, "/src/new/a/b1/a.c"); assertNotMatched(pattern, "/src/new/a/b2/file.c"); assertNotMatched(pattern, "/src/new/a/bb1/file.c"); assertNotMatched(pattern, "/src/new/a/file.c"); + assertNotMatched(pattern, "/src/new/a/bb1"); + assertMatched(pattern, "/src/new/a/b1"); } @Test public void testTrailingSlash() { String pattern = "/src/"; assertMatched(pattern, "/src/"); - assertMatched(pattern, "/src/new"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/a.c"); assertNotMatched(pattern, "/src"); assertNotMatched(pattern, "/srcA/"); + + pattern = "src/"; + assertMatched(pattern, "src/"); + assertMatched(pattern, "/src/"); + assertNotMatched(pattern, "src"); + assertNotMatched(pattern, "/src/new"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "foo/src/a.c"); + assertNotMatched(pattern, "foo/src/bar/a.c"); + assertNotMatched(pattern, "foo/src/bar/src"); + assertMatched(pattern, "foo/src/"); + assertMatched(pattern, "foo/src/bar/src/"); } @Test @@ -239,51 +254,58 @@ assertMatched(pattern, "/src/test.stp"); assertNotMatched(pattern, "/test.stp1"); assertNotMatched(pattern, "/test.astp"); + assertNotMatched(pattern, "test.stp/foo.bar"); + assertMatched(pattern, "test.stp"); + assertMatched(pattern, "test.stp/"); + assertMatched(pattern, "test.stp/test.stp"); //Test matches for name-only, applies to file name or folder name pattern = "src"; assertMatched(pattern, "/src"); assertMatched(pattern, "/src/"); - assertMatched(pattern, "/src/a.c"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/new/src/a.c"); + assertNotMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/new/src/a.c"); assertMatched(pattern, "/file/src"); //Test matches for name-only, applies only to folder names pattern = "src/"; - assertMatched(pattern, "/src/"); - assertMatched(pattern, "/src/a.c"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/new/src/a.c"); + assertNotMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/new/src/a.c"); assertNotMatched(pattern, "/src"); assertNotMatched(pattern, "/file/src"); + assertMatched(pattern, "/file/src/"); //Test matches for name-only, applies to file name or folder name //With a small wildcard pattern = "?rc"; - assertMatched(pattern, "/src/a.c"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/new/src/a.c"); + assertNotMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/new/src/a.c"); + assertMatched(pattern, "/new/src/"); assertMatched(pattern, "/file/src"); assertMatched(pattern, "/src/"); //Test matches for name-only, applies to file name or folder name //With a small wildcard pattern = "?r[a-c]"; - assertMatched(pattern, "/src/a.c"); - assertMatched(pattern, "/src/new/a.c"); - assertMatched(pattern, "/new/src/a.c"); + assertNotMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + assertNotMatched(pattern, "/new/src/a.c"); assertMatched(pattern, "/file/src"); assertMatched(pattern, "/src/"); - assertMatched(pattern, "/srb/a.c"); - assertMatched(pattern, "/grb/new/a.c"); - assertMatched(pattern, "/new/crb/a.c"); + assertNotMatched(pattern, "/srb/a.c"); + assertNotMatched(pattern, "/grb/new/a.c"); + assertNotMatched(pattern, "/new/crb/a.c"); assertMatched(pattern, "/file/3rb"); assertMatched(pattern, "/xrb/"); - assertMatched(pattern, "/3ra/a.c"); - assertMatched(pattern, "/5ra/new/a.c"); - assertMatched(pattern, "/new/1ra/a.c"); + assertNotMatched(pattern, "/3ra/a.c"); + assertNotMatched(pattern, "/5ra/new/a.c"); + assertNotMatched(pattern, "/new/1ra/a.c"); + assertNotMatched(pattern, "/new/1ra/a.c/"); assertMatched(pattern, "/file/dra"); + assertMatched(pattern, "/file/dra/"); assertMatched(pattern, "/era/"); assertNotMatched(pattern, "/crg"); assertNotMatched(pattern, "/cr3"); @@ -293,28 +315,28 @@ public void testGetters() { AttributesRule r = new AttributesRule("/pattern/", ""); assertFalse(r.isNameOnly()); - assertTrue(r.dirOnly()); + assertTrue(r.isDirOnly()); assertNotNull(r.getAttributes()); assertTrue(r.getAttributes().isEmpty()); assertEquals(r.getPattern(), "/pattern"); r = new AttributesRule("/patter?/", ""); assertFalse(r.isNameOnly()); - assertTrue(r.dirOnly()); + assertTrue(r.isDirOnly()); assertNotNull(r.getAttributes()); assertTrue(r.getAttributes().isEmpty()); assertEquals(r.getPattern(), "/patter?"); r = new AttributesRule("patt*", ""); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertTrue(r.getAttributes().isEmpty()); assertEquals(r.getPattern(), "patt*"); r = new AttributesRule("pattern", "attribute1"); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertFalse(r.getAttributes().isEmpty()); assertEquals(r.getAttributes().size(), 1); @@ -322,28 +344,28 @@ r = new AttributesRule("pattern", "attribute1 -attribute2"); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertEquals(r.getAttributes().size(), 2); assertEquals(r.getPattern(), "pattern"); r = new AttributesRule("pattern", "attribute1 \t-attribute2 \t"); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertEquals(r.getAttributes().size(), 2); assertEquals(r.getPattern(), "pattern"); r = new AttributesRule("pattern", "attribute1\t-attribute2\t"); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertEquals(r.getAttributes().size(), 2); assertEquals(r.getPattern(), "pattern"); r = new AttributesRule("pattern", "attribute1\t -attribute2\t "); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertEquals(r.getAttributes().size(), 2); assertEquals(r.getPattern(), "pattern"); @@ -351,7 +373,7 @@ r = new AttributesRule("pattern", "attribute1 -attribute2 attribute3=value "); assertTrue(r.isNameOnly()); - assertFalse(r.dirOnly()); + assertFalse(r.isDirOnly()); assertNotNull(r.getAttributes()); assertEquals(r.getAttributes().size(), 3); assertEquals(r.getPattern(), "pattern"); @@ -360,6 +382,39 @@ assertEquals(r.getAttributes().get(2).toString(), "attribute3=value"); } + @Test + public void testBracketsInGroup() { + //combinations of brackets in brackets, escaped and not + + String[] patterns = new String[]{"[[\\]]", "[\\[\\]]"}; + for (String pattern : patterns) { + assertNotMatched(pattern, ""); + assertNotMatched(pattern, "[]"); + assertNotMatched(pattern, "]["); + assertNotMatched(pattern, "[\\[]"); + assertNotMatched(pattern, "[[]"); + assertNotMatched(pattern, "[[]]"); + assertNotMatched(pattern, "[\\[\\]]"); + + assertMatched(pattern, "["); + assertMatched(pattern, "]"); + } + + patterns = new String[]{"[[]]", "[\\[]]"}; + for (String pattern : patterns) { + assertNotMatched(pattern, ""); + assertMatched(pattern, "[]"); + assertNotMatched(pattern, "]["); + assertNotMatched(pattern, "[\\[]"); + assertNotMatched(pattern, "[[]"); + assertNotMatched(pattern, "[[]]"); + assertNotMatched(pattern, "[\\[\\]]"); + + assertNotMatched(pattern, "["); + assertNotMatched(pattern, "]"); + } + } + /** * Check for a match. If target ends with "/", match will assume that the * target is meant to be a directory. @@ -369,7 +424,7 @@ * @param target * Target file path relative to repository's GIT_DIR */ - public void assertMatched(String pattern, String target) { + private void assertMatched(String pattern, String target) { boolean value = match(pattern, target); assertTrue("Expected a match for: " + pattern + " with: " + target, value); @@ -384,7 +439,7 @@ * @param target * Target file path relative to repository's GIT_DIR */ - public void assertNotMatched(String pattern, String target) { + private void assertNotMatched(String pattern, String target) { boolean value = match(pattern, target); assertFalse("Expected no match for: " + pattern + " with: " + target, value); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,9 +52,7 @@ import java.io.IOException; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.attributes.Attribute.State; @@ -243,27 +241,32 @@ DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class); assertNotNull("has tree", itr); - AttributesNode attributeNode = itr.getEntryAttributesNode(db + AttributesNode attributesNode = itr.getEntryAttributesNode(db .newObjectReader()); - assertAttributeNode(pathName, attributeNode, nodeAttrs); + assertAttributesNode(pathName, attributesNode, nodeAttrs); if (D.equals(type)) walk.enterSubtree(); } - private void assertAttributeNode(String pathName, - AttributesNode attributeNode, List nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) + throws IOException { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { - Map entryAttributes = new LinkedHashMap(); - attributeNode.getAttributes(pathName, false, entryAttributes); + Attributes entryAttributes = new Attributes(); + new AttributesHandler(walk).mergeAttributes(attributesNode, + pathName, + false, + entryAttributes); if (nodeAttrs != null && !nodeAttrs.isEmpty()) { for (Attribute attribute : nodeAttrs) { - assertThat(entryAttributes.values(), hasItem(attribute)); + assertThat(entryAttributes.getAll(), + hasItem(attribute)); } } else { assertTrue( diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2014, Obeo. + * 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.attributes; + +import static org.eclipse.jgit.attributes.Attribute.State.SET; +import static org.eclipse.jgit.attributes.Attribute.State.UNSET; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.After; +import org.junit.Test; + +/** + * Test {@link AttributesNode} + */ +public class AttributesNodeTest { + private static final TreeWalk DUMMY_WALK = new TreeWalk( + new InMemoryRepository(new DfsRepositoryDescription("FooBar"))); + + private static final Attribute A_SET_ATTR = new Attribute("A", SET); + + private static final Attribute A_UNSET_ATTR = new Attribute("A", UNSET); + + private static final Attribute B_SET_ATTR = new Attribute("B", SET); + + private static final Attribute B_UNSET_ATTR = new Attribute("B", UNSET); + + private static final Attribute C_VALUE_ATTR = new Attribute("C", "value"); + + private static final Attribute C_VALUE2_ATTR = new Attribute("C", "value2"); + + private InputStream is; + + @After + public void after() throws IOException { + if (is != null) + is.close(); + } + + @Test + public void testBasic() throws IOException { + String attributeFileContent = "*.type1 A -B C=value\n" + + "*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + } + + @Test + public void testNegativePattern() throws IOException { + String attributeFileContent = "!*.type1 A -B C=value\n" + + "!*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, new Attributes()); + assertAttribute("file.type2", node, new Attributes()); + } + + @Test + public void testEmptyNegativeAttributeKey() throws IOException { + String attributeFileContent = "*.type1 - \n" // + + "*.type2 - -A"; + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, new Attributes()); + assertAttribute("file.type2", node, asSet(A_UNSET_ATTR)); + } + + @Test + public void testEmptyValueKey() throws IOException { + String attributeFileContent = "*.type1 = \n" // + + "*.type2 =value\n"// + + "*.type3 attr=\n"; + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, new Attributes()); + assertAttribute("file.type2", node, new Attributes()); + assertAttribute("file.type3", node, asSet(new Attribute("attr", ""))); + } + + @Test + public void testEmptyLine() throws IOException { + String attributeFileContent = "*.type1 A -B C=value\n" // + + "\n" // + + " \n" // + + "*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + } + + @Test + public void testTabSeparator() throws IOException { + String attributeFileContent = "*.type1 \tA -B\tC=value\n" + + "*.type2\t -A\tB C=value2\n" // + + "*.type3 \t\t B\n" // + + "*.type3\t-A";// + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR)); + } + + @Test + public void testDoubleAsteriskAtEnd() throws IOException { + String attributeFileContent = "dir/** \tA -B\tC=value"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("dir", node, + asSet(new Attribute[]{})); + assertAttribute("dir/", node, + asSet(new Attribute[]{})); + assertAttribute("dir/file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("dir/sub/", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("dir/sub/file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + } + + private void assertAttribute(String path, AttributesNode node, + Attributes attrs) throws IOException { + Attributes attributes = new Attributes(); + new AttributesHandler(DUMMY_WALK).mergeAttributes(node, path, false, + attributes); + assertEquals(attrs, attributes); + } + + static Attributes asSet(Attribute... attrs) { + return new Attributes(attrs); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,12 +53,9 @@ import java.io.File; import java.io.IOException; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.eclipse.jgit.attributes.Attribute.State; -import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.FileMode; @@ -76,14 +73,10 @@ private static final FileMode F = FileMode.REGULAR_FILE; - private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); - private static Attribute EOL_LF = new Attribute("eol", "lf"); private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); - private static Attribute CUSTOM_VALUE = new Attribute("custom", "value"); - private TreeWalk walk; @Test @@ -112,25 +105,19 @@ walk = beginWalk(); assertIteration(F, ".gitattributes"); - assertIteration(F, "global.txt", asList(EOL_LF), null, - asList(CUSTOM_VALUE)); - assertIteration(F, "readme.txt", asList(EOL_LF), null, - asList(CUSTOM_VALUE)); + assertIteration(F, "global.txt", asList(EOL_LF)); + assertIteration(F, "readme.txt", asList(EOL_LF)); assertIteration(D, "src"); assertIteration(D, "src/config"); assertIteration(F, "src/config/.gitattributes"); - assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null, - asList(CUSTOM_VALUE)); - assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF), - null); - assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET), - asList(EOL_CRLF), asList(CUSTOM_VALUE)); - - assertIteration(F, "windows.file", null, asList(EOL_CRLF), null); - assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF), - asList(CUSTOM_VALUE)); + assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET)); + assertIteration(F, "src/config/windows.file", null); + assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET)); + + assertIteration(F, "windows.file", null); + assertIteration(F, "windows.txt", asList(EOL_LF)); endWalk(); } @@ -212,14 +199,11 @@ private void assertIteration(FileMode type, String pathName) throws IOException { - assertIteration(type, pathName, Collections. emptyList(), - Collections. emptyList(), - Collections. emptyList()); + assertIteration(type, pathName, Collections. emptyList()); } private void assertIteration(FileMode type, String pathName, - List nodeAttrs, List infoAttrs, - List globalAttrs) + List nodeAttrs) throws IOException { assertTrue("walk has entry", walk.next()); assertEquals(pathName, walk.getPathString()); @@ -227,29 +211,29 @@ WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class); assertNotNull("has tree", itr); - AttributesNode attributeNode = itr.getEntryAttributesNode(); - assertAttributeNode(pathName, attributeNode, nodeAttrs); - AttributesNode infoAttributeNode = itr.getInfoAttributesNode(); - assertAttributeNode(pathName, infoAttributeNode, infoAttrs); - AttributesNode globalAttributeNode = itr.getGlobalAttributesNode(); - assertAttributeNode(pathName, globalAttributeNode, globalAttrs); + AttributesNode attributesNode = itr.getEntryAttributesNode(); + assertAttributesNode(pathName, attributesNode, nodeAttrs); if (D.equals(type)) walk.enterSubtree(); } - private void assertAttributeNode(String pathName, - AttributesNode attributeNode, List nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) + throws IOException { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { - Map entryAttributes = new LinkedHashMap(); - attributeNode.getAttributes(pathName, false, entryAttributes); + Attributes entryAttributes = new Attributes(); + new AttributesHandler(walk).mergeAttributes(attributesNode, + pathName, false, + entryAttributes); if (nodeAttrs != null && !nodeAttrs.isEmpty()) { for (Attribute attribute : nodeAttrs) { - assertThat(entryAttributes.values(), hasItem(attribute)); + assertThat(entryAttributes.getAll(), + hasItem(attribute)); } } else { assertTrue( @@ -270,7 +254,7 @@ writeTrashFile(name, data.toString()); } - private TreeWalk beginWalk() throws CorruptObjectException { + private TreeWalk beginWalk() { TreeWalk newWalk = new TreeWalk(db); newWalk.addTree(new FileTreeIterator(db)); return newWalk; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2017 Thomas Wolf + * 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.attributes; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests that verify that the attributes of files in a repository are the same + * in JGit and in C-git. + */ +public class CGitAttributesTest extends RepositoryTestCase { + + @Before + public void initRepo() throws IOException { + // Because we run C-git, we must ensure that global or user exclude + // files cannot influence the tests. So we set core.excludesFile to an + // empty file inside the repository. + StoredConfig config = db.getConfig(); + File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", ""); + config.setString("core", null, "excludesFile", + fakeUserGitignore.getAbsolutePath()); + // Disable case-insensitivity -- JGit doesn't handle that yet. + config.setBoolean("core", null, "ignoreCase", false); + // And try to switch off the global attributes file, too. + config.setString("core", null, "attributesFile", + fakeUserGitignore.getAbsolutePath()); + config.save(); + } + + private void createFiles(String... paths) throws IOException { + for (String path : paths) { + writeTrashFile(path, "x"); + } + } + + private String toString(TemporaryBuffer b) throws IOException { + return RawParseUtils.decode(b.toByteArray()); + } + + private Attribute fromString(String key, String value) { + if ("set".equals(value)) { + return new Attribute(key, Attribute.State.SET); + } + if ("unset".equals(value)) { + return new Attribute(key, Attribute.State.UNSET); + } + if ("unspecified".equals(value)) { + return new Attribute(key, Attribute.State.UNSPECIFIED); + } + return new Attribute(key, value); + } + + private LinkedHashMap cgitAttributes( + Set allFiles) throws Exception { + FS fs = db.getFS(); + StringBuilder input = new StringBuilder(); + for (String filename : allFiles) { + input.append(filename).append('\n'); + } + ProcessBuilder builder = fs.runInShell("git", + new String[] { "check-attr", "--stdin", "--all" }); + builder.directory(db.getWorkTree()); + builder.environment().put("HOME", fs.userHome().getAbsolutePath()); + ExecutionResult result = fs.execute(builder, new ByteArrayInputStream( + input.toString().getBytes(Constants.CHARSET))); + String errorOut = toString(result.getStderr()); + assertEquals("External git failed", "exit 0\n", + "exit " + result.getRc() + '\n' + errorOut); + LinkedHashMap map = new LinkedHashMap<>(); + try (BufferedReader r = new BufferedReader(new InputStreamReader( + new BufferedInputStream(result.getStdout().openInputStream()), + Constants.CHARSET))) { + r.lines().forEach(line -> { + // Parse the line and add to result map + int start = 0; + int i = line.indexOf(':'); + String path = line.substring(0, i).trim(); + start = i + 1; + i = line.indexOf(':', start); + String key = line.substring(start, i).trim(); + String value = line.substring(i + 1).trim(); + Attribute attr = fromString(key, value); + Attributes attrs = map.get(path); + if (attrs == null) { + attrs = new Attributes(attr); + map.put(path, attrs); + } else { + attrs.put(attr); + } + }); + } + return map; + } + + private LinkedHashMap jgitAttributes() + throws IOException { + // Do a tree walk and return a list of all files and directories with + // their attributes + LinkedHashMap result = new LinkedHashMap<>(); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new FileTreeIterator(db)); + walk.setFilter(new NotIgnoredFilter(0)); + while (walk.next()) { + String path = walk.getPathString(); + if (walk.isSubtree() && !path.endsWith("/")) { + // git check-attr expects directory paths to end with a + // slash + path += '/'; + } + Attributes attrs = walk.getAttributes(); + if (attrs != null && !attrs.isEmpty()) { + result.put(path, attrs); + } else { + result.put(path, null); + } + if (walk.isSubtree()) { + walk.enterSubtree(); + } + } + } + return result; + } + + private void assertSameAsCGit() throws Exception { + LinkedHashMap jgit = jgitAttributes(); + LinkedHashMap cgit = cgitAttributes(jgit.keySet()); + // remove all without attributes + Iterator> iterator = jgit.entrySet() + .iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue() == null) { + iterator.remove(); + } + } + assertArrayEquals("JGit attributes differ from C git", + cgit.entrySet().toArray(), jgit.entrySet().toArray()); + } + + @Test + public void testBug508568() throws Exception { + createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar"); + writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n"); + assertSameAsCGit(); + } + + @Test + public void testRelativePath() throws Exception { + createFiles("sub/foo.txt"); + writeTrashFile("sub/.gitattributes", "sub/** sub\n" + "*.txt txt\n"); + assertSameAsCGit(); + } + + @Test + public void testRelativePaths() throws Exception { + createFiles("sub/foo.txt", "sub/sub/bar", "foo/sub/a.txt", + "foo/sub/bar/a.tmp"); + writeTrashFile(".gitattributes", "sub/** sub\n" + "*.txt txt\n"); + assertSameAsCGit(); + } + + @Test + public void testNestedMatchNot() throws Exception { + createFiles("foo.xml/bar.jar", "foo.xml/bar.xml", "sub/b.jar", + "sub/b.xml"); + writeTrashFile("sub/.gitattributes", "*.xml xml\n" + "*.jar jar\n"); + assertSameAsCGit(); + } + + @Test + public void testNestedMatch() throws Exception { + // This is an interesting test. At the time of this writing, the + // gitignore documentation says: "In other words, foo/ will match a + // directory foo AND PATHS UNDERNEATH IT, but will not match a regular + // file or a symbolic link foo". (Emphasis added.) And gitattributes is + // supposed to follow the same rules. But the documentation appears to + // lie: C-git will *not* apply the attribute "xml" to *any* files in + // any subfolder "foo" here. It will only apply the "jar" attribute + // to the three *.jar files. + // + // The point is probably that ignores are handled top-down, and once a + // directory "foo" is matched (here: on paths "foo" and "sub/foo" by + // pattern "foo/"), the directory is excluded and the gitignore + // documentation also says: "It is not possible to re-include a file if + // a parent directory of that file is excluded." So once the pattern + // "foo/" has matched, it appears as if everything beneath would also be + // matched. + // + // But not so for gitattributes! The foo/ rule only matches the + // directory itself, but not anything beneath. + createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml", + "sub/foo/b.jar"); + writeTrashFile(".gitattributes", + "foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n"); + assertSameAsCGit(); + } + + @Test + public void testNestedMatchWithWildcard() throws Exception { + // See above. + createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml", + "sub/foo/b.jar"); + writeTrashFile(".gitattributes", + "**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n"); + assertSameAsCGit(); + } + + @Test + public void testNestedMatchRecursive() throws Exception { + createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml", + "sub/foo/b.jar"); + writeTrashFile(".gitattributes", "foo/** xml\n" + "*.jar jar\n"); + assertSameAsCGit(); + } + + @Test + public void testStarMatchOnSlashNot() throws Exception { + createFiles("sub/a.txt", "foo/sext", "foo/s.txt"); + writeTrashFile(".gitattributes", "s*xt bar"); + assertSameAsCGit(); + } + + @Test + public void testPrefixMatchNot() throws Exception { + createFiles("src/new/foo.txt"); + writeTrashFile(".gitattributes", "src/new bar\n"); + assertSameAsCGit(); + } + + @Test + public void testComplexPathMatchNot() throws Exception { + createFiles("src/new/foo.txt", "src/ndw"); + writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n"); + assertSameAsCGit(); + } + + @Test + public void testStarPathMatchNot() throws Exception { + createFiles("src/new/foo.txt", "src/ndw"); + writeTrashFile(".gitattributes", "src/* bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubSimple() throws Exception { + createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new"); + writeTrashFile(".gitattributes", "src/new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursive() throws Exception { + createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new"); + writeTrashFile(".gitattributes", "**/src/new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack() throws Exception { + createFiles("src/new/foo.txt", "src/src/new/foo.txt"); + writeTrashFile(".gitattributes", "**/src/new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception { + createFiles("src/new/foo.txt", "src/src/new/foo.txt"); + writeTrashFile(".gitattributes", "**/**/src/new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception { + createFiles("src/new/src/new/foo.txt", + "foo/src/new/bar/src/new/foo.txt"); + writeTrashFile(".gitattributes", "**/src/new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception { + createFiles("src/src/src/new/foo.txt", + "foo/src/src/bar/src/new/foo.txt"); + writeTrashFile(".gitattributes", "**/src/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception { + createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt", + "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt"); + writeTrashFile(".gitattributes", "**/*/a/b bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception { + createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt"); + writeTrashFile(".gitattributes", "**/*/**/a/b bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles1() throws Exception { + createFiles("a", "dir/b", "dir/sub/c"); + writeTrashFile(".gitattributes", "**/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles2() throws Exception { + createFiles("a", "dir/b", "dir/sub/c"); + writeTrashFile(".gitattributes", "**/**/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles3() throws Exception { + createFiles("a", "x/b", "sub/x/c", "sub/x/d/e"); + writeTrashFile(".gitattributes", "x/**/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles4() throws Exception { + createFiles("a", "dir/x", "dir/sub1/x", "dir/sub2/x/y"); + writeTrashFile(".gitattributes", "x/**/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubComplex() throws Exception { + createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new"); + writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatch() throws Exception { + createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new"); + writeTrashFile(".gitattributes", "new/ bar\n"); + assertSameAsCGit(); + } + + @Test + public void testBracketsInGroup() throws Exception { + createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]"); + writeTrashFile(".gitattributes", "[[]] bar1\n" + "[\\[]] bar2\n" + + "[[\\]] bar3\n" + "[\\[\\]] bar4\n"); + assertSameAsCGit(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr) + * 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.attributes.merge; + +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.assertTrue; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.function.Consumer; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeResult; +import org.eclipse.jgit.api.MergeResult.MergeStatus; +import org.eclipse.jgit.api.errors.CheckoutConflictException; +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidMergeHeadsException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.api.errors.NoMessageException; +import org.eclipse.jgit.api.errors.WrongRepositoryStateException; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.junit.Ignore; +import org.junit.Test; + +public class MergeGitAttributeTest extends RepositoryTestCase { + + private static final String REFS_HEADS_RIGHT = "refs/heads/right"; + + private static final String REFS_HEADS_MASTER = "refs/heads/master"; + + private static final String REFS_HEADS_LEFT = "refs/heads/left"; + + private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked"; + + private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked"; + + private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif"; + + public Git createRepositoryBinaryConflict(Consumer initialCommit, + Consumer leftCommit, Consumer rightCommit) + throws NoFilepatternException, GitAPIException, NoWorkTreeException, + IOException { + // Set up a git whith conflict commits on images + Git git = new Git(db); + + // First commit + initialCommit.accept(git); + git.add().addFilepattern(".").call(); + RevCommit firstCommit = git.commit().setAll(true) + .setMessage("initial commit adding git attribute file").call(); + + // Create branch and add an icon Checked_Boxe (enabled_checked) + createBranch(firstCommit, REFS_HEADS_LEFT); + checkoutBranch(REFS_HEADS_LEFT); + leftCommit.accept(git); + git.add().addFilepattern(".").call(); + git.commit().setMessage("Left").call(); + + // Create a second branch from master Unchecked_Boxe + checkoutBranch(REFS_HEADS_MASTER); + createBranch(firstCommit, REFS_HEADS_RIGHT); + checkoutBranch(REFS_HEADS_RIGHT); + rightCommit.accept(git); + git.add().addFilepattern(".").call(); + git.commit().setMessage("Right").call(); + + checkoutBranch(REFS_HEADS_LEFT); + return git; + + } + + @Test + public void mergeTextualFile_NoAttr() throws NoWorkTreeException, + NoFilepatternException, GitAPIException, IOException { + try (Git git = createRepositoryBinaryConflict(g -> { + try { + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + })) { + checkoutBranch(REFS_HEADS_LEFT); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + + MergeResult mergeResult = git.merge() + .include(git.getRepository().resolve(REFS_HEADS_RIGHT)) + .call(); + assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus()); + + assertNull(mergeResult.getConflicts()); + + // Check that the image was not modified (not conflict marker added) + String result = read( + writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n")); + assertEquals(result, read(git.getRepository().getWorkTree().toPath() + .resolve("main.cat").toFile())); + } + } + + @Test + public void mergeTextualFile_UnsetMerge_Conflict() + throws NoWorkTreeException, NoFilepatternException, GitAPIException, + IOException { + try (Git git = createRepositoryBinaryConflict(g -> { + try { + writeTrashFile(".gitattributes", "*.cat -merge"); + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + })) { + // Check that the merge attribute is unset + assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat"); + assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat"); + + checkoutBranch(REFS_HEADS_LEFT); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + + String catContent = read(git.getRepository().getWorkTree().toPath() + .resolve("main.cat").toFile()); + + MergeResult mergeResult = git.merge() + .include(git.getRepository().resolve(REFS_HEADS_RIGHT)) + .call(); + assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus()); + + // Check that the image was not modified (not conflict marker added) + assertEquals(catContent, read(git.getRepository().getWorkTree() + .toPath().resolve("main.cat").toFile())); + } + } + + @Test + public void mergeTextualFile_UnsetMerge_NoConflict() + throws NoWorkTreeException, NoFilepatternException, GitAPIException, + IOException { + try (Git git = createRepositoryBinaryConflict(g -> { + try { + writeTrashFile(".gitattributes", "*.txt -merge"); + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + })) { + // Check that the merge attribute is unset + assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat"); + assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat"); + + checkoutBranch(REFS_HEADS_LEFT); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + + MergeResult mergeResult = git.merge() + .include(git.getRepository().resolve(REFS_HEADS_RIGHT)) + .call(); + assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus()); + + // Check that the image was not modified (not conflict marker added) + String result = read( + writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n")); + assertEquals(result, read(git.getRepository().getWorkTree() + .toPath().resolve("main.cat").toFile())); + } + } + + @Test + public void mergeTextualFile_SetBinaryMerge_Conflict() + throws NoWorkTreeException, NoFilepatternException, GitAPIException, + IOException { + try (Git git = createRepositoryBinaryConflict(g -> { + try { + writeTrashFile(".gitattributes", "*.cat merge=binary"); + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n"); + } catch (IOException e) { + e.printStackTrace(); + } + }, g -> { + try { + writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n"); + } catch (IOException e) { + e.printStackTrace(); + } + })) { + // Check that the merge attribute is set to binary + assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat", + "binary"); + assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat", + "binary"); + + checkoutBranch(REFS_HEADS_LEFT); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + + String catContent = read(git.getRepository().getWorkTree().toPath() + .resolve("main.cat").toFile()); + + MergeResult mergeResult = git.merge() + .include(git.getRepository().resolve(REFS_HEADS_RIGHT)) + .call(); + assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus()); + + // Check that the image was not modified (not conflict marker added) + assertEquals(catContent, read(git.getRepository().getWorkTree() + .toPath().resolve("main.cat").toFile())); + } + } + + /* + * This test is commented because JGit add conflict markers in binary files. + * cf. https://www.eclipse.org/forums/index.php/t/1086511/ + */ + @Test + @Ignore + public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException, + IOException, NoHeadException, ConcurrentRefUpdateException, + CheckoutConflictException, InvalidMergeHeadsException, + WrongRepositoryStateException, NoMessageException, GitAPIException { + + RevCommit disableCheckedCommit; + // Set up a git with conflict commits on images + try (Git git = new Git(db)) { + // First commit + write(new File(db.getWorkTree(), ".gitattributes"), ""); + git.add().addFilepattern(".gitattributes").call(); + RevCommit firstCommit = git.commit() + .setMessage("initial commit adding git attribute file") + .call(); + + // Create branch and add an icon Checked_Boxe (enabled_checked) + createBranch(firstCommit, ENABLE_CHECKED_BRANCH); + checkoutBranch(ENABLE_CHECKED_BRANCH); + copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + git.commit().setMessage("enabled_checked commit").call(); + + // Create a second branch from master Unchecked_Boxe + checkoutBranch(REFS_HEADS_MASTER); + createBranch(firstCommit, DISABLE_CHECK_BRANCH); + checkoutBranch(DISABLE_CHECK_BRANCH); + copy("disabled_checked.gif", ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + disableCheckedCommit = git.commit() + .setMessage("disabled_checked commit").call(); + + // Check that the merge attribute is unset + assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH, + ENABLED_CHECKED_GIF); + assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH, + ENABLED_CHECKED_GIF); + + checkoutBranch(ENABLE_CHECKED_BRANCH); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + MergeResult mergeResult = git.merge().include(disableCheckedCommit) + .call(); + assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus()); + + // Check that the image was not modified (no conflict marker added) + try (FileInputStream mergeResultFile = new FileInputStream( + db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF) + .toFile())) { + assertTrue(contentEquals( + getClass().getResourceAsStream(ENABLED_CHECKED_GIF), + mergeResultFile)); + } + } + } + + @Test + public void mergeBinaryFile_UnsetMerge_Conflict() + throws IllegalStateException, + IOException, NoHeadException, ConcurrentRefUpdateException, + CheckoutConflictException, InvalidMergeHeadsException, + WrongRepositoryStateException, NoMessageException, GitAPIException { + + RevCommit disableCheckedCommit; + // Set up a git whith conflict commits on images + try (Git git = new Git(db)) { + // First commit + write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge"); + git.add().addFilepattern(".gitattributes").call(); + RevCommit firstCommit = git.commit() + .setMessage("initial commit adding git attribute file") + .call(); + + // Create branch and add an icon Checked_Boxe (enabled_checked) + createBranch(firstCommit, ENABLE_CHECKED_BRANCH); + checkoutBranch(ENABLE_CHECKED_BRANCH); + copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + git.commit().setMessage("enabled_checked commit").call(); + + // Create a second branch from master Unchecked_Boxe + checkoutBranch(REFS_HEADS_MASTER); + createBranch(firstCommit, DISABLE_CHECK_BRANCH); + checkoutBranch(DISABLE_CHECK_BRANCH); + copy("disabled_checked.gif", ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + disableCheckedCommit = git.commit() + .setMessage("disabled_checked commit").call(); + + // Check that the merge attribute is unset + assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH, + ENABLED_CHECKED_GIF); + assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH, + ENABLED_CHECKED_GIF); + + checkoutBranch(ENABLE_CHECKED_BRANCH); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + MergeResult mergeResult = git.merge().include(disableCheckedCommit) + .call(); + assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus()); + + // Check that the image was not modified (not conflict marker added) + try (FileInputStream mergeResultFile = new FileInputStream( + db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF) + .toFile())) { + assertTrue(contentEquals( + getClass().getResourceAsStream(ENABLED_CHECKED_GIF), + mergeResultFile)); + } + } + } + + @Test + public void mergeBinaryFile_SetMerge_Conflict() + throws IllegalStateException, IOException, NoHeadException, + ConcurrentRefUpdateException, CheckoutConflictException, + InvalidMergeHeadsException, WrongRepositoryStateException, + NoMessageException, GitAPIException { + + RevCommit disableCheckedCommit; + // Set up a git whith conflict commits on images + try (Git git = new Git(db)) { + // First commit + write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge"); + git.add().addFilepattern(".gitattributes").call(); + RevCommit firstCommit = git.commit() + .setMessage("initial commit adding git attribute file") + .call(); + + // Create branch and add an icon Checked_Boxe (enabled_checked) + createBranch(firstCommit, ENABLE_CHECKED_BRANCH); + checkoutBranch(ENABLE_CHECKED_BRANCH); + copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + git.commit().setMessage("enabled_checked commit").call(); + + // Create a second branch from master Unchecked_Boxe + checkoutBranch(REFS_HEADS_MASTER); + createBranch(firstCommit, DISABLE_CHECK_BRANCH); + checkoutBranch(DISABLE_CHECK_BRANCH); + copy("disabled_checked.gif", ENABLED_CHECKED_GIF, ""); + git.add().addFilepattern(ENABLED_CHECKED_GIF).call(); + disableCheckedCommit = git.commit() + .setMessage("disabled_checked commit").call(); + + // Check that the merge attribute is set + assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH, + ENABLED_CHECKED_GIF); + assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH, + ENABLED_CHECKED_GIF); + + checkoutBranch(ENABLE_CHECKED_BRANCH); + // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked + MergeResult mergeResult = git.merge().include(disableCheckedCommit) + .call(); + assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus()); + + // Check that the image was not modified (not conflict marker added) + try (FileInputStream mergeResultFile = new FileInputStream( + db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF) + .toFile())) { + assertFalse(contentEquals( + getClass().getResourceAsStream(ENABLED_CHECKED_GIF), + mergeResultFile)); + } + } + } + + /* + * Copied from org.apache.commons.io.IOUtils + */ + private boolean contentEquals(InputStream input1, InputStream input2) + throws IOException { + if (input1 == input2) { + return true; + } + if (!(input1 instanceof BufferedInputStream)) { + input1 = new BufferedInputStream(input1); + } + if (!(input2 instanceof BufferedInputStream)) { + input2 = new BufferedInputStream(input2); + } + + int ch = input1.read(); + while (-1 != ch) { + final int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + final int ch2 = input2.read(); + return ch2 == -1; + } + + private void assertAddMergeAttributeUnset(String branch, String fileName) + throws IllegalStateException, IOException { + checkoutBranch(branch); + + try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) { + treeWaklEnableChecked.addTree(new FileTreeIterator(db)); + treeWaklEnableChecked.setFilter(PathFilter.create(fileName)); + + assertTrue(treeWaklEnableChecked.next()); + Attributes attributes = treeWaklEnableChecked.getAttributes(); + Attribute mergeAttribute = attributes.get("merge"); + assertNotNull(mergeAttribute); + assertEquals(Attribute.State.UNSET, mergeAttribute.getState()); + } + } + + private void assertAddMergeAttributeSet(String branch, String fileName) + throws IllegalStateException, IOException { + checkoutBranch(branch); + + try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) { + treeWaklEnableChecked.addTree(new FileTreeIterator(db)); + treeWaklEnableChecked.setFilter(PathFilter.create(fileName)); + + assertTrue(treeWaklEnableChecked.next()); + Attributes attributes = treeWaklEnableChecked.getAttributes(); + Attribute mergeAttribute = attributes.get("merge"); + assertNotNull(mergeAttribute); + assertEquals(Attribute.State.SET, mergeAttribute.getState()); + } + } + + private void assertAddMergeAttributeUndefined(String branch, + String fileName) throws IllegalStateException, IOException { + checkoutBranch(branch); + + try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) { + treeWaklEnableChecked.addTree(new FileTreeIterator(db)); + treeWaklEnableChecked.setFilter(PathFilter.create(fileName)); + + assertTrue(treeWaklEnableChecked.next()); + Attributes attributes = treeWaklEnableChecked.getAttributes(); + Attribute mergeAttribute = attributes.get("merge"); + assertNull(mergeAttribute); + } + } + + private void assertAddMergeAttributeCustom(String branch, String fileName, + String value) throws IllegalStateException, IOException { + checkoutBranch(branch); + + try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) { + treeWaklEnableChecked.addTree(new FileTreeIterator(db)); + treeWaklEnableChecked.setFilter(PathFilter.create(fileName)); + + assertTrue(treeWaklEnableChecked.next()); + Attributes attributes = treeWaklEnableChecked.getAttributes(); + Attribute mergeAttribute = attributes.get("merge"); + assertNotNull(mergeAttribute); + assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState()); + assertEquals(value, mergeAttribute.getValue()); + } + } + + private void copy(String resourcePath, String resourceNewName, + String pathInRepo) throws IOException { + InputStream input = getClass().getResourceAsStream(resourcePath); + Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo) + .resolve(resourceNewName)); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2014, Obeo. + * 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.attributes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests the attributes are correctly computed in a {@link TreeWalk}. + * + * @see TreeWalk#getAttributes() + */ +public class TreeWalkAttributeTest extends RepositoryTestCase { + + private static final FileMode M = FileMode.MISSING; + + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); + + private static Attribute EOL_LF = new Attribute("eol", "lf"); + + private static Attribute TEXT_SET = new Attribute("text", State.SET); + + private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET); + + private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); + + private static Attribute DELTA_SET = new Attribute("delta", State.SET); + + private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global"); + + private static Attribute CUSTOM_INFO = new Attribute("custom", "info"); + + private static Attribute CUSTOM_ROOT = new Attribute("custom", "root"); + + private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent"); + + private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current"); + + private static Attribute CUSTOM2_UNSET = new Attribute("custom2", + State.UNSET); + + private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET); + + private TreeWalk walk; + + private TreeWalk ci_walk; + + private Git git; + + private File customAttributeFile; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + if (customAttributeFile != null) + customAttributeFile.delete(); + } + + /** + * Checks that the attributes are computed correctly depending on the + * operation type. + *

        + * In this test we changed the content of the attribute files in the working + * tree compared to the one in the index. + *

        + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testCheckinCheckoutDifferences() throws IOException, + NoFilepatternException, GitAPIException { + + writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + git.add().addFilepattern(".").call(); + + beginWalk(); + + // Modify all attributes + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=lf"); + writeAttributesFile(".gitattributes", "*.txt custom=info"); + writeAttributesFile("level1/.gitattributes", "*.txt -text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt delta"); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.txt", + asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET)); + + endWalk(); + } + + /** + * Checks that the index is used as fallback when the git attributes file + * are missing in the working tree. + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testIndexOnly() throws IOException, NoFilepatternException, + GitAPIException { + List attrFiles = new ArrayList<>(); + attrFiles.add(writeGlobalAttributeFile("globalAttributesFile", + "*.txt -custom2")); + attrFiles.add(writeAttributesFile(".git/info/attributes", + "*.txt eol=crlf")); + attrFiles + .add(writeAttributesFile(".gitattributes", "*.txt custom=root")); + attrFiles + .add(writeAttributesFile("level1/.gitattributes", "*.txt text")); + attrFiles.add(writeAttributesFile("level1/level2/.gitattributes", + "*.txt -delta")); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + git.add().addFilepattern(".").call(); + + // Modify all attributes + for (File attrFile : attrFiles) + attrFile.delete(); + + beginWalk(); + + assertEntry(M, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT)); + + assertEntry(D, "level1"); + assertEntry(M, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + + asSet(CUSTOM_ROOT, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(M, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.txt", + + asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET)); + + endWalk(); + } + + /** + * Check that we search in the working tree for attributes although the file + * we are currently inspecting does not exist anymore in the working tree. + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testIndexOnly2() + throws IOException, NoFilepatternException, GitAPIException { + File l2 = writeTrashFile("level1/level2/l2.txt", ""); + writeTrashFile("level1/level2/l1.txt", ""); + + git.add().addFilepattern(".").call(); + + writeAttributesFile(".gitattributes", "*.txt custom=root"); + assertTrue(l2.delete()); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(D, "level1"); + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT)); + assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT)); + + endWalk(); + } + + /** + * Basic test for git attributes. + *

        + * In this use case files are present in both the working tree and the index + *

        + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testRules() throws IOException, NoFilepatternException, + GitAPIException { + writeAttributesFile(".git/info/attributes", "windows* eol=crlf"); + + writeAttributesFile(".gitattributes", "*.txt eol=lf"); + writeTrashFile("windows.file", ""); + writeTrashFile("windows.txt", ""); + writeTrashFile("readme.txt", ""); + + writeAttributesFile("src/config/.gitattributes", "*.txt -delta"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + writeTrashFile("src/config/windows.txt", ""); + + beginWalk(); + + git.add().addFilepattern(".").call(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "readme.txt", asSet(EOL_LF)); + + assertEntry(D, "src"); + assertEntry(D, "src/config"); + assertEntry(F, "src/config/.gitattributes"); + assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF)); + assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF)); + assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF)); + + assertEntry(F, "windows.file", asSet(EOL_CRLF)); + assertEntry(F, "windows.txt", asSet(EOL_CRLF)); + + endWalk(); + } + + /** + * Checks that if there is no .gitattributes file in the repository + * everything still work fine. + * + * @throws IOException + */ + @Test + public void testNoAttributes() throws IOException { + writeTrashFile("l0.txt", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, "l0.txt"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/l1.txt"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l2.txt"); + + endWalk(); + } + + /** + * Checks that an empty .gitattribute file does not return incorrect value. + * + * @throws IOException + */ + @Test + public void testEmptyGitAttributeFile() throws IOException { + writeAttributesFile(".git/info/attributes", ""); + writeTrashFile("l0.txt", ""); + writeAttributesFile(".gitattributes", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/l1.txt"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l2.txt"); + + endWalk(); + } + + @Test + public void testNoMatchingAttributes() throws IOException { + writeAttributesFile(".git/info/attributes", "*.java delta"); + writeAttributesFile(".gitattributes", "*.java -delta"); + writeAttributesFile("levelA/.gitattributes", "*.java eol=lf"); + writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf"); + + writeTrashFile("levelA/lA.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "levelA"); + assertEntry(F, "levelA/.gitattributes"); + assertEntry(F, "levelA/lA.txt"); + + assertEntry(D, "levelB"); + assertEntry(F, "levelB/.gitattributes"); + + endWalk(); + } + + /** + * Checks that $GIT_DIR/info/attributes file has the highest precedence. + * + * @throws IOException + */ + @Test + public void testPrecedenceInfo() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".git/info/attributes", "*.txt custom=info"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + writeAttributesFile("level1/level2/.gitattributes", + "*.txt custom=current"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO)); + + endWalk(); + } + + /** + * Checks that a subfolder ".gitattributes" file has precedence over its + * parent. + * + * @throws IOException + */ + @Test + public void testPrecedenceCurrent() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + writeAttributesFile("level1/level2/.gitattributes", + "*.txt custom=current"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT)); + + endWalk(); + } + + /** + * Checks that the parent ".gitattributes" file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceParent() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT)); + + endWalk(); + } + + /** + * Checks that the grand parent ".gitattributes" file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceRoot() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT)); + + endWalk(); + } + + /** + * Checks that the global attribute file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceGlobal() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(D, "level1"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL)); + + endWalk(); + } + + /** + * Checks the precedence on a hierarchy with multiple attributes. + *

        + * In this test all file are present in both the working tree and the index. + *

        + * + * @throws IOException + * @throws GitAPIException + * @throws NoFilepatternException + */ + @Test + public void testHierarchyBothIterator() throws IOException, + NoFilepatternException, GitAPIException { + writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); + writeAttributesFile(".gitattributes", "*.local eol=lf"); + writeAttributesFile("level1/.gitattributes", "*.local text"); + writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); + + writeTrashFile("l0.global", ""); + writeTrashFile("l0.local", ""); + + writeTrashFile("level1/l1.global", ""); + writeTrashFile("level1/l1.local", ""); + + writeTrashFile("level1/level2/l2.global", ""); + writeTrashFile("level1/level2/l2.local", ""); + + beginWalk(); + + git.add().addFilepattern(".").call(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.global", asSet(EOL_CRLF)); + assertEntry(F, "l0.local", asSet(EOL_LF)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); + + endWalk(); + + } + + /** + * Checks the precedence on a hierarchy with multiple attributes. + *

        + * In this test all file are present only in the working tree. + *

        + * + * @throws IOException + * @throws GitAPIException + * @throws NoFilepatternException + */ + @Test + public void testHierarchyWorktreeOnly() + throws IOException, NoFilepatternException, GitAPIException { + writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); + writeAttributesFile(".gitattributes", "*.local eol=lf"); + writeAttributesFile("level1/.gitattributes", "*.local text"); + writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); + + writeTrashFile("l0.global", ""); + writeTrashFile("l0.local", ""); + + writeTrashFile("level1/l1.global", ""); + writeTrashFile("level1/l1.local", ""); + + writeTrashFile("level1/level2/l2.global", ""); + writeTrashFile("level1/level2/l2.local", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.global", asSet(EOL_CRLF)); + assertEntry(F, "l0.local", asSet(EOL_LF)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); + + endWalk(); + + } + + /** + * Checks that the list of attributes is an aggregation of all the + * attributes from the attributes files hierarchy. + * + * @throws IOException + */ + @Test + public void testAggregation() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry( + F, + "level1/level2/l2.txt", + asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET, + CUSTOM2_UNSET)); + + endWalk(); + + } + + /** + * Checks that the last entry in .gitattributes is used if 2 lines match the + * same attribute + * + * @throws IOException + */ + @Test + public void testOverriding() throws IOException { + writeAttributesFile(".git/info/attributes",// + // + "*.txt custom=current",// + "*.txt custom=parent",// + "*.txt custom=root",// + "*.txt custom=info", + // + "*.txt delta",// + "*.txt -delta", + // + "*.txt eol=lf",// + "*.txt eol=crlf", + // + "*.txt text",// + "*.txt -text"); + + writeTrashFile("l0.txt", ""); + beginWalk(); + + assertEntry(F, "l0.txt", + asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); + + endWalk(); + } + + /** + * Checks that the last value of an attribute is used if in the same line an + * attribute is defined several time. + * + * @throws IOException + */ + @Test + public void testOverriding2() throws IOException { + writeAttributesFile(".git/info/attributes", + "*.txt custom=current custom=parent custom=root custom=info",// + "*.txt delta -delta",// + "*.txt eol=lf eol=crlf",// + "*.txt text -text"); + writeTrashFile("l0.txt", ""); + beginWalk(); + + assertEntry(F, "l0.txt", + asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); + + endWalk(); + } + + @Test + public void testRulesInherited() throws Exception { + writeAttributesFile(".gitattributes", "**/*.txt eol=lf"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(D, "src"); + assertEntry(D, "src/config"); + + assertEntry(F, "src/config/readme.txt", asSet(EOL_LF)); + assertEntry(F, "src/config/windows.file", + Collections. emptySet()); + + endWalk(); + } + + private void beginWalk() throws NoWorkTreeException, IOException { + walk = new TreeWalk(db); + walk.addTree(new FileTreeIterator(db)); + walk.addTree(new DirCacheIterator(db.readDirCache())); + + ci_walk = new TreeWalk(db); + ci_walk.setOperationType(OperationType.CHECKIN_OP); + ci_walk.addTree(new FileTreeIterator(db)); + ci_walk.addTree(new DirCacheIterator(db.readDirCache())); + } + + /** + * Assert an entry in which checkin and checkout attributes are expected to + * be the same. + * + * @param type + * @param pathName + * @param forBothOperaiton + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName, + Set forBothOperaiton) throws IOException { + assertEntry(type, pathName, forBothOperaiton, forBothOperaiton); + } + + /** + * Assert an entry with no attribute expected. + * + * @param type + * @param pathName + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName) throws IOException { + assertEntry(type, pathName, Collections. emptySet(), + Collections. emptySet()); + } + + /** + * Assert that an entry; + *
          + *
        • Has the correct type
        • + *
        • Exist in the tree walk
        • + *
        • Has the expected attributes on a checkin operation
        • + *
        • Has the expected attributes on a checkout operation
        • + *
        + * + * @param type + * @param pathName + * @param checkinAttributes + * @param checkoutAttributes + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName, + Set checkinAttributes, Set checkoutAttributes) + throws IOException { + assertTrue("walk has entry", walk.next()); + assertTrue("walk has entry", ci_walk.next()); + assertEquals(pathName, walk.getPathString()); + assertEquals(type, walk.getFileMode(0)); + + assertEquals(checkinAttributes, + asSet(ci_walk.getAttributes().getAll())); + assertEquals(checkoutAttributes, + asSet(walk.getAttributes().getAll())); + + if (D.equals(type)) { + walk.enterSubtree(); + ci_walk.enterSubtree(); + } + } + + private static Set asSet(Collection attributes) { + Set ret = new HashSet<>(); + for (Attribute a : attributes) { + ret.add(a); + } + return (ret); + } + + private File writeAttributesFile(String name, String... rules) + throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) + data.append(line + "\n"); + return writeTrashFile(name, data.toString()); + } + + /** + * Creates an attributes file and set its location in the git configuration. + * + * @param fileName + * @param attributes + * @return The attribute file + * @throws IOException + * @see Repository#getConfig() + */ + private File writeGlobalAttributeFile(String fileName, String... attributes) + throws IOException { + customAttributeFile = File.createTempFile("tmp_", fileName, null); + customAttributeFile.deleteOnExit(); + StringBuilder attributesFileContent = new StringBuilder(); + for (String attr : attributes) { + attributesFileContent.append(attr).append("\n"); + } + JGitTestUtil.write(customAttributeFile, + attributesFileContent.toString()); + db.getConfig().setString("core", null, "attributesfile", + customAttributeFile.getAbsolutePath()); + return customAttributeFile; + } + + static Set asSet(Attribute... attrs) { + HashSet result = new HashSet<>(); + for (Attribute attr : attrs) + result.add(attr); + return result; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + assertFalse("Not all files tested", ci_walk.next()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,10 @@ package org.eclipse.jgit.diff; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.io.UnsupportedEncodingException; - import org.junit.Test; public abstract class AbstractDiffTestCase { @@ -196,6 +195,32 @@ } @Test + public void testEdit_DeleteNearCommonTail() { + EditList r = diff(t("aCq}nD}nb"), t("aq}nb")); + assertEquals(new Edit(1, 2, 1, 1), r.get(0)); + assertEquals(new Edit(5, 8, 4, 4), r.get(1)); + assertEquals(2, r.size()); + } + + @Test + public void testEdit_DeleteNearCommonCenter() { + EditList r = diff(t("abcd123123uvwxpq"), t("aBcd123uvwxPq")); + assertEquals(new Edit(1, 2, 1, 2), r.get(0)); + assertEquals(new Edit(7, 10, 7, 7), r.get(1)); + assertEquals(new Edit(14, 15, 11, 12), r.get(2)); + assertEquals(3, r.size()); + } + + @Test + public void testEdit_InsertNearCommonCenter() { + EditList r = diff(t("aBcd123uvwxPq"), t("abcd123123uvwxpq")); + assertEquals(new Edit(1, 2, 1, 2), r.get(0)); + assertEquals(new Edit(7, 7, 7, 10), r.get(1)); + assertEquals(new Edit(11, 12, 14, 15), r.get(2)); + assertEquals(3, r.size()); + } + + @Test public void testEdit_LinuxBug() { EditList r = diff(t("a{bcdE}z"), t("a{0bcdEE}z")); assertEquals(new Edit(2, 2, 2, 3), r.get(0)); @@ -215,10 +240,6 @@ r.append(text.charAt(i)); r.append('\n'); } - try { - return new RawText(r.toString().getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return new RawText(r.toString().getBytes(UTF_8)); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,260 +77,268 @@ public void shouldListAddedFileInInitialCommit() throws Exception { // given writeTrashFile("a.txt", "content"); - Git git = new Git(db); - git.add().addFilepattern("a.txt").call(); - RevCommit c = git.commit().setMessage("initial commit").call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(new EmptyTreeIterator()); - walk.addTree(c.getTree()); - List result = DiffEntry.scan(walk); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - - DiffEntry entry = result.get(0); - assertThat(entry.getChangeType(), is(ChangeType.ADD)); - assertThat(entry.getNewPath(), is("a.txt")); - assertThat(entry.getOldPath(), is(DEV_NULL)); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + git.add().addFilepattern("a.txt").call(); + RevCommit c = git.commit().setMessage("initial commit").call(); + + // when + walk.addTree(new EmptyTreeIterator()); + walk.addTree(c.getTree()); + List result = DiffEntry.scan(walk); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + + DiffEntry entry = result.get(0); + assertThat(entry.getChangeType(), is(ChangeType.ADD)); + assertThat(entry.getNewPath(), is("a.txt")); + assertThat(entry.getOldPath(), is(DEV_NULL)); + } } @Test public void shouldListAddedFileBetweenTwoCommits() throws Exception { // given - Git git = new Git(db); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - writeTrashFile("a.txt", "content"); - git.add().addFilepattern("a.txt").call(); - RevCommit c2 = git.commit().setMessage("second commit").call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List result = DiffEntry.scan(walk); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - - DiffEntry entry = result.get(0); - assertThat(entry.getChangeType(), is(ChangeType.ADD)); - assertThat(entry.getNewPath(), is("a.txt")); - assertThat(entry.getOldPath(), is(DEV_NULL)); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + RevCommit c1 = git.commit().setMessage("initial commit").call(); + writeTrashFile("a.txt", "content"); + git.add().addFilepattern("a.txt").call(); + RevCommit c2 = git.commit().setMessage("second commit").call(); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + + DiffEntry entry = result.get(0); + assertThat(entry.getChangeType(), is(ChangeType.ADD)); + assertThat(entry.getNewPath(), is("a.txt")); + assertThat(entry.getOldPath(), is(DEV_NULL)); + } } @Test public void shouldListModificationBetweenTwoCommits() throws Exception { // given - Git git = new Git(db); - File file = writeTrashFile("a.txt", "content"); - git.add().addFilepattern("a.txt").call(); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - write(file, "new content"); - RevCommit c2 = git.commit().setAll(true).setMessage("second commit") - .call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List result = DiffEntry.scan(walk); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - - DiffEntry entry = result.get(0); - assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); - assertThat(entry.getNewPath(), is("a.txt")); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + File file = writeTrashFile("a.txt", "content"); + git.add().addFilepattern("a.txt").call(); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + write(file, "new content"); + RevCommit c2 = git.commit().setAll(true).setMessage("second commit") + .call(); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + + DiffEntry entry = result.get(0); + assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); + assertThat(entry.getNewPath(), is("a.txt")); + } } @Test public void shouldListDeletionBetweenTwoCommits() throws Exception { // given - Git git = new Git(db); - File file = writeTrashFile("a.txt", "content"); - git.add().addFilepattern("a.txt").call(); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - delete(file); - RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt") - .call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List result = DiffEntry.scan(walk); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - - DiffEntry entry = result.get(0); - assertThat(entry.getOldPath(), is("a.txt")); - assertThat(entry.getNewPath(), is(DEV_NULL)); - assertThat(entry.getChangeType(), is(ChangeType.DELETE)); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + File file = writeTrashFile("a.txt", "content"); + git.add().addFilepattern("a.txt").call(); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + delete(file); + RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt") + .call(); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + + DiffEntry entry = result.get(0); + assertThat(entry.getOldPath(), is("a.txt")); + assertThat(entry.getNewPath(), is(DEV_NULL)); + assertThat(entry.getChangeType(), is(ChangeType.DELETE)); + } } @Test public void shouldListModificationInDirWithoutModifiedTrees() throws Exception { // given - Git git = new Git(db); - File tree = new File(new File(db.getWorkTree(), "a"), "b"); - FileUtils.mkdirs(tree); - File file = new File(tree, "c.txt"); - FileUtils.createNewFile(file); - write(file, "content"); - git.add().addFilepattern("a").call(); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - write(file, "new line"); - RevCommit c2 = git.commit().setAll(true).setMessage("second commit") - .call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - walk.setRecursive(true); - List result = DiffEntry.scan(walk); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - - DiffEntry entry = result.get(0); - assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); - assertThat(entry.getNewPath(), is("a/b/c.txt")); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + File tree = new File(new File(db.getWorkTree(), "a"), "b"); + FileUtils.mkdirs(tree); + File file = new File(tree, "c.txt"); + FileUtils.createNewFile(file); + write(file, "content"); + git.add().addFilepattern("a").call(); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + write(file, "new line"); + RevCommit c2 = git.commit().setAll(true).setMessage("second commit") + .call(); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + walk.setRecursive(true); + List result = DiffEntry.scan(walk); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + + DiffEntry entry = result.get(0); + assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); + assertThat(entry.getNewPath(), is("a/b/c.txt")); + } } @Test public void shouldListModificationInDirWithModifiedTrees() throws Exception { // given - Git git = new Git(db); - File tree = new File(new File(db.getWorkTree(), "a"), "b"); - FileUtils.mkdirs(tree); - File file = new File(tree, "c.txt"); - FileUtils.createNewFile(file); - write(file, "content"); - git.add().addFilepattern("a").call(); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - write(file, "new line"); - RevCommit c2 = git.commit().setAll(true).setMessage("second commit") - .call(); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List result = DiffEntry.scan(walk, true); - - // then - assertThat(result, notNullValue()); - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3))); - - DiffEntry entry = result.get(0); - assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); - assertThat(entry.getNewPath(), is("a")); - - entry = result.get(1); - assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); - assertThat(entry.getNewPath(), is("a/b")); - - entry = result.get(2); - assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); - assertThat(entry.getNewPath(), is("a/b/c.txt")); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + File tree = new File(new File(db.getWorkTree(), "a"), "b"); + FileUtils.mkdirs(tree); + File file = new File(tree, "c.txt"); + FileUtils.createNewFile(file); + write(file, "content"); + git.add().addFilepattern("a").call(); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + write(file, "new line"); + RevCommit c2 = git.commit().setAll(true).setMessage("second commit") + .call(); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk, true); + + // then + assertThat(result, notNullValue()); + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3))); + + DiffEntry entry = result.get(0); + assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); + assertThat(entry.getNewPath(), is("a")); + + entry = result.get(1); + assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); + assertThat(entry.getNewPath(), is("a/b")); + + entry = result.get(2); + assertThat(entry.getChangeType(), is(ChangeType.MODIFY)); + assertThat(entry.getNewPath(), is("a/b/c.txt")); + } } @Test public void shouldListChangesInWorkingTree() throws Exception { // given writeTrashFile("a.txt", "content"); - Git git = new Git(db); - git.add().addFilepattern("a.txt").call(); - RevCommit c = git.commit().setMessage("initial commit").call(); - writeTrashFile("b.txt", "new line"); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c.getTree()); - walk.addTree(new FileTreeIterator(db)); - List result = DiffEntry.scan(walk, true); - - // then - assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); - DiffEntry entry = result.get(0); - - assertThat(entry.getChangeType(), is(ChangeType.ADD)); - assertThat(entry.getNewPath(), is("b.txt")); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + git.add().addFilepattern("a.txt").call(); + RevCommit c = git.commit().setMessage("initial commit").call(); + writeTrashFile("b.txt", "new line"); + + // when + walk.addTree(c.getTree()); + walk.addTree(new FileTreeIterator(db)); + List result = DiffEntry.scan(walk, true); + + // then + assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1))); + DiffEntry entry = result.get(0); + + assertThat(entry.getChangeType(), is(ChangeType.ADD)); + assertThat(entry.getNewPath(), is("b.txt")); + } } @Test public void shouldMarkEntriesWhenGivenMarkTreeFilter() throws Exception { // given - Git git = new Git(db); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - FileUtils.mkdir(new File(db.getWorkTree(), "b")); - writeTrashFile("a.txt", "a"); - writeTrashFile("b/1.txt", "b1"); - writeTrashFile("b/2.txt", "b2"); - writeTrashFile("c.txt", "c"); - git.add().addFilepattern("a.txt").addFilepattern("b") - .addFilepattern("c.txt").call(); - RevCommit c2 = git.commit().setMessage("second commit").call(); - TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt"); - TreeFilter filterB = PathFilterGroup.createFromStrings("b"); - TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt"); - - // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List result = DiffEntry.scan(walk, true, new TreeFilter[] { - filterA, filterB, filterB2 }); - - // then - assertThat(result, notNullValue()); - assertEquals(5, result.size()); - - DiffEntry entryA = result.get(0); - DiffEntry entryB = result.get(1); - DiffEntry entryB1 = result.get(2); - DiffEntry entryB2 = result.get(3); - DiffEntry entryC = result.get(4); - - assertThat(entryA.getNewPath(), is("a.txt")); - assertTrue(entryA.isMarked(0)); - assertFalse(entryA.isMarked(1)); - assertFalse(entryA.isMarked(2)); - assertEquals(1, entryA.getTreeFilterMarks()); - - assertThat(entryB.getNewPath(), is("b")); - assertFalse(entryB.isMarked(0)); - assertTrue(entryB.isMarked(1)); - assertTrue(entryB.isMarked(2)); - assertEquals(6, entryB.getTreeFilterMarks()); - - assertThat(entryB1.getNewPath(), is("b/1.txt")); - assertFalse(entryB1.isMarked(0)); - assertTrue(entryB1.isMarked(1)); - assertFalse(entryB1.isMarked(2)); - assertEquals(2, entryB1.getTreeFilterMarks()); - - assertThat(entryB2.getNewPath(), is("b/2.txt")); - assertFalse(entryB2.isMarked(0)); - assertTrue(entryB2.isMarked(1)); - assertTrue(entryB2.isMarked(2)); - assertEquals(6, entryB2.getTreeFilterMarks()); - - assertThat(entryC.getNewPath(), is("c.txt")); - assertFalse(entryC.isMarked(0)); - assertFalse(entryC.isMarked(1)); - assertFalse(entryC.isMarked(2)); - assertEquals(0, entryC.getTreeFilterMarks()); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + RevCommit c1 = git.commit().setMessage("initial commit").call(); + FileUtils.mkdir(new File(db.getWorkTree(), "b")); + writeTrashFile("a.txt", "a"); + writeTrashFile("b/1.txt", "b1"); + writeTrashFile("b/2.txt", "b2"); + writeTrashFile("c.txt", "c"); + git.add().addFilepattern("a.txt").addFilepattern("b") + .addFilepattern("c.txt").call(); + RevCommit c2 = git.commit().setMessage("second commit").call(); + TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt"); + TreeFilter filterB = PathFilterGroup.createFromStrings("b"); + TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt"); + + // when + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk, true, new TreeFilter[] { + filterA, filterB, filterB2 }); + + // then + assertThat(result, notNullValue()); + assertEquals(5, result.size()); + + DiffEntry entryA = result.get(0); + DiffEntry entryB = result.get(1); + DiffEntry entryB1 = result.get(2); + DiffEntry entryB2 = result.get(3); + DiffEntry entryC = result.get(4); + + assertThat(entryA.getNewPath(), is("a.txt")); + assertTrue(entryA.isMarked(0)); + assertFalse(entryA.isMarked(1)); + assertFalse(entryA.isMarked(2)); + assertEquals(1, entryA.getTreeFilterMarks()); + + assertThat(entryB.getNewPath(), is("b")); + assertFalse(entryB.isMarked(0)); + assertTrue(entryB.isMarked(1)); + assertTrue(entryB.isMarked(2)); + assertEquals(6, entryB.getTreeFilterMarks()); + + assertThat(entryB1.getNewPath(), is("b/1.txt")); + assertFalse(entryB1.isMarked(0)); + assertTrue(entryB1.isMarked(1)); + assertFalse(entryB1.isMarked(2)); + assertEquals(2, entryB1.getTreeFilterMarks()); + + assertThat(entryB2.getNewPath(), is("b/2.txt")); + assertFalse(entryB2.isMarked(0)); + assertTrue(entryB2.isMarked(1)); + assertTrue(entryB2.isMarked(2)); + assertEquals(6, entryB2.getTreeFilterMarks()); + + assertThat(entryC.getNewPath(), is("c.txt")); + assertFalse(entryC.isMarked(0)); + assertFalse(entryC.isMarked(1)); + assertFalse(entryC.isMarked(2)); + assertEquals(0, entryC.getTreeFilterMarks()); + } } @Test(expected = IllegalArgumentException.class) @@ -339,9 +347,10 @@ // given - we don't need anything here // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(new EmptyTreeIterator()); - DiffEntry.scan(walk); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new EmptyTreeIterator()); + DiffEntry.scan(walk); + } } @Test(expected = IllegalArgumentException.class) @@ -350,11 +359,12 @@ // given - we don't need anything here // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(new EmptyTreeIterator()); - walk.addTree(new EmptyTreeIterator()); - walk.addTree(new EmptyTreeIterator()); - DiffEntry.scan(walk); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new EmptyTreeIterator()); + walk.addTree(new EmptyTreeIterator()); + walk.addTree(new EmptyTreeIterator()); + DiffEntry.scan(walk); + } } @Test(expected = IllegalArgumentException.class) @@ -363,46 +373,48 @@ // given - we don't need anything here // when - TreeWalk walk = new TreeWalk(db); - walk.addTree(new EmptyTreeIterator()); - walk.addTree(new EmptyTreeIterator()); - walk.setRecursive(true); - DiffEntry.scan(walk, true); + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new EmptyTreeIterator()); + walk.addTree(new EmptyTreeIterator()); + walk.setRecursive(true); + DiffEntry.scan(walk, true); + } } @Test public void shouldReportFileModeChange() throws Exception { writeTrashFile("a.txt", "content"); - Git git = new Git(db); - git.add().addFilepattern("a.txt").call(); - RevCommit c1 = git.commit().setMessage("initial commit").call(); - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - final TreeWalk walk = new TreeWalk(db); - walk.addTree(c1.getTree()); - walk.setRecursive(true); - assertTrue(walk.next()); - - editor.add(new PathEdit("a.txt") { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.EXECUTABLE_FILE); - ent.setObjectId(walk.getObjectId(0)); - } - }); - assertTrue(editor.commit()); - RevCommit c2 = git.commit().setMessage("second commit").call(); - walk.reset(); - walk.addTree(c1.getTree()); - walk.addTree(c2.getTree()); - List diffs = DiffEntry.scan(walk, false); - assertEquals(1, diffs.size()); - DiffEntry diff = diffs.get(0); - assertEquals(ChangeType.MODIFY,diff.getChangeType()); - assertEquals(diff.getOldId(), diff.getNewId()); - assertEquals("a.txt", diff.getOldPath()); - assertEquals(diff.getOldPath(), diff.getNewPath()); - assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode()); - assertEquals(FileMode.REGULAR_FILE, diff.getOldMode()); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + git.add().addFilepattern("a.txt").call(); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + walk.addTree(c1.getTree()); + walk.setRecursive(true); + assertTrue(walk.next()); + + editor.add(new PathEdit("a.txt") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.EXECUTABLE_FILE); + ent.setObjectId(walk.getObjectId(0)); + } + }); + assertTrue(editor.commit()); + RevCommit c2 = git.commit().setMessage("second commit").call(); + walk.reset(); + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List diffs = DiffEntry.scan(walk, false); + assertEquals(1, diffs.size()); + DiffEntry diff = diffs.get(0); + assertEquals(ChangeType.MODIFY,diff.getChangeType()); + assertEquals(diff.getOldId(), diff.getNewId()); + assertEquals("a.txt", diff.getOldPath()); + assertEquals(diff.getOldPath(), diff.getNewPath()); + assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode()); + assertEquals(FileMode.REGULAR_FILE, diff.getOldMode()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -181,17 +181,14 @@ } private Patch parseTestPatchFile(final String patchFile) throws IOException { - final InputStream in = getClass().getResourceAsStream(patchFile); - if (in == null) { - fail("No " + patchFile + " test vector"); - return null; // Never happens - } - try { + try (InputStream in = getClass().getResourceAsStream(patchFile)) { + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } final Patch p = new Patch(); p.parse(in); return p; - } finally { - in.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,6 +45,7 @@ import static org.junit.Assert.assertEquals; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -54,18 +55,19 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.io.DisabledOutputStream; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -89,7 +91,7 @@ @Before public void setUp() throws Exception { super.setUp(); - testDb = new TestRepository(db); + testDb = new TestRepository<>(db); df = new DiffFormatter(DisabledOutputStream.INSTANCE); df.setRepository(db); df.setAbbreviationLength(8); @@ -379,30 +381,31 @@ File folder = new File(db.getDirectory().getParent(), "folder"); FileUtils.mkdir(folder); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os)); - dfmt.setRepository(db); - dfmt.setPathFilter(PathFilter.create("folder")); - DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); - FileTreeIterator newTree = new FileTreeIterator(db); - dfmt.format(oldTree, newTree); - dfmt.flush(); - - String actual = os.toString("UTF-8"); - String expected = - "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "index 0119635..95c4c65 100644\n" - + "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n" - + "@@ -1 +1 @@\n" + "-folder\n" - + "\\ No newline at end of file\n" + "+folder change\n" - + "\\ No newline at end of file\n"; + try (Git git = new Git(db); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + dfmt.setRepository(db); + dfmt.setPathFilter(PathFilter.create("folder")); + DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); + FileTreeIterator newTree = new FileTreeIterator(db); + + dfmt.format(oldTree, newTree); + dfmt.flush(); + + String actual = os.toString("UTF-8"); + String expected = + "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "index 0119635..95c4c65 100644\n" + + "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n" + + "@@ -1 +1 @@\n" + "-folder\n" + + "\\ No newline at end of file\n" + "+folder change\n" + + "\\ No newline at end of file\n"; - assertEquals(expected, actual); + assertEquals(expected, actual); + } } @Test @@ -411,29 +414,30 @@ File folder = new File(db.getDirectory().getParent(), "folder"); FileUtils.mkdir(folder); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os)); - dfmt.setRepository(db); - dfmt.setPathFilter(PathFilter.create("folder")); - dfmt.format(null, commit.getTree().getId()); - dfmt.flush(); - - String actual = os.toString("UTF-8"); - String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "new file mode 100644\n" - + "index 0000000..0119635\n" - + "--- /dev/null\n" - + "+++ b/folder/folder.txt\n" - + "@@ -0,0 +1 @@\n" - + "+folder\n" - + "\\ No newline at end of file\n"; + try (Git git = new Git(db); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) { + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + + dfmt.setRepository(db); + dfmt.setPathFilter(PathFilter.create("folder")); + dfmt.format(null, commit.getTree().getId()); + dfmt.flush(); + + String actual = os.toString("UTF-8"); + String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "new file mode 100644\n" + + "index 0000000..0119635\n" + + "--- /dev/null\n" + + "+++ b/folder/folder.txt\n" + + "@@ -0,0 +1 @@\n" + + "+folder\n" + + "\\ No newline at end of file\n"; - assertEquals(expected, actual); + assertEquals(expected, actual); + } } @Test @@ -442,43 +446,141 @@ File folder = new File(db.getDirectory().getParent(), "folder"); FileUtils.mkdir(folder); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os)); - dfmt.setRepository(db); - dfmt.setPathFilter(PathFilter.create("folder")); - dfmt.format(commit.getTree().getId(), null); - dfmt.flush(); - - String actual = os.toString("UTF-8"); - String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" - + "deleted file mode 100644\n" - + "index 0119635..0000000\n" - + "--- a/folder/folder.txt\n" - + "+++ /dev/null\n" - + "@@ -1 +0,0 @@\n" - + "-folder\n" - + "\\ No newline at end of file\n"; + try (Git git = new Git(db); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os));) { + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + + dfmt.setRepository(db); + dfmt.setPathFilter(PathFilter.create("folder")); + dfmt.format(commit.getTree().getId(), null); + dfmt.flush(); + + String actual = os.toString("UTF-8"); + String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n" + + "deleted file mode 100644\n" + + "index 0119635..0000000\n" + + "--- a/folder/folder.txt\n" + + "+++ /dev/null\n" + + "@@ -1 +0,0 @@\n" + + "-folder\n" + + "\\ No newline at end of file\n"; - assertEquals(expected, actual); + assertEquals(expected, actual); + } } @Test public void testDiffNullToNull() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os)); - dfmt.setRepository(db); - dfmt.format((AnyObjectId) null, null); - dfmt.flush(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) { + dfmt.setRepository(db); + dfmt.format((AnyObjectId) null, null); + dfmt.flush(); - String actual = os.toString("UTF-8"); - String expected = ""; + String actual = os.toString("UTF-8"); + String expected = ""; - assertEquals(expected, actual); + assertEquals(expected, actual); + } + } + + @Test + public void testDiffAutoCrlfSmallFile() throws Exception { + String content = "01234\r\n01234\r\n01234\r\n"; + String expectedDiff = "diff --git a/test.txt b/test.txt\n" + + "index fe25983..a44a032 100644\n" // + + "--- a/test.txt\n" // + + "+++ b/test.txt\n" // + + "@@ -1,3 +1,4 @@\n" // + + " 01234\n" // + + "+ABCD\n" // + + " 01234\n" // + + " 01234\n"; + doAutoCrLfTest(content, expectedDiff); + } + + @Test + public void testDiffAutoCrlfMediumFile() throws Exception { + String content = mediumCrLfString(); + String expectedDiff = "diff --git a/test.txt b/test.txt\n" + + "index 215c502..c10f08c 100644\n" // + + "--- a/test.txt\n" // + + "+++ b/test.txt\n" // + + "@@ -1,4 +1,5 @@\n" // + + " 01234567\n" // + + "+ABCD\n" // + + " 01234567\n" // + + " 01234567\n" // + + " 01234567\n"; + doAutoCrLfTest(content, expectedDiff); + } + + @Test + public void testDiffAutoCrlfLargeFile() throws Exception { + String content = largeCrLfString(); + String expectedDiff = "diff --git a/test.txt b/test.txt\n" + + "index 7014942..c0487a7 100644\n" // + + "--- a/test.txt\n" // + + "+++ b/test.txt\n" // + + "@@ -1,4 +1,5 @@\n" + + " 012345678901234567890123456789012345678901234567\n" + + "+ABCD\n" + + " 012345678901234567890123456789012345678901234567\n" + + " 012345678901234567890123456789012345678901234567\n" + + " 012345678901234567890123456789012345678901234567\n"; + doAutoCrLfTest(content, expectedDiff); + } + + private void doAutoCrLfTest(String content, String expectedDiff) + throws Exception { + FileBasedConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, "true"); + config.save(); + commitFile("test.txt", content, "master"); + // Insert a line into content + int i = content.indexOf('\n'); + content = content.substring(0, i + 1) + "ABCD\r\n" + + content.substring(i + 1); + writeTrashFile("test.txt", content); + // Create the patch + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter dfmt = new DiffFormatter( + new BufferedOutputStream(os))) { + dfmt.setRepository(db); + dfmt.format(new DirCacheIterator(db.readDirCache()), + new FileTreeIterator(db)); + dfmt.flush(); + + String actual = os.toString("UTF-8"); + + assertEquals(expectedDiff, actual); + } + } + + private static String largeCrLfString() { + String line = "012345678901234567890123456789012345678901234567\r\n"; + StringBuilder builder = new StringBuilder( + 2 * RawText.FIRST_FEW_BYTES); + while (builder.length() < 2 * RawText.FIRST_FEW_BYTES) { + builder.append(line); + } + return builder.toString(); + } + + private static String mediumCrLfString() { + // Create a CR-LF string longer than RawText.FIRST_FEW_BYTES whose + // canonical representation is shorter than RawText.FIRST_FEW_BYTES. + String line = "01234567\r\n"; // 10 characters + StringBuilder builder = new StringBuilder( + RawText.FIRST_FEW_BYTES + line.length()); + while (builder.length() <= RawText.FIRST_FEW_BYTES) { + builder.append(line); + } + return builder.toString(); } private static String makeDiffHeader(String pathA, String pathB, diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ import org.junit.Test; public class EditListTest { + @SuppressWarnings("unlikely-arg-type") @Test public void testEmpty() { final EditList l = new EditList(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -125,6 +125,7 @@ assertEquals("REPLACE(1-2,1-4)", e.toString()); } + @SuppressWarnings("unlikely-arg-type") @Test public void testEquals1() { final Edit e1 = new Edit(1, 2, 3, 4); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,21 +61,22 @@ File folder = new File(db.getDirectory().getParent(), "folder"); folder.mkdir(); write(new File(folder, "folder.txt"), "folder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "folder change"); - - PatchIdDiffFormatter df = new PatchIdDiffFormatter(); - df.setRepository(db); - df.setPathFilter(PathFilter.create("folder")); - DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); - FileTreeIterator newTree = new FileTreeIterator(db); - df.format(oldTree, newTree); - df.flush(); - - assertEquals("1ff64e0f9333e9b81967c3e8d7a81362b14d5441", df - .getCalulatedPatchId().name()); + try (Git git = new Git(db); + PatchIdDiffFormatter df = new PatchIdDiffFormatter()) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "folder change"); + + df.setRepository(db); + df.setPathFilter(PathFilter.create("folder")); + DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); + FileTreeIterator newTree = new FileTreeIterator(db); + df.format(oldTree, newTree); + df.flush(); + + assertEquals("1ff64e0f9333e9b81967c3e8d7a81362b14d5441", df + .getCalulatedPatchId().name()); + } } @Test @@ -84,37 +85,40 @@ File folder = new File(db.getDirectory().getParent(), "folder"); folder.mkdir(); write(new File(folder, "folder.txt"), "\n\n\n\nfolder"); - Git git = new Git(db); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "\n\n\n\nfolder change"); - - PatchIdDiffFormatter df = new PatchIdDiffFormatter(); - df.setRepository(db); - df.setPathFilter(PathFilter.create("folder")); - DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); - FileTreeIterator newTree = new FileTreeIterator(db); - df.format(oldTree, newTree); - df.flush(); - - assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df - .getCalulatedPatchId().name()); - - write(new File(folder, "folder.txt"), "a\n\n\n\nfolder"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - write(new File(folder, "folder.txt"), "a\n\n\n\nfolder change"); - - df = new PatchIdDiffFormatter(); - df.setRepository(db); - df.setPathFilter(PathFilter.create("folder")); - oldTree = new DirCacheIterator(db.readDirCache()); - newTree = new FileTreeIterator(db); - df.format(oldTree, newTree); - df.flush(); - - assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df - .getCalulatedPatchId().name()); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "\n\n\n\nfolder change"); + + try (PatchIdDiffFormatter df = new PatchIdDiffFormatter()) { + df.setRepository(db); + df.setPathFilter(PathFilter.create("folder")); + DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); + FileTreeIterator newTree = new FileTreeIterator(db); + df.format(oldTree, newTree); + df.flush(); + + assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df + .getCalulatedPatchId().name()); + } + + write(new File(folder, "folder.txt"), "a\n\n\n\nfolder"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + write(new File(folder, "folder.txt"), "a\n\n\n\nfolder change"); + + try (PatchIdDiffFormatter df = new PatchIdDiffFormatter()) { + df.setRepository(db); + df.setPathFilter(PathFilter.create("folder")); + DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache()); + FileTreeIterator newTree = new FileTreeIterator(db); + df.format(oldTree, newTree); + df.flush(); + + assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df + .getCalulatedPatchId().name()); + } + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextLoadTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextLoadTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextLoadTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextLoadTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017, 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.diff; + +import org.eclipse.jgit.errors.BinaryBlobException; +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +public class RawTextLoadTest extends RepositoryTestCase { + private static byte[] generate(int size, int nullAt) { + byte[] data = new byte[size]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) ((i % 72 == 0) ? '\n' : (i%10) + '0'); + } + if (nullAt >= 0) { + data[nullAt] = '\0'; + } + return data; + } + + private RawText textFor(byte[] data, int limit) throws IOException, BinaryBlobException { + FileRepository repo = createBareRepository(); + ObjectId id; + try (ObjectInserter ins = repo.getObjectDatabase().newInserter()) { + id = ins.insert(Constants.OBJ_BLOB, data); + } + ObjectLoader ldr = repo.open(id); + return RawText.load(ldr, limit); + } + + @Test + public void testSmallOK() throws Exception { + byte[] data = generate(1000, -1); + RawText result = textFor(data, 1 << 20); + Assert.assertArrayEquals(result.content, data); + } + + @Test(expected = BinaryBlobException.class) + public void testSmallNull() throws Exception { + byte[] data = generate(1000, 22); + textFor(data, 1 << 20); + } + + @Test + public void testBigOK() throws Exception { + byte[] data = generate(10000, -1); + RawText result = textFor(data, 1 << 20); + Assert.assertArrayEquals(result.content, data); + } + + @Test(expected = BinaryBlobException.class) + public void testBigWithNullAtStart() throws Exception { + byte[] data = generate(10000, 22); + textFor(data, 1 << 20); + } + + @Test(expected = BinaryBlobException.class) + public void testBinaryThreshold() throws Exception { + byte[] data = generate(2 << 20, -1); + textFor(data, 1 << 20); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.diff; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -51,7 +52,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.RawParseUtils; @@ -110,8 +110,7 @@ } @Test - public void testComparatorReduceCommonStartEnd() - throws UnsupportedEncodingException { + public void testComparatorReduceCommonStartEnd() { final RawTextComparator c = RawTextComparator.DEFAULT; Edit e; @@ -137,54 +136,51 @@ e = c.reduceCommonStartEnd(t("abQxy"), t("abRxy"), e); assertEquals(new Edit(2, 3, 2, 3), e); - RawText a = new RawText("p\na b\nQ\nc d\n".getBytes("UTF-8")); - RawText b = new RawText("p\na b \nR\n c d \n".getBytes("UTF-8")); + RawText a = new RawText("p\na b\nQ\nc d\n".getBytes(UTF_8)); + RawText b = new RawText("p\na b \nR\n c d \n".getBytes(UTF_8)); e = new Edit(0, 4, 0, 4); e = RawTextComparator.WS_IGNORE_ALL.reduceCommonStartEnd(a, b, e); assertEquals(new Edit(2, 3, 2, 3), e); } @Test - public void testComparatorReduceCommonStartEnd_EmptyLine() - throws UnsupportedEncodingException { + public void testComparatorReduceCommonStartEnd_EmptyLine() { RawText a; RawText b; Edit e; - a = new RawText("R\n y\n".getBytes("UTF-8")); - b = new RawText("S\n\n y\n".getBytes("UTF-8")); + a = new RawText("R\n y\n".getBytes(UTF_8)); + b = new RawText("S\n\n y\n".getBytes(UTF_8)); e = new Edit(0, 2, 0, 3); e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e); assertEquals(new Edit(0, 1, 0, 2), e); - a = new RawText("S\n\n y\n".getBytes("UTF-8")); - b = new RawText("R\n y\n".getBytes("UTF-8")); + a = new RawText("S\n\n y\n".getBytes(UTF_8)); + b = new RawText("R\n y\n".getBytes(UTF_8)); e = new Edit(0, 3, 0, 2); e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e); assertEquals(new Edit(0, 2, 0, 1), e); } @Test - public void testComparatorReduceCommonStartButLastLineNoEol() - throws UnsupportedEncodingException { + public void testComparatorReduceCommonStartButLastLineNoEol() { RawText a; RawText b; Edit e; - a = new RawText("start".getBytes("UTF-8")); - b = new RawText("start of line".getBytes("UTF-8")); + a = new RawText("start".getBytes(UTF_8)); + b = new RawText("start of line".getBytes(UTF_8)); e = new Edit(0, 1, 0, 1); e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e); assertEquals(new Edit(0, 1, 0, 1), e); } @Test - public void testComparatorReduceCommonStartButLastLineNoEol_2() - throws UnsupportedEncodingException { + public void testComparatorReduceCommonStartButLastLineNoEol_2() { RawText a; RawText b; Edit e; - a = new RawText("start".getBytes("UTF-8")); - b = new RawText("start of\nlastline".getBytes("UTF-8")); + a = new RawText("start".getBytes(UTF_8)); + b = new RawText("start of\nlastline".getBytes(UTF_8)); e = new Edit(0, 1, 0, 2); e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e); assertEquals(new Edit(0, 1, 0, 2), e); @@ -243,10 +239,6 @@ r.append(text.charAt(i)); r.append('\n'); } - try { - return new RawText(r.toString().getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return new RawText(r.toString().getBytes(UTF_8)); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.List; import org.eclipse.jgit.diff.DiffEntry.ChangeType; @@ -74,7 +75,7 @@ @Before public void setUp() throws Exception { super.setUp(); - testDb = new TestRepository(db); + testDb = new TestRepository<>(db); rd = new RenameDetector(db); } @@ -227,6 +228,19 @@ } @Test + public void testExactRename_UnstagedFile() throws Exception { + ObjectId aId = blob("foo"); + DiffEntry a = DiffEntry.delete(PATH_A, aId); + DiffEntry b = DiffEntry.add(PATH_B, aId); + + rd.addAll(Arrays.asList(a, b)); + List entries = rd.compute(); + + assertEquals(1, entries.size()); + assertRename(a, b, 100, entries.get(0)); + } + + @Test public void testInexactRename_OnePair() throws Exception { ObjectId aId = blob("foo\nbar\nbaz\nblarg\n"); ObjectId bId = blob("foo\nbar\nbaz\nblah\n"); @@ -427,6 +441,23 @@ assertEquals(2, entries.size()); assertSame(a, entries.get(0)); assertSame(b, entries.get(1)); + } + + @Test + public void testNoRenames_UntrackedFile() throws Exception { + ObjectId aId = blob("foo"); + ObjectId bId = ObjectId + .fromString("3049eb6eee7e1318f4e78e799bf33f1e54af9cbf"); + + DiffEntry a = DiffEntry.delete(PATH_A, aId); + DiffEntry b = DiffEntry.add(PATH_B, bId); + + rd.addAll(Arrays.asList(a, b)); + List entries = rd.compute(); + + assertEquals(2, entries.size()); + assertSame(a, entries.get(0)); + assertSame(b, entries.get(1)); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,7 @@ package org.eclipse.jgit.diff; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -81,7 +82,7 @@ + "A\n" // + "B\n" // + "B\n" // - + "B\n").getBytes("UTF-8"); + + "B\n").getBytes(UTF_8); SimilarityIndex si = new SimilarityIndex(); si.hash(new ByteArrayInputStream(in), in.length, false); assertEquals(2, si.size()); @@ -129,12 +130,12 @@ + "D\r\n" // + "B\r\n"; SimilarityIndex src = new SimilarityIndex(); - byte[] bytes1 = text.getBytes("UTF-8"); + byte[] bytes1 = text.getBytes(UTF_8); src.hash(new ByteArrayInputStream(bytes1), bytes1.length, true); src.sort(); SimilarityIndex dst = new SimilarityIndex(); - byte[] bytes2 = text.replace("\r", "").getBytes("UTF-8"); + byte[] bytes2 = text.replace("\r", "").getBytes(UTF_8); dst.hash(new ByteArrayInputStream(bytes2), bytes2.length, true); dst.sort(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -255,9 +255,11 @@ DirCacheBuilder b = dc.builder(); DirCacheEntry e = new DirCacheEntry(path); e.setFileMode(FileMode.REGULAR_FILE); - e.setObjectId(new ObjectInserter.Formatter().idFor( - Constants.OBJ_BLOB, - Constants.encode(path))); + try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) { + e.setObjectId(formatter.idFor( + Constants.OBJ_BLOB, + Constants.encode(path))); + } b.add(e); b.commit(); db.readDirCache(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -78,23 +78,24 @@ final int expIdx = 2; final DirCacheBuilder b = dc.builder(); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new DirCacheBuildIterator(b)); - tw.setRecursive(true); - tw.setFilter(PathFilterGroup.createFromStrings(Collections - .singleton(paths[expIdx]))); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new DirCacheBuildIterator(b)); + tw.setRecursive(true); + tw.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(paths[expIdx]))); - assertTrue("found " + paths[expIdx], tw.next()); - final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); - assertNotNull(c); - assertEquals(expIdx, c.ptr); - assertSame(ents[expIdx], c.getDirCacheEntry()); - assertEquals(paths[expIdx], tw.getPathString()); - assertEquals(mode.getBits(), tw.getRawMode(0)); - assertSame(mode, tw.getFileMode(0)); - b.add(c.getDirCacheEntry()); + assertTrue("found " + paths[expIdx], tw.next()); + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(expIdx, c.ptr); + assertSame(ents[expIdx], c.getDirCacheEntry()); + assertEquals(paths[expIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + b.add(c.getDirCacheEntry()); - assertFalse("no more entries", tw.next()); + assertFalse("no more entries", tw.next()); + } b.finish(); assertEquals(ents.length, dc.getEntryCount()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -211,6 +211,7 @@ DirCache dc = db.lockDirCache(); IndexChangedListener listener = new IndexChangedListener() { + @Override public void onIndexChanged(IndexChangedEvent event) { throw new ReceivedEventMarkerException(); } @@ -238,6 +239,7 @@ dc = db.lockDirCache(); listener = new IndexChangedListener() { + @Override public void onIndexChanged(IndexChangedEvent event) { throw new ReceivedEventMarkerException(); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,7 @@ package org.eclipse.jgit.dircache; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.junit.Assert.assertEquals; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -98,17 +99,18 @@ assertEquals(ls.size(), dc.getEntryCount()); { final Iterator rItr = ls.values().iterator(); - final TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc)); - while (rItr.hasNext()) { - final DirCacheIterator dcItr; - - assertTrue(tw.next()); - dcItr = tw.getTree(0, DirCacheIterator.class); - assertNotNull(dcItr); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc)); + while (rItr.hasNext()) { + final DirCacheIterator dcItr; + + assertTrue(tw.next()); + dcItr = tw.getTree(0, DirCacheIterator.class); + assertNotNull(dcItr); - assertEqual(rItr.next(), dcItr.getDirCacheEntry()); + assertEqual(rItr.next(), dcItr.getDirCacheEntry()); + } } } } @@ -177,7 +179,7 @@ .getObjectId()); assertEquals(cList.size(), jTree.getEntrySpan()); - final ArrayList subtrees = new ArrayList(); + final ArrayList subtrees = new ArrayList<>(); for (final CGitLsTreeRecord r : cTree.values()) { if (FileMode.TREE.equals(r.mode)) subtrees.add(r); @@ -232,33 +234,27 @@ } private static Map readLsFiles() throws Exception { - final LinkedHashMap r = new LinkedHashMap(); - final BufferedReader br = new BufferedReader(new InputStreamReader( - new FileInputStream(pathOf("gitgit.lsfiles")), "UTF-8")); - try { + final LinkedHashMap r = new LinkedHashMap<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(pathOf("gitgit.lsfiles")), UTF_8))) { String line; while ((line = br.readLine()) != null) { final CGitIndexRecord cr = new CGitIndexRecord(line); r.put(cr.path, cr); } - } finally { - br.close(); } return r; } private static Map readLsTree() throws Exception { - final LinkedHashMap r = new LinkedHashMap(); - final BufferedReader br = new BufferedReader(new InputStreamReader( - new FileInputStream(pathOf("gitgit.lstree")), "UTF-8")); - try { + final LinkedHashMap r = new LinkedHashMap<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(pathOf("gitgit.lstree")), UTF_8))) { String line; while ((line = br.readLine()) != null) { final CGitLsTreeRecord cr = new CGitLsTreeRecord(line); r.put(cr.path, cr); } - } finally { - br.close(); } return r; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,15 +69,17 @@ assertFalse(isValidPath("a\u0000b")); } - private static boolean isValidPath(final String path) { + @SuppressWarnings("unused") + private static boolean isValidPath(String path) { try { - DirCacheCheckout.checkValidPath(path); + new DirCacheEntry(path); return true; } catch (InvalidPathException e) { return false; } } + @SuppressWarnings("unused") @Test public void testCreate_ByStringPath() { assertEquals("a", new DirCacheEntry("a").getPathString()); @@ -91,6 +93,7 @@ } } + @SuppressWarnings("unused") @Test public void testCreate_ByStringPathAndStage() { DirCacheEntry e; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -76,9 +76,10 @@ final DirCache dc = DirCache.newInCore(); assertEquals(0, dc.getEntryCount()); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new DirCacheIterator(dc)); - assertFalse(tw.next()); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new DirCacheIterator(dc)); + assertFalse(tw.next()); + } } @Test @@ -125,19 +126,20 @@ b.finish(); final DirCacheIterator i = new DirCacheIterator(dc); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(i); - int pathIdx = 0; - while (tw.next()) { - assertSame(i, tw.getTree(0, DirCacheIterator.class)); - assertEquals(pathIdx, i.ptr); - assertSame(ents[pathIdx], i.getDirCacheEntry()); - assertEquals(paths[pathIdx], tw.getPathString()); - assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0)); - assertSame(modes[pathIdx], tw.getFileMode(0)); - pathIdx++; + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(i); + int pathIdx = 0; + while (tw.next()) { + assertSame(i, tw.getTree(0, DirCacheIterator.class)); + assertEquals(pathIdx, i.ptr); + assertSame(ents[pathIdx], i.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0)); + assertSame(modes[pathIdx], tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); } - assertEquals(paths.length, pathIdx); } @Test @@ -162,26 +164,27 @@ final int expPos[] = { 0, -1, 4 }; final DirCacheIterator i = new DirCacheIterator(dc); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(i); - tw.setRecursive(false); - int pathIdx = 0; - while (tw.next()) { - assertSame(i, tw.getTree(0, DirCacheIterator.class)); - assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0)); - assertSame(expModes[pathIdx], tw.getFileMode(0)); - assertEquals(expPaths[pathIdx], tw.getPathString()); - - if (expPos[pathIdx] >= 0) { - assertEquals(expPos[pathIdx], i.ptr); - assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry()); - } else { - assertSame(FileMode.TREE, tw.getFileMode(0)); - } + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(i); + tw.setRecursive(false); + int pathIdx = 0; + while (tw.next()) { + assertSame(i, tw.getTree(0, DirCacheIterator.class)); + assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0)); + assertSame(expModes[pathIdx], tw.getFileMode(0)); + assertEquals(expPaths[pathIdx], tw.getPathString()); + + if (expPos[pathIdx] >= 0) { + assertEquals(expPos[pathIdx], i.ptr); + assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry()); + } else { + assertSame(FileMode.TREE, tw.getFileMode(0)); + } - pathIdx++; + pathIdx++; + } + assertEquals(expPaths.length, pathIdx); } - assertEquals(expPaths.length, pathIdx); } @Test @@ -202,21 +205,22 @@ b.finish(); final DirCacheIterator i = new DirCacheIterator(dc); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(i); - tw.setRecursive(true); - int pathIdx = 0; - while (tw.next()) { - final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); - assertNotNull(c); - assertEquals(pathIdx, c.ptr); - assertSame(ents[pathIdx], c.getDirCacheEntry()); - assertEquals(paths[pathIdx], tw.getPathString()); - assertEquals(mode.getBits(), tw.getRawMode(0)); - assertSame(mode, tw.getFileMode(0)); - pathIdx++; + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(i); + tw.setRecursive(true); + int pathIdx = 0; + while (tw.next()) { + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(pathIdx, c.ptr); + assertSame(ents[pathIdx], c.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); } - assertEquals(paths.length, pathIdx); } @Test @@ -236,21 +240,22 @@ b.add(ents[i]); b.finish(); - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new DirCacheIterator(dc)); - tw.setRecursive(true); - int pathIdx = 0; - while (tw.next()) { - final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); - assertNotNull(c); - assertEquals(pathIdx, c.ptr); - assertSame(ents[pathIdx], c.getDirCacheEntry()); - assertEquals(paths[pathIdx], tw.getPathString()); - assertEquals(mode.getBits(), tw.getRawMode(0)); - assertSame(mode, tw.getFileMode(0)); - pathIdx++; + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new DirCacheIterator(dc)); + tw.setRecursive(true); + int pathIdx = 0; + while (tw.next()) { + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(pathIdx, c.ptr); + assertSame(ents[pathIdx], c.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); } - assertEquals(paths.length, pathIdx); } @Test @@ -397,22 +402,23 @@ b.add(ents[i]); b.finish(); - final TreeWalk tw = new TreeWalk(db); - for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) { - tw.reset(); - tw.addTree(new DirCacheIterator(dc)); - tw.setFilter(PathFilterGroup.createFromStrings(Collections - .singleton(paths[victimIdx]))); - tw.setRecursive(tw.getFilter().shouldBeRecursive()); - assertTrue(tw.next()); - final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); - assertNotNull(c); - assertEquals(victimIdx, c.ptr); - assertSame(ents[victimIdx], c.getDirCacheEntry()); - assertEquals(paths[victimIdx], tw.getPathString()); - assertEquals(mode.getBits(), tw.getRawMode(0)); - assertSame(mode, tw.getFileMode(0)); - assertFalse(tw.next()); + try (final TreeWalk tw = new TreeWalk(db)) { + for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) { + tw.reset(); + tw.addTree(new DirCacheIterator(dc)); + tw.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(paths[victimIdx]))); + tw.setRecursive(tw.getFilter().shouldBeRecursive()); + assertTrue(tw.next()); + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(victimIdx, c.ptr); + assertSame(ents[victimIdx], c.getDirCacheEntry()); + assertEquals(paths[victimIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + assertFalse(tw.next()); + } } } @@ -424,18 +430,19 @@ final DirCache dc = DirCache.read(path, FS.DETECTED); assertEquals(2, dc.getEntryCount()); - final TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc)); - - assertTrue(tw.next()); - assertEquals("a/a", tw.getPathString()); - assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0)); - - assertTrue(tw.next()); - assertEquals("q", tw.getPathString()); - assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0)); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc)); + + assertTrue(tw.next()); + assertEquals("a/a", tw.getPathString()); + assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0)); + + assertTrue(tw.next()); + assertEquals("q", tw.getPathString()); + assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0)); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,13 @@ package org.eclipse.jgit.dircache; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.errors.DirCacheNameConflictException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.junit.Test; @@ -70,7 +72,7 @@ } private static final class RecordingEdit extends PathEdit { - final List entries = new ArrayList(); + final List entries = new ArrayList<>(); public RecordingEdit(String entryPath) { super(entryPath); @@ -154,6 +156,125 @@ assertEquals(DirCacheEntry.STAGE_3, entries.get(2).getStage()); } + @Test + public void testFileReplacesTree() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("b/c")); + editor.add(new AddEdit("b/d")); + editor.add(new AddEdit("e")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("b")); + editor.finish(); + + assertEquals(3, dc.getEntryCount()); + assertEquals("a", dc.getEntry(0).getPathString()); + assertEquals("b", dc.getEntry(1).getPathString()); + assertEquals("e", dc.getEntry(2).getPathString()); + + dc.clear(); + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/c")); + editor.add(new AddEdit("A0c")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("A")); + editor.finish(); + assertEquals(3, dc.getEntryCount()); + assertEquals("A", dc.getEntry(0).getPathString()); + assertEquals("A.c", dc.getEntry(1).getPathString()); + assertEquals("A0c", dc.getEntry(2).getPathString()); + } + + @Test + public void testTreeReplacesFile() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("ab")); + editor.add(new AddEdit("b")); + editor.add(new AddEdit("e")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("b/c/d/f")); + editor.add(new AddEdit("b/g/h/i")); + editor.finish(); + + assertEquals(5, dc.getEntryCount()); + assertEquals("a", dc.getEntry(0).getPathString()); + assertEquals("ab", dc.getEntry(1).getPathString()); + assertEquals("b/c/d/f", dc.getEntry(2).getPathString()); + assertEquals("b/g/h/i", dc.getEntry(3).getPathString()); + assertEquals("e", dc.getEntry(4).getPathString()); + } + + @Test + public void testDuplicateFiles() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("a")); + + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("a a", e.getMessage()); + assertEquals("a", e.getPath1()); + assertEquals("a", e.getPath2()); + } + } + + @Test + public void testFileOverlapsTree() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("a/b").setReplace(false)); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("a a/b", e.getMessage()); + assertEquals("a", e.getPath1()); + assertEquals("a/b", e.getPath2()); + } + + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/c").setReplace(false)); + editor.add(new AddEdit("A0c")); + editor.add(new AddEdit("A")); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("A A/c", e.getMessage()); + assertEquals("A", e.getPath1()); + assertEquals("A/c", e.getPath2()); + } + + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/b/c/d").setReplace(false)); + editor.add(new AddEdit("A/b/c")); + editor.add(new AddEdit("A0c")); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("A/b/c A/b/c/d", e.getMessage()); + assertEquals("A/b/c", e.getPath1()); + assertEquals("A/b/c/d", e.getPath2()); + } + } + private static DirCacheEntry createEntry(String path, int stage) { DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setFileMode(FileMode.REGULAR_FILE); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,6 +56,7 @@ final ConfigChangedEvent[] events = new ConfigChangedEvent[1]; db.getListenerList().addConfigChangedListener( new ConfigChangedListener() { + @Override public void onConfigChanged(ConfigChangedEvent event) { events[0] = event; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,7 +69,7 @@ final boolean appendCanMatchExpected) throws InvalidPatternException { final FileNameMatcher matcher = new FileNameMatcher(pattern, - new Character(excludedCharacter)); + Character.valueOf(excludedCharacter)); matcher.append(input); assertEquals(matchExpected, matcher.isMatch()); assertEquals(appendCanMatchExpected, matcher.canAppendMatch()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,13 +42,18 @@ */ package org.eclipse.jgit.gitrepo; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import java.io.StringBufferInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; import java.util.HashSet; import java.util.Set; import org.junit.Test; +import org.xml.sax.SAXException; public class ManifestParserTest { @@ -56,7 +61,7 @@ public void testManifestParser() throws Exception { String baseUrl = "https://git.google.com/"; StringBuilder xmlContent = new StringBuilder(); - Set results = new HashSet(); + Set results = new HashSet<>(); xmlContent.append("\n") .append("") .append("") @@ -77,7 +82,7 @@ ManifestParser parser = new ManifestParser( null, null, "master", baseUrl, null, null); - parser.read(new StringBufferInputStream(xmlContent.toString())); + parser.read(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8))); // Unfiltered projects should have them all. results.clear(); results.add("foo"); @@ -109,4 +114,49 @@ "Filtered projects shouldn't contain any unexpected results", results.isEmpty()); } + + @Test + public void testManifestParserWithMissingFetchOnRemote() throws Exception { + String baseUrl = "https://git.google.com/"; + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + + ManifestParser parser = new ManifestParser(null, null, "master", + baseUrl, null, null); + try { + parser.read(new ByteArrayInputStream( + xmlContent.toString().getBytes(UTF_8))); + fail("ManifestParser did not throw exception for missing fetch"); + } catch (IOException e) { + assertTrue(e.getCause() instanceof SAXException); + assertTrue(e.getCause().getMessage() + .contains("is missing fetch attribute")); + } + } + + void testNormalize(String in, String want) { + URI got = ManifestParser.normalizeEmptyPath(URI.create(in)); + if (!got.toString().equals(want)) { + fail(String.format("normalize(%s) = %s want %s", in, got, want)); + } + } + + @Test + public void testNormalizeEmptyPath() { + testNormalize("http://a.b", "http://a.b/"); + testNormalize("http://a.b/", "http://a.b/"); + testNormalize("", ""); + testNormalize("a/b", "a/b"); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016, 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.gitrepo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; +import org.junit.Before; +import org.junit.Test; + +public class RepoCommandSymlinkTest extends RepositoryTestCase { + @Before + public void beforeMethod() { + // If this assumption fails the tests are skipped. When running on a + // filesystem not supporting symlinks I don't want this tests + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + } + + private Repository defaultDb; + + private String rootUri; + private String defaultUri; + + @Override + public void setUp() throws Exception { + super.setUp(); + + defaultDb = createWorkRepository(); + try (Git git = new Git(defaultDb)) { + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "hello world"); + git.add().addFilepattern("hello.txt").call(); + git.commit().setMessage("Initial commit").call(); + addRepoToClose(defaultDb); + } + + defaultUri = defaultDb.getDirectory().toURI().toString(); + int root = defaultUri.lastIndexOf("/", + defaultUri.lastIndexOf("/.git") - 1) + + 1; + rootUri = defaultUri.substring(0, root) + + "manifest"; + defaultUri = defaultUri.substring(root); + } + + @Test + public void testLinkFileBare() throws Exception { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { + StringBuilder xmlContent = new StringBuilder(); + xmlContent + .append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testCopyFileBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + + // The LinkedHello symlink should exist. + File linkedhello = new File(localDb.getWorkTree(), "LinkedHello"); + assertTrue("The LinkedHello file should exist", + localDb.getFS().exists(linkedhello)); + assertTrue("The LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedhello)); + assertEquals("foo/hello.txt", + localDb.getFS().readSymLink(linkedhello)); + + // The foo/LinkedHello file should be skipped. + File linkedfoohello = new File(localDb.getWorkTree(), "foo/LinkedHello"); + assertFalse("The foo/LinkedHello file should be skipped", + localDb.getFS().exists(linkedfoohello)); + + // The subdir/LinkedHello file should use a relative ../ + File linkedsubdirhello = new File(localDb.getWorkTree(), + "subdir/LinkedHello"); + assertTrue("The subdir/LinkedHello file should exist", + localDb.getFS().exists(linkedsubdirhello)); + assertTrue("The subdir/LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedsubdirhello)); + assertEquals("../foo/hello.txt", + localDb.getFS().readSymLink(linkedsubdirhello)); + + // The bar/foo/LinkedHello file should use a single relative ../ + File linkedbarfoohello = new File(localDb.getWorkTree(), + "bar/foo/LinkedHello"); + assertTrue("The bar/foo/LinkedHello file should exist", + localDb.getFS().exists(linkedbarfoohello)); + assertTrue("The bar/foo/LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedbarfoohello)); + assertEquals("../baz/hello.txt", + localDb.getFS().readSymLink(linkedbarfoohello)); + + localDb.close(); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,20 +42,38 @@ */ package org.eclipse.jgit.gitrepo; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileReader; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.BlobBasedConfig; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class RepoCommandTest extends RepositoryTestCase { @@ -76,46 +94,407 @@ private ObjectId oldCommitId; + @Override public void setUp() throws Exception { super.setUp(); defaultDb = createWorkRepository(); - Git git = new Git(defaultDb); - JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "branch world"); - git.add().addFilepattern("hello.txt").call(); - oldCommitId = git.commit().setMessage("Initial commit").call().getId(); - git.checkout().setName(BRANCH).setCreateBranch(true).call(); - git.checkout().setName("master").call(); - git.tag().setName(TAG).call(); - JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "master world"); - git.add().addFilepattern("hello.txt").call(); - git.commit().setMessage("Second commit").call(); - addRepoToClose(defaultDb); + try (Git git = new Git(defaultDb)) { + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "branch world"); + git.add().addFilepattern("hello.txt").call(); + oldCommitId = git.commit().setMessage("Initial commit").call().getId(); + git.checkout().setName(BRANCH).setCreateBranch(true).call(); + git.checkout().setName("master").call(); + git.tag().setName(TAG).call(); + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "master world"); + git.add().addFilepattern("hello.txt").call(); + git.commit().setMessage("Second commit").call(); + addRepoToClose(defaultDb); + } notDefaultDb = createWorkRepository(); - git = new Git(notDefaultDb); - JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello"); - git.add().addFilepattern("world.txt").call(); - git.commit().setMessage("Initial commit").call(); - addRepoToClose(notDefaultDb); + try (Git git = new Git(notDefaultDb)) { + JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello"); + git.add().addFilepattern("world.txt").call(); + git.commit().setMessage("Initial commit").call(); + addRepoToClose(notDefaultDb); + } groupADb = createWorkRepository(); - git = new Git(groupADb); - JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world"); - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("Initial commit").call(); - addRepoToClose(groupADb); + try (Git git = new Git(groupADb)) { + JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world"); + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("Initial commit").call(); + addRepoToClose(groupADb); + } groupBDb = createWorkRepository(); - git = new Git(groupBDb); - JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world"); - git.add().addFilepattern("b.txt").call(); - git.commit().setMessage("Initial commit").call(); - addRepoToClose(groupBDb); + try (Git git = new Git(groupBDb)) { + JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world"); + git.add().addFilepattern("b.txt").call(); + git.commit().setMessage("Initial commit").call(); + addRepoToClose(groupBDb); + } resolveRelativeUris(); } + class IndexedRepos implements RepoCommand.RemoteReader { + Map uriRepoMap; + IndexedRepos() { + uriRepoMap = new HashMap<>(); + } + + void put(String u, Repository r) { + uriRepoMap.put(u, r); + } + + @Override + public ObjectId sha1(String uri, String refname) throws GitAPIException { + if (!uriRepoMap.containsKey(uri)) { + return null; + } + + Repository r = uriRepoMap.get(uri); + try { + Ref ref = r.findRef(refname); + if (ref == null) return null; + + ref = r.peel(ref); + ObjectId id = ref.getObjectId(); + return id; + } catch (IOException e) { + throw new InvalidRemoteException("", e); + } + } + + @Override + public byte[] readFile(String uri, String refName, String path) + throws GitAPIException, IOException { + Repository repo = uriRepoMap.get(uri); + + String idStr = refName + ":" + path; + ObjectId id = repo.resolve(idStr); + if (id == null) { + throw new RefNotFoundException( + String.format("repo %s does not have %s", repo.toString(), idStr)); + } + try (ObjectReader reader = repo.newObjectReader()) { + return reader.open(id).getCachedBytes(Integer.MAX_VALUE); + } + } + } + + @Test + public void runTwiceIsNOP() throws Exception { + Repository child = Git.cloneRepository() + .setURI(groupADb.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)).setBare(true).call() + .getRepository(); + + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)).setBare(true).call() + .getRepository(); + + assertTrue(dest.isBare()); + assertTrue(child.isBare()); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + RepoCommand cmd = new RepoCommand(dest); + + IndexedRepos repos = new IndexedRepos(); + repos.put("platform/base", child); + + RevCommit commit = cmd + .setInputStream(new ByteArrayInputStream( + xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI("platform/") + .setTargetURI("platform/superproject") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + + String firstIdStr = commit.getId().name() + ":" + ".gitmodules"; + commit = new RepoCommand(dest) + .setInputStream(new ByteArrayInputStream( + xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI("platform/") + .setTargetURI("platform/superproject") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + String idStr = commit.getId().name() + ":" + ".gitmodules"; + assertEquals(firstIdStr, idStr); + child.close(); + dest.close(); + } + + @Test + public void androidSetup() throws Exception { + Repository child = Git.cloneRepository() + .setURI(groupADb.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)).setBare(true).call() + .getRepository(); + + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)).setBare(true).call() + .getRepository(); + + assertTrue(dest.isBare()); + assertTrue(child.isBare()); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + RepoCommand cmd = new RepoCommand(dest); + + IndexedRepos repos = new IndexedRepos(); + repos.put("platform/base", child); + + RevCommit commit = cmd + .setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI("platform/") + .setTargetURI("platform/superproject") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + + String idStr = commit.getId().name() + ":" + ".gitmodules"; + ObjectId modId = dest.resolve(idStr); + + try (ObjectReader reader = dest.newObjectReader()) { + byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE); + Config base = new Config(); + BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); + String subUrl = cfg.getString("submodule", "base", "url"); + assertEquals(subUrl, "../base"); + } + + child.close(); + dest.close(); + } + + @Test + public void recordUnreachableRemotes() throws Exception { + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)).setBare(true).call() + .getRepository(); + + assertTrue(dest.isBare()); + + RevCommit commit = new RepoCommand(dest) + .setInputStream(new ByteArrayInputStream( + xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(new IndexedRepos()) + .setURI("platform/") + .setTargetURI("platform/superproject") + .setRecordRemoteBranch(true) + .setIgnoreRemoteFailures(true) + .setRecordSubmoduleLabels(true) + .call(); + + String idStr = commit.getId().name() + ":" + ".gitmodules"; + ObjectId modId = dest.resolve(idStr); + + try (ObjectReader reader = dest.newObjectReader()) { + byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE); + Config base = new Config(); + BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); + String subUrl = cfg.getString("submodule", "base", "url"); + assertEquals(subUrl, "https://host.com/platform/base"); + } + + dest.close(); + } + + @Test + public void gerritSetup() throws Exception { + Repository child = + Git.cloneRepository().setURI(groupADb.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()).setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + + assertTrue(dest.isBare()); + assertTrue(child.isBare()); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + RepoCommand cmd = new RepoCommand(dest); + + IndexedRepos repos = new IndexedRepos(); + repos.put("plugins/cookbook", child); + + RevCommit commit = cmd + .setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI("") + .setTargetURI("gerrit") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + + String idStr = commit.getId().name() + ":" + ".gitmodules"; + ObjectId modId = dest.resolve(idStr); + + try (ObjectReader reader = dest.newObjectReader()) { + byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE); + Config base = new Config(); + BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); + String subUrl = cfg.getString("submodule", "plugins/cookbook", "url"); + assertEquals(subUrl, "../plugins/cookbook"); + } + + child.close(); + dest.close(); + } + + @Test + public void absoluteRemoteURL() throws Exception { + Repository child = + Git.cloneRepository().setURI(groupADb.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()).setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + String abs = "https://chromium.googlesource.com"; + String repoUrl = "https://chromium.googlesource.com/chromium/src"; + boolean fetchSlash = false; + boolean baseSlash = false; + do { + do { + String fetchUrl = fetchSlash ? abs + "/" : abs; + String baseUrl = baseSlash ? abs + "/" : abs; + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + RepoCommand cmd = new RepoCommand(dest); + + IndexedRepos repos = new IndexedRepos(); + repos.put(repoUrl, child); + + RevCommit commit = cmd + .setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI(baseUrl) + .setTargetURI("gerrit") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + + String idStr = commit.getId().name() + ":" + ".gitmodules"; + ObjectId modId = dest.resolve(idStr); + + try (ObjectReader reader = dest.newObjectReader()) { + byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE); + Config base = new Config(); + BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); + String subUrl = cfg.getString("submodule", "src", "url"); + assertEquals("https://chromium.googlesource.com/chromium/src", subUrl); + } + fetchSlash = !fetchSlash; + } while (fetchSlash); + baseSlash = !baseSlash; + } while (baseSlash); + child.close(); + dest.close(); + } + + @Test + public void absoluteRemoteURLAbsoluteTargetURL() throws Exception { + Repository child = + Git.cloneRepository().setURI(groupADb.getDirectory().toURI().toString()) + .setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + Repository dest = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()).setDirectory(createUniqueTestGitDir(true)) + .setBare(true).call().getRepository(); + String abs = "https://chromium.googlesource.com"; + String repoUrl = "https://chromium.googlesource.com/chromium/src"; + boolean fetchSlash = false; + boolean baseSlash = false; + do { + do { + String fetchUrl = fetchSlash ? abs + "/" : abs; + String baseUrl = baseSlash ? abs + "/" : abs; + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + RepoCommand cmd = new RepoCommand(dest); + + IndexedRepos repos = new IndexedRepos(); + repos.put(repoUrl, child); + + RevCommit commit = cmd + .setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8))) + .setRemoteReader(repos) + .setURI(baseUrl) + .setTargetURI(abs + "/superproject") + .setRecordRemoteBranch(true) + .setRecordSubmoduleLabels(true) + .call(); + + String idStr = commit.getId().name() + ":" + ".gitmodules"; + ObjectId modId = dest.resolve(idStr); + + try (ObjectReader reader = dest.newObjectReader()) { + byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE); + Config base = new Config(); + BlobBasedConfig cfg = new BlobBasedConfig(base, bytes); + String subUrl = cfg.getString("submodule", "src", "url"); + assertEquals("../chromium/src", subUrl); + } + fetchSlash = !fetchSlash; + } while (fetchSlash); + baseSlash = !baseSlash; + } while (baseSlash); + child.close(); + dest.close(); + } + @Test public void testAddRepoManifest() throws Exception { StringBuilder xmlContent = new StringBuilder(); @@ -241,46 +620,42 @@ @Test public void testBareRepo() throws Exception { - try ( - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", - xmlContent.toString()); - RepoCommand command = new RepoCommand(remoteDb); - command.setPath( - tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") - .setURI(rootUri).call(); - // Clone it - File directory = createTempDirectory("testBareRepo"); - Repository localDb = Git.cloneRepository().setDirectory(directory) - .setURI(remoteDb.getDirectory().toURI().toString()).call() - .getRepository(); - // The .gitmodules file should exist - File gitmodules = new File(localDb.getWorkTree(), ".gitmodules"); - assertTrue("The .gitmodules file should exist", gitmodules.exists()); - // The first line of .gitmodules file should be expected - BufferedReader reader = new BufferedReader(new FileReader( - gitmodules)); - String content = reader.readLine(); - reader.close(); - assertEquals( - "The first line of .gitmodules file should be as expected", - "[submodule \"foo\"]", content); - // The gitlink should be the same as remote head sha1 - String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); - localDb.close(); - String remote = defaultDb.resolve(Constants.HEAD).name(); - assertEquals("The gitlink should be the same as remote head", - remote, gitlink); - } + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testBareRepo"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + // The .gitmodules file should exist + File gitmodules = new File(localDb.getWorkTree(), ".gitmodules"); + assertTrue("The .gitmodules file should exist", gitmodules.exists()); + // The first line of .gitmodules file should be expected + BufferedReader reader = new BufferedReader(new FileReader(gitmodules)); + String content = reader.readLine(); + reader.close(); + assertEquals("The first line of .gitmodules file should be as expected", + "[submodule \"foo\"]", content); + // The gitlink should be the same as remote head sha1 + String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); + localDb.close(); + String remote = defaultDb.resolve(Constants.HEAD).name(); + assertEquals("The gitlink should be the same as remote head", remote, + gitlink); } @Test @@ -363,203 +738,192 @@ @Test public void testRevisionBare() throws Exception { - try ( - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent.append("\n") - .append("") - .append("") - .append("") - .append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", - xmlContent.toString()); - RepoCommand command = new RepoCommand(remoteDb); - command.setPath( - tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") - .setURI(rootUri).call(); - // Clone it - File directory = createTempDirectory("testRevisionBare"); - Repository localDb = Git.cloneRepository().setDirectory(directory) - .setURI(remoteDb.getDirectory().toURI().toString()).call() - .getRepository(); - // The gitlink should be the same as oldCommitId - String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); - localDb.close(); - assertEquals("The gitlink is same as remote head", - oldCommitId.name(), gitlink); - } + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testRevisionBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + // The gitlink should be the same as oldCommitId + String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); + localDb.close(); + assertEquals("The gitlink is same as remote head", oldCommitId.name(), + gitlink); } @Test public void testCopyFileBare() throws Exception { - try ( - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", - xmlContent.toString()); - RepoCommand command = new RepoCommand(remoteDb); - command.setPath( - tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") - .setURI(rootUri).call(); - // Clone it - File directory = createTempDirectory("testCopyFileBare"); - Repository localDb = Git.cloneRepository().setDirectory(directory) - .setURI(remoteDb.getDirectory().toURI().toString()).call() - .getRepository(); - // The Hello file should exist - File hello = new File(localDb.getWorkTree(), "Hello"); - localDb.close(); - assertTrue("The Hello file should exist", hello.exists()); - // The content of Hello file should be expected - BufferedReader reader = new BufferedReader(new FileReader(hello)); - String content = reader.readLine(); - reader.close(); - assertEquals("The Hello file should have expected content", - "branch world", content); - } + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testCopyFileBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + // The Hello file should exist + File hello = new File(localDb.getWorkTree(), "Hello"); + assertTrue("The Hello file should exist", hello.exists()); + // The foo/Hello file should be skipped. + File foohello = new File(localDb.getWorkTree(), "foo/Hello"); + assertFalse("The foo/Hello file should be skipped", foohello.exists()); + localDb.close(); + // The content of Hello file should be expected + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("The Hello file should have expected content", + "branch world", content); } @Test public void testReplaceManifestBare() throws Exception { - try ( - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "old.xml", - xmlContent.toString()); - RepoCommand command = new RepoCommand(remoteDb); - command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/old.xml") - .setURI(rootUri).call(); - xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "new.xml", - xmlContent.toString()); - command = new RepoCommand(remoteDb); - command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/new.xml") - .setURI(rootUri).call(); - // Clone it - File directory = createTempDirectory("testReplaceManifestBare"); - Repository localDb = Git.cloneRepository().setDirectory(directory) - .setURI(remoteDb.getDirectory().toURI().toString()).call() - .getRepository(); - // The Hello file should not exist - File hello = new File(localDb.getWorkTree(), "Hello"); - assertFalse("The Hello file shouldn't exist", hello.exists()); - // The Hello.txt file should exist - File hellotxt = new File(localDb.getWorkTree(), "Hello.txt"); - assertTrue("The Hello.txt file should exist", hellotxt.exists()); - // The .gitmodules file should have 'submodule "bar"' and shouldn't - // have - // 'submodule "foo"' lines. - File dotmodules = new File(localDb.getWorkTree(), - Constants.DOT_GIT_MODULES); - localDb.close(); - BufferedReader reader = new BufferedReader(new FileReader( - dotmodules)); - boolean foo = false; - boolean bar = false; - while (true) { - String line = reader.readLine(); - if (line == null) - break; - if (line.contains("submodule \"foo\"")) - foo = true; - if (line.contains("submodule \"bar\"")) - bar = true; - } - reader.close(); - assertTrue("The bar submodule should exist", bar); - assertFalse("The foo submodule shouldn't exist", foo); + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "old.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/old.xml") + .setURI(rootUri).call(); + xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "new.xml", xmlContent.toString()); + command = new RepoCommand(remoteDb); + command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/new.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testReplaceManifestBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + // The Hello file should not exist + File hello = new File(localDb.getWorkTree(), "Hello"); + assertFalse("The Hello file shouldn't exist", hello.exists()); + // The Hello.txt file should exist + File hellotxt = new File(localDb.getWorkTree(), "Hello.txt"); + assertTrue("The Hello.txt file should exist", hellotxt.exists()); + // The .gitmodules file should have 'submodule "bar"' and shouldn't + // have + // 'submodule "foo"' lines. + File dotmodules = new File(localDb.getWorkTree(), + Constants.DOT_GIT_MODULES); + localDb.close(); + BufferedReader reader = new BufferedReader(new FileReader(dotmodules)); + boolean foo = false; + boolean bar = false; + while (true) { + String line = reader.readLine(); + if (line == null) + break; + if (line.contains("submodule \"foo\"")) + foo = true; + if (line.contains("submodule \"bar\"")) + bar = true; } + reader.close(); + assertTrue("The bar submodule should exist", bar); + assertFalse("The foo submodule shouldn't exist", foo); } @Test public void testRemoveOverlappingBare() throws Exception { - try ( - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("") - .append("").append("").append(""); - JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", - xmlContent.toString()); - RepoCommand command = new RepoCommand(remoteDb); - command.setPath( - tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") - .setURI(rootUri).call(); - // Clone it - File directory = createTempDirectory("testRemoveOverlappingBare"); - Repository localDb = Git.cloneRepository().setDirectory(directory) - .setURI(remoteDb.getDirectory().toURI().toString()).call() - .getRepository(); - // The .gitmodules file should have 'submodule "foo"' and shouldn't - // have - // 'submodule "foo/bar"' lines. - File dotmodules = new File(localDb.getWorkTree(), - Constants.DOT_GIT_MODULES); - localDb.close(); - BufferedReader reader = new BufferedReader(new FileReader( - dotmodules)); - boolean foo = false; - boolean foobar = false; - boolean a = false; - while (true) { - String line = reader.readLine(); - if (line == null) - break; - if (line.contains("submodule \"foo\"")) - foo = true; - if (line.contains("submodule \"foo/bar\"")) - foobar = true; - if (line.contains("submodule \"a\"")) - a = true; - } - reader.close(); - assertTrue("The foo submodule should exist", foo); - assertFalse("The foo/bar submodule shouldn't exist", foobar); - assertTrue("The a submodule should exist", a); + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testRemoveOverlappingBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + // The .gitmodules file should have 'submodule "foo"' and shouldn't + // have + // 'submodule "foo/bar"' lines. + File dotmodules = new File(localDb.getWorkTree(), + Constants.DOT_GIT_MODULES); + localDb.close(); + BufferedReader reader = new BufferedReader(new FileReader(dotmodules)); + boolean foo = false; + boolean foobar = false; + boolean a = false; + while (true) { + String line = reader.readLine(); + if (line == null) + break; + if (line.contains("submodule \"foo\"")) + foo = true; + if (line.contains("submodule \"foo/bar\"")) + foobar = true; + if (line.contains("submodule \"a\"")) + a = true; } + reader.close(); + assertTrue("The foo submodule should exist", foo); + assertFalse("The foo/bar submodule shouldn't exist", foobar); + assertTrue("The a submodule should exist", a); } @Test @@ -601,21 +965,16 @@ assertEquals("submodule content should be as expected", "master world", content); } - @Test - public void testNonDefaultRemotes() throws Exception { + public void testRemoteAlias() throws Exception { StringBuilder xmlContent = new StringBuilder(); xmlContent.append("\n") .append("") - .append("") - .append("") - .append("") + .append("") + .append("") .append("") - .append("") .append(""); Repository localDb = createWorkRepository(); @@ -628,68 +987,215 @@ .call(); File file = new File(localDb.getWorkTree(), "foo/hello.txt"); assertTrue("We should have foo", file.exists()); - file = new File(localDb.getWorkTree(), "bar/world.txt"); - assertTrue("We should have bar", file.exists()); } @Test - public void testRemoteAlias() throws Exception { + public void testTargetBranch() throws Exception { + Repository remoteDb1 = createBareRepository(); + Repository remoteDb2 = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append(""); + 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); + } + + @Test + public void testRecordRemoteBranch() throws Exception { + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).setRecordRemoteBranch(true).call(); + // Clone it + File directory = createTempDirectory("testBareRepo"); + try (Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository();) { + // The .gitmodules file should exist + File gitmodules = new File(localDb.getWorkTree(), ".gitmodules"); + assertTrue("The .gitmodules file should exist", + gitmodules.exists()); + FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED); + c.load(); + assertEquals( + "Recording remote branches should work for short branch descriptions", + "master", + c.getString("submodule", "with-branch", "branch")); + assertEquals( + "Recording remote branches should work for full ref specs", + "refs/heads/master", + c.getString("submodule", "with-long-branch", "branch")); + } + } + + + @Test + public void testRecordSubmoduleLabels() throws Exception { + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).setRecordSubmoduleLabels(true).call(); + // Clone it + File directory = createTempDirectory("testBareRepo"); + try (Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository();) { + // The .gitattributes file should exist + File gitattributes = new File(localDb.getWorkTree(), + ".gitattributes"); + assertTrue("The .gitattributes file should exist", + gitattributes.exists()); + try (BufferedReader reader = new BufferedReader( + new FileReader(gitattributes));) { + String content = reader.readLine(); + assertEquals(".gitattributes content should be as expected", + "/test a1 a2", content); + } + } + } + + @Test + public void testRecordShallowRecommendation() throws Exception { + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).setRecommendShallow(true).call(); + // Clone it + File directory = createTempDirectory("testBareRepo"); + try (Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository();) { + // The .gitmodules file should exist + File gitmodules = new File(localDb.getWorkTree(), ".gitmodules"); + assertTrue("The .gitmodules file should exist", + gitmodules.exists()); + FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED); + c.load(); + assertEquals("Recording shallow configuration should work", "true", + c.getString("submodule", "shallow-please", "shallow")); + assertNull("Recording non shallow configuration should work", + c.getString("submodule", "non-shallow", "shallow")); + } + } + + @Test + public void testRemoteRevision() throws Exception { StringBuilder xmlContent = new StringBuilder(); xmlContent.append("\n") .append("") - .append("") - .append("") - .append("") + .append("") + .append("") + .append("") .append(""); - - Repository localDb = createWorkRepository(); - JGitTestUtil.writeTrashFile( - localDb, "manifest.xml", xmlContent.toString()); - RepoCommand command = new RepoCommand(localDb); - command - .setPath(localDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + writeTrashFile("manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(db); + command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml") .setURI(rootUri) .call(); - File file = new File(localDb.getWorkTree(), "foo/hello.txt"); - assertTrue("We should have foo", file.exists()); + File hello = new File(db.getWorkTree(), "foo/hello.txt"); + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("submodule content should be as expected", + "branch world", content); } @Test - public void testTargetBranch() throws Exception { - try ( - Repository remoteDb1 = createBareRepository(); - Repository remoteDb2 = createBareRepository(); - Repository tempDb = createWorkRepository()) { - StringBuilder xmlContent = new StringBuilder(); - xmlContent - .append("\n") - .append("") - .append("") - .append("") - .append("").append(""); - 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); - } + public void testDefaultRemoteRevision() throws Exception { + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + writeTrashFile("manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(db); + command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + File hello = new File(db.getWorkTree(), "foo/hello.txt"); + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("submodule content should be as expected", + "branch world", content); } private void resolveRelativeUris() { @@ -716,4 +1222,29 @@ start = newStart; } } + + void testRelative(String a, String b, String want) { + String got = RepoCommand.relativize(URI.create(a), URI.create(b)).toString(); + + if (!got.equals(want)) { + fail(String.format("relative('%s', '%s') = '%s', want '%s'", a, b, got, want)); + } + } + + @Test + public void relative() { + testRelative("a/b/", "a/", "../"); + // Normalization: + testRelative("a/p/..//b/", "a/", "../"); + testRelative("a/b", "a/", ""); + testRelative("a/", "a/b/", "b/"); + testRelative("a/", "a/b", "b"); + testRelative("/a/b/c", "/b/c", "../../b/c"); + testRelative("/abc", "bcd", "bcd"); + testRelative("abc", "def", "def"); + testRelative("abc", "/bcd", "/bcd"); + testRelative("http://a", "a/b", "a/b"); + testRelative("http://base.com/a/", "http://child.com/a/b", "http://child.com/a/b"); + testRelative("http://base.com/a/", "http://base.com/a/b", "b"); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import org.eclipse.jgit.ignore.internal.Strings; import org.junit.Test; public class BasicRuleTest { @@ -73,4 +74,31 @@ assertNotEquals(rule1.toString(), rule3.toString()); } + @Test + public void testDirectoryPattern() { + assertTrue(Strings.isDirectoryPattern("/")); + assertTrue(Strings.isDirectoryPattern("/ ")); + assertTrue(Strings.isDirectoryPattern("/ ")); + assertFalse(Strings.isDirectoryPattern(" ")); + assertFalse(Strings.isDirectoryPattern("")); + } + + @Test + public void testStripTrailingChar() { + assertEquals("", Strings.stripTrailing("/", '/')); + assertEquals("", Strings.stripTrailing("///", '/')); + assertEquals("a", Strings.stripTrailing("a/", '/')); + assertEquals("a", Strings.stripTrailing("a///", '/')); + assertEquals("a/ ", Strings.stripTrailing("a/ ", '/')); + } + + @Test + public void testStripTrailingWhitespace() { + assertEquals("", Strings.stripTrailingWhitespace("")); + assertEquals("", Strings.stripTrailingWhitespace(" ")); + assertEquals("a", Strings.stripTrailingWhitespace("a")); + assertEquals("a", Strings.stripTrailingWhitespace("a ")); + assertEquals("a", Strings.stripTrailingWhitespace("a ")); + assertEquals("a", Strings.stripTrailingWhitespace("a \t")); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2017 Thomas Wolf + * 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.ignore; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests that verify that the set of ignore files in a repository is the same in + * JGit and in C-git. + */ +public class CGitIgnoreTest extends RepositoryTestCase { + + @Before + public void initRepo() throws IOException { + // These tests focus on .gitignore files inside the repository. Because + // we run C-git, we must ensure that global or user exclude files cannot + // influence the tests. So we set core.excludesFile to an empty file + // inside the repository. + File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", ""); + StoredConfig config = db.getConfig(); + config.setString("core", null, "excludesFile", + fakeUserGitignore.getAbsolutePath()); + // Disable case-insensitivity -- JGit doesn't handle that yet. + config.setBoolean("core", null, "ignoreCase", false); + config.save(); + } + + private void createFiles(String... paths) throws IOException { + for (String path : paths) { + writeTrashFile(path, "x"); + } + } + + private String toString(TemporaryBuffer b) throws IOException { + return RawParseUtils.decode(b.toByteArray()); + } + + private String[] cgitIgnored() throws Exception { + FS fs = db.getFS(); + ProcessBuilder builder = fs.runInShell("git", new String[] { "ls-files", + "--ignored", "--exclude-standard", "-o" }); + builder.directory(db.getWorkTree()); + builder.environment().put("HOME", fs.userHome().getAbsolutePath()); + ExecutionResult result = fs.execute(builder, + new ByteArrayInputStream(new byte[0])); + String errorOut = toString(result.getStderr()); + assertEquals("External git failed", "exit 0\n", + "exit " + result.getRc() + '\n' + errorOut); + try (BufferedReader r = new BufferedReader(new InputStreamReader( + new BufferedInputStream(result.getStdout().openInputStream()), + Constants.CHARSET))) { + return r.lines().toArray(String[]::new); + } + } + + private String[] cgitUntracked() throws Exception { + FS fs = db.getFS(); + ProcessBuilder builder = fs.runInShell("git", + new String[] { "ls-files", "--exclude-standard", "-o" }); + builder.directory(db.getWorkTree()); + builder.environment().put("HOME", fs.userHome().getAbsolutePath()); + ExecutionResult result = fs.execute(builder, + new ByteArrayInputStream(new byte[0])); + String errorOut = toString(result.getStderr()); + assertEquals("External git failed", "exit 0\n", + "exit " + result.getRc() + '\n' + errorOut); + try (BufferedReader r = new BufferedReader(new InputStreamReader( + new BufferedInputStream(result.getStdout().openInputStream()), + Constants.CHARSET))) { + return r.lines().toArray(String[]::new); + } + } + + private void jgitIgnoredAndUntracked(LinkedHashSet ignored, + LinkedHashSet untracked) throws IOException { + // Do a tree walk that does descend into ignored directories and return + // a list of all ignored files + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new FileTreeIterator(db)); + walk.setRecursive(true); + while (walk.next()) { + if (walk.getTree(WorkingTreeIterator.class).isEntryIgnored()) { + ignored.add(walk.getPathString()); + } else { + // tests of this class won't add any files to the index, + // hence everything what is not ignored is untracked + untracked.add(walk.getPathString()); + } + } + } + } + + private void assertNoIgnoredVisited(Set ignored) throws Exception { + // Do a recursive tree walk with a NotIgnoredFilter and verify that none + // of the files visited is in the ignored set + try (TreeWalk walk = new TreeWalk(db)) { + walk.addTree(new FileTreeIterator(db)); + walk.setFilter(new NotIgnoredFilter(0)); + walk.setRecursive(true); + while (walk.next()) { + String path = walk.getPathString(); + assertFalse("File " + path + " is ignored, should not appear", + ignored.contains(path)); + } + } + } + + private void assertSameAsCGit(String... notIgnored) throws Exception { + LinkedHashSet ignored = new LinkedHashSet<>(); + LinkedHashSet untracked = new LinkedHashSet<>(); + jgitIgnoredAndUntracked(ignored, untracked); + String[] cgit = cgitIgnored(); + String[] cgitUntracked = cgitUntracked(); + assertArrayEquals(cgit, ignored.toArray()); + assertArrayEquals(cgitUntracked, untracked.toArray()); + for (String notExcluded : notIgnored) { + assertFalse("File " + notExcluded + " should not be ignored", + ignored.contains(notExcluded)); + } + assertNoIgnoredVisited(ignored); + } + + @Test + public void testSimpleIgnored() throws Exception { + createFiles("a.txt", "a.tmp", "src/sub/a.txt", "src/a.tmp", + "src/a.txt/b.tmp", "ignored/a.tmp", "ignored/not_ignored/a.tmp", + "ignored/other/a.tmp"); + writeTrashFile(".gitignore", + "*.txt\n" + "/ignored/*\n" + "!/ignored/not_ignored"); + assertSameAsCGit("ignored/not_ignored/a.tmp"); + } + + @Test + public void testDirOnlyMatch() throws Exception { + createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt"); + writeTrashFile(".gitignore", "foo/"); + assertSameAsCGit(); + } + + @Test + public void testDirOnlyMatchDeep() throws Exception { + createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt"); + writeTrashFile(".gitignore", "**/foo/"); + assertSameAsCGit(); + } + + @Test + public void testStarMatchOnSlashNot() throws Exception { + createFiles("sub/a.txt", "foo/sext", "foo/s.txt"); + writeTrashFile(".gitignore", "s*xt"); + assertSameAsCGit("sub/a.txt"); + } + + @Test + public void testPrefixMatch() throws Exception { + createFiles("src/new/foo.txt"); + writeTrashFile(".gitignore", "src/new"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursive() throws Exception { + createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new"); + writeTrashFile(".gitignore", "**/src/new/"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack() throws Exception { + createFiles("src/new/foo.txt", "src/src/new/foo.txt"); + writeTrashFile(".gitignore", "**/src/new/"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception { + createFiles("src/new/foo.txt", "src/src/new/foo.txt"); + writeTrashFile(".gitignore", "**/**/src/new/"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception { + createFiles("x/a/a/b/foo.txt"); + writeTrashFile(".gitignore", "**/*/a/b/"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception { + createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt", + "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt"); + writeTrashFile(".gitignore", "**/*/a/b bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception { + createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt"); + writeTrashFile(".gitignore", "**/*/**/a/b bar\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles1() throws Exception { + createFiles("a", "dir/b", "dir/sub/c"); + writeTrashFile(".gitignore", "**/\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles2() throws Exception { + createFiles("a", "dir/b", "dir/sub/c"); + writeTrashFile(".gitignore", "**/**/\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles3() throws Exception { + createFiles("a", "x/b", "sub/x/c", "sub/x/d/e"); + writeTrashFile(".gitignore", "x/**/\n"); + assertSameAsCGit(); + } + + @Test + public void testDirectoryWildmatchDoesNotMatchFiles4() throws Exception { + createFiles("a", "dir/x", "dir/sub1/x", "dir/sub2/x/y"); + writeTrashFile(".gitignore", "**/x/\n"); + assertSameAsCGit(); + } + + @Test + public void testUnescapedBracketsInGroup() throws Exception { + createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]"); + writeTrashFile(".gitignore", "[[]]\n"); + assertSameAsCGit(); + } + + @Test + public void testEscapedFirstBracketInGroup() throws Exception { + createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]"); + writeTrashFile(".gitignore", "[\\[]]\n"); + assertSameAsCGit(); + } + + @Test + public void testEscapedSecondBracketInGroup() throws Exception { + createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]"); + writeTrashFile(".gitignore", "[[\\]]\n"); + assertSameAsCGit(); + } + + @Test + public void testEscapedBothBracketsInGroup() throws Exception { + createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]"); + writeTrashFile(".gitignore", "[\\[\\]]\n"); + assertSameAsCGit(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalNegation1() throws Exception { + // see IgnoreNodeTest.testSimpleRootGitIgnoreGlobalNegation1 + createFiles("x1", "a/x2", "x3/y"); + writeTrashFile(".gitignore", "*\n!x*"); + assertSameAsCGit(); + } + + @Test + public void testRepeatedNegationInDifferentFiles5() throws Exception { + // see IgnoreNodeTest.testRepeatedNegationInDifferentFiles5 + createFiles("a/b/e/nothere.o"); + writeTrashFile(".gitignore", "e"); + writeTrashFile("a/.gitignore", "e"); + writeTrashFile("a/b/.gitignore", "!e"); + assertSameAsCGit(); + } + + @Test + public void testRepeatedNegationInDifferentFilesWithWildmatcher1() + throws Exception { + createFiles("e", "x/e/f", "a/e/x1", "a/e/x2", "a/e/y", "a/e/sub/y"); + writeTrashFile(".gitignore", "a/e/**"); + writeTrashFile("a/.gitignore", "!e/x*"); + assertSameAsCGit(); + } + + @Test + public void testRepeatedNegationInDifferentFilesWithWildmatcher2() + throws Exception { + createFiles("e", "dir/f", "dir/g/h", "a/dir/i", "a/dir/j/k", + "a/b/dir/l", "a/b/dir/m/n", "a/b/dir/m/o/p", "a/q/dir/r", + "a/q/dir/dir/s", "c/d/dir/x", "c/d/dir/y"); + writeTrashFile(".gitignore", "**/dir/*"); + writeTrashFile("a/.gitignore", "!dir/*"); + writeTrashFile("a/b/.gitignore", "!**/dir/*"); + writeTrashFile("c/.gitignore", "!d/dir/x"); + assertSameAsCGit(); + } + + @Test + public void testNegationForSubDirectoryWithinIgnoredDirectoryHasNoEffect1() + throws Exception { + createFiles("e", "a/f", "a/b/g", "a/b/h/i"); + writeTrashFile(".gitignore", "a/b"); + writeTrashFile("a/.gitignore", "!b/*"); + assertSameAsCGit(); + } + + /* + * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=407475 + */ + @Test + public void testNegationAllExceptJavaInSrcAndExceptChildDirInSrc() + throws Exception { + // see + // IgnoreNodeTest.testNegationAllExceptJavaInSrcAndExceptChildDirInSrc + createFiles("nothere.o", "src/keep.java", "src/nothere.o", + "src/a/keep.java", "src/a/keep.o"); + writeTrashFile(".gitignore", "/*\n!/src/"); + writeTrashFile("src/.gitignore", "*\n!*.java\n!*/"); + assertSameAsCGit(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,12 +48,21 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.junit.Before; import org.junit.Test; public class FastIgnoreRuleTest { + private boolean pathMatch; + + @Before + public void setup() { + pathMatch = false; + } + @Test public void testSimpleCharClass() { + assertMatched("][a]", "]a"); assertMatched("[a]", "a"); assertMatched("][a]", "]a"); assertMatched("[a]", "a/"); @@ -122,6 +131,17 @@ } @Test + public void testTrailingSpaces() { + assertMatched("a ", "a"); + assertMatched("a/ ", "a/"); + assertMatched("a/ ", "a/b"); + assertMatched("a/\\ ", "a/ "); + assertNotMatched("a/\\ ", "a/"); + assertNotMatched("a/\\ ", "a/b"); + assertNotMatched("/ ", "a"); + } + + @Test public void testAsteriskDot() { assertMatched("*.a", ".a"); assertMatched("*.a", "/.a"); @@ -379,7 +399,6 @@ assertMatched("/**/a/b", "c/d/a/b"); assertMatched("/**/**/a/b", "c/d/a/b"); - assertMatched("a/b/**", "a/b"); assertMatched("a/b/**", "a/b/c"); assertMatched("a/b/**", "a/b/c/d/"); assertMatched("a/b/**/**", "a/b/c/d"); @@ -399,14 +418,37 @@ assertMatched("a/**/b/**/c", "a/c/b/d/c"); assertMatched("a/**/**/b/**/**/c", "a/c/b/d/c"); + + assertMatched("**/", "a/"); + assertMatched("**/", "a/b"); + assertMatched("**/", "a/b/c"); + assertMatched("**/**/", "a/"); + assertMatched("**/**/", "a/b"); + assertMatched("**/**/", "a/b/"); + assertMatched("**/**/", "a/b/c"); + assertMatched("x/**/", "x/a/"); + assertMatched("x/**/", "x/a/b"); + assertMatched("x/**/", "x/a/b/"); + assertMatched("**/x/", "a/x/"); + assertMatched("**/x/", "a/b/x/"); } @Test public void testWildmatchDoNotMatch() { + assertNotMatched("a/**", "a/"); + assertNotMatched("a/b/**", "a/b/"); + assertNotMatched("a/**", "a"); + assertNotMatched("a/b/**", "a/b"); + assertNotMatched("a/b/**/", "a/b"); + assertNotMatched("a/b/**/**", "a/b"); assertNotMatched("**/a/b", "a/c/b"); assertNotMatched("!/**/*.zip", "c/a/b.zip"); assertNotMatched("!**/*.zip", "c/a/b.zip"); assertNotMatched("a/**/b", "a/c/bb"); + + assertNotMatched("**/", "a"); + assertNotMatched("**/**/", "a"); + assertNotMatched("**/x/", "a/b/x"); } @SuppressWarnings("unused") @@ -448,7 +490,29 @@ split("/a/b/c/", '/').toArray()); } - public void assertMatched(String pattern, String path) { + @Test + public void testPathMatch() { + pathMatch = true; + assertMatched("a", "a"); + assertMatched("a/", "a/"); + assertNotMatched("a/", "a/b"); + + assertMatched("**", "a"); + assertMatched("**", "a/"); + assertMatched("**", "a/b"); + + assertNotMatched("**/", "a"); + assertNotMatched("**/", "a/b"); + assertMatched("**/", "a/"); + assertMatched("**/", "a/b/"); + + assertNotMatched("x/**/", "x/a"); + assertNotMatched("x/**/", "x/a/b"); + assertMatched("x/**/", "x/a/"); + assertMatched("x/**/", "x/y/a/"); + } + + private void assertMatched(String pattern, String path) { boolean match = match(pattern, path); String result = path + " is " + (match ? "ignored" : "not ignored") + " via '" + pattern + "' rule"; @@ -468,7 +532,7 @@ match); } - public void assertNotMatched(String pattern, String path) { + private void assertNotMatched(String pattern, String path) { boolean match = match(pattern, path); String result = path + " is " + (match ? "ignored" : "not ignored") + " via '" + pattern + "' rule"; @@ -503,7 +567,7 @@ FastIgnoreRule r = new FastIgnoreRule(pattern); // If speed of this test is ever an issue, we can use a presetRule field // to avoid recompiling a pattern each time. - boolean match = r.isMatch(target, isDirectory); + boolean match = r.isMatch(target, isDirectory, pathMatch); if (r.getNegation()) match = !match; return match; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -346,7 +346,7 @@ * Target file path relative to repository's GIT_DIR * @param assume */ - public void assertMatched(String pattern, String target, Boolean... assume) { + private void assertMatched(String pattern, String target, Boolean... assume) { boolean value = match(pattern, target); if (assume.length == 0 || !assume[0].booleanValue()) assertTrue("Expected a match for: " + pattern + " with: " + target, @@ -366,7 +366,7 @@ * Target file path relative to repository's GIT_DIR * @param assume */ - public void assertNotMatched(String pattern, String target, + private void assertNotMatched(String pattern, String target, Boolean... assume) { boolean value = match(pattern, target); if (assume.length == 0 || !assume[0].booleanValue()) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,7 @@ */ package org.eclipse.jgit.ignore; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -55,7 +56,6 @@ import java.util.ArrayList; import java.util.Arrays; -import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.ignore.IgnoreNode.MatchResult; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.FileMode; @@ -63,6 +63,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.SystemReader; import org.junit.Test; /** @@ -80,6 +81,141 @@ private TreeWalk walk; @Test + public void testSimpleRootGitIgnoreGlobalIgnore() throws IOException { + writeIgnoreFile(".gitignore", "x"); + + writeTrashFile("a/x/file", ""); + writeTrashFile("b/x", ""); + writeTrashFile("x/file", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(D, ignored, "a/x"); + assertEntry(F, ignored, "a/x/file"); + assertEntry(D, tracked, "b"); + assertEntry(F, ignored, "b/x"); + assertEntry(D, ignored, "x"); + assertEntry(F, ignored, "x/file"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalDirIgnore() throws IOException { + writeIgnoreFile(".gitignore", "x/"); + + writeTrashFile("a/x/file", ""); + writeTrashFile("x/file", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(D, ignored, "a/x"); + assertEntry(F, ignored, "a/x/file"); + assertEntry(D, ignored, "x"); + assertEntry(F, ignored, "x/file"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreWildMatcher() throws IOException { + writeIgnoreFile(".gitignore", "**"); + + writeTrashFile("a/x", ""); + writeTrashFile("y", ""); + + beginWalk(); + assertEntry(F, ignored, ".gitignore"); + assertEntry(D, ignored, "a"); + assertEntry(F, ignored, "a/x"); + assertEntry(F, ignored, "y"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreWildMatcherDirOnly() throws IOException { + writeIgnoreFile(".gitignore", "**/"); + + writeTrashFile("a/x", ""); + writeTrashFile("y", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, ignored, "a"); + assertEntry(F, ignored, "a/x"); + assertEntry(F, tracked, "y"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalNegation1() throws IOException { + writeIgnoreFile(".gitignore", "*", "!x*"); + writeTrashFile("x1", ""); + writeTrashFile("a/x2", ""); + writeTrashFile("x3/y", ""); + + beginWalk(); + assertEntry(F, ignored, ".gitignore"); + assertEntry(D, ignored, "a"); + assertEntry(F, ignored, "a/x2"); + assertEntry(F, tracked, "x1"); + assertEntry(D, tracked, "x3"); + assertEntry(F, ignored, "x3/y"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalNegation2() throws IOException { + writeIgnoreFile(".gitignore", "*", "!x*", "!/a"); + writeTrashFile("x1", ""); + writeTrashFile("a/x2", ""); + writeTrashFile("x3/y", ""); + + beginWalk(); + assertEntry(F, ignored, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(F, tracked, "a/x2"); + assertEntry(F, tracked, "x1"); + assertEntry(D, tracked, "x3"); + assertEntry(F, ignored, "x3/y"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalNegation3() throws IOException { + writeIgnoreFile(".gitignore", "*", "!x*", "!x*/**"); + writeTrashFile("x1", ""); + writeTrashFile("a/x2", ""); + writeTrashFile("x3/y", ""); + + beginWalk(); + assertEntry(F, ignored, ".gitignore"); + assertEntry(D, ignored, "a"); + assertEntry(F, ignored, "a/x2"); + assertEntry(F, tracked, "x1"); + assertEntry(D, tracked, "x3"); + assertEntry(F, tracked, "x3/y"); + endWalk(); + } + + @Test + public void testSimpleRootGitIgnoreGlobalNegation4() throws IOException { + writeIgnoreFile(".gitignore", "*", "!**/"); + writeTrashFile("x1", ""); + writeTrashFile("a/x2", ""); + writeTrashFile("x3/y", ""); + + beginWalk(); + assertEntry(F, ignored, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(F, ignored, "a/x2"); + assertEntry(F, ignored, "x1"); + assertEntry(D, tracked, "x3"); + assertEntry(F, ignored, "x3/y"); + endWalk(); + } + + @Test public void testRules() throws IOException { writeIgnoreFile(".git/info/exclude", "*~", "/out"); @@ -209,7 +345,7 @@ assertEntry(F, ignored, "src/.gitignore"); assertEntry(D, tracked, "src/a"); assertEntry(F, tracked, "src/a/keep.java"); - assertEntry(F, tracked, "src/a/keep.o"); + assertEntry(F, ignored, "src/a/keep.o"); assertEntry(F, tracked, "src/keep.java"); assertEntry(F, ignored, "src/nothere.o"); endWalk(); @@ -315,6 +451,103 @@ } @Test + public void testRepeatedNegationInDifferentFiles5() throws IOException { + writeIgnoreFile(".gitignore", "e"); + writeIgnoreFile("a/.gitignore", "e"); + writeIgnoreFile("a/b/.gitignore", "!e"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(F, tracked, "a/.gitignore"); + assertEntry(D, tracked, "a/b"); + assertEntry(F, tracked, "a/b/.gitignore"); + assertEntry(D, tracked, "a/b/e"); + assertEntry(F, tracked, "a/b/e/nothere.o"); + endWalk(); + } + + @Test + public void testIneffectiveNegationDifferentLevels1() throws IOException { + writeIgnoreFile(".gitignore", "a/b/e/", "!a/b/e/*"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(D, tracked, "a/b"); + assertEntry(D, ignored, "a/b/e"); + assertEntry(F, ignored, "a/b/e/nothere.o"); + endWalk(); + } + + @Test + public void testIneffectiveNegationDifferentLevels2() throws IOException { + writeIgnoreFile(".gitignore", "a/b/e/"); + writeIgnoreFile("a/.gitignore", "!b/e/*"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(F, tracked, "a/.gitignore"); + assertEntry(D, tracked, "a/b"); + assertEntry(D, ignored, "a/b/e"); + assertEntry(F, ignored, "a/b/e/nothere.o"); + endWalk(); + } + + @Test + public void testIneffectiveNegationDifferentLevels3() throws IOException { + writeIgnoreFile(".gitignore", "a/b/e/"); + writeIgnoreFile("a/b/.gitignore", "!e/*"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(D, tracked, "a/b"); + assertEntry(F, tracked, "a/b/.gitignore"); + assertEntry(D, ignored, "a/b/e"); + assertEntry(F, ignored, "a/b/e/nothere.o"); + endWalk(); + } + + @Test + public void testIneffectiveNegationDifferentLevels4() throws IOException { + writeIgnoreFile(".gitignore", "a/b/e/"); + writeIgnoreFile("a/b/e/.gitignore", "!*"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(D, tracked, "a/b"); + assertEntry(D, ignored, "a/b/e"); + assertEntry(F, ignored, "a/b/e/.gitignore"); + assertEntry(F, ignored, "a/b/e/nothere.o"); + endWalk(); + } + + @Test + public void testIneffectiveNegationDifferentLevels5() throws IOException { + writeIgnoreFile("a/.gitignore", "b/e/"); + writeIgnoreFile("a/b/.gitignore", "!e/*"); + writeTrashFile("a/b/e/nothere.o", ""); + + beginWalk(); + assertEntry(D, tracked, "a"); + assertEntry(F, tracked, "a/.gitignore"); + assertEntry(D, tracked, "a/b"); + assertEntry(F, tracked, "a/b/.gitignore"); + assertEntry(D, ignored, "a/b/e"); + assertEntry(F, ignored, "a/b/e/nothere.o"); + endWalk(); + } + + @SuppressWarnings("deprecation") + @Test public void testEmptyIgnoreNode() { // Rules are never empty: WorkingTreeIterator optimizes empty files away // So we have to test it manually in case third party clients use @@ -468,6 +701,9 @@ @Test public void testTrailingSpaces() throws IOException { + // Windows can't create files with trailing spaces + // If this assumption fails the test is halted and ignored. + org.junit.Assume.assumeFalse(SystemReader.getInstance().isWindows()); writeTrashFile("a /a", ""); writeTrashFile("a /a ", ""); writeTrashFile("a /a ", ""); @@ -477,8 +713,9 @@ writeTrashFile("a/a", ""); writeTrashFile("a/a ", ""); writeTrashFile("a/a ", ""); + writeTrashFile("b/c", ""); - writeIgnoreFile(".gitignore", "a\\ ", "a \\ "); + writeIgnoreFile(".gitignore", "a\\ ", "a \\ ", "b/ "); beginWalk(); assertEntry(F, tracked, ".gitignore"); @@ -494,6 +731,8 @@ assertEntry(F, tracked, "a/a"); assertEntry(F, ignored, "a/a "); assertEntry(F, ignored, "a/a "); + assertEntry(D, ignored, "b"); + assertEntry(F, ignored, "b/c"); endWalk(); } @@ -505,7 +744,7 @@ .toString()); } - private void beginWalk() throws CorruptObjectException { + private void beginWalk() { walk = new TreeWalk(db); walk.addTree(new FileTreeIterator(db)); } @@ -535,11 +774,11 @@ writeTrashFile(name, data.toString()); } - private InputStream writeToString(String... rules) throws IOException { + private InputStream writeToString(String... rules) { StringBuilder data = new StringBuilder(); for (String line : rules) { data.append(line + "\n"); } - return new ByteArrayInputStream(data.toString().getBytes("UTF-8")); + return new ByteArrayInputStream(data.toString().getBytes(UTF_8)); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -768,7 +768,7 @@ @Test public void testSpecialGroupCase9() throws Exception { - assertMatch("][", "][", true); + assertMatch("][", "][", false); } @Test @@ -968,6 +968,59 @@ 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); + + assertMatch("?", "[", true); + assertMatch("*", "[", true); + + // Escaped bracket matches, but see weird things below... + assertMatch("\\[", "[", true); + } + + /** + * The ignore rules here do not match any paths because single '[' + * begins character group and the entire rule cannot be parsed due the + * invalid glob pattern. See + * http://article.gmane.org/gmane.comp.version-control.git/278699. + * + * @throws Exception + */ + @Test + public void testBracketsUnmatched1() throws Exception { + assertMatch("[", "[", false); + assertMatch("[*", "[", false); + assertMatch("*[", "[", false); + assertMatch("*[", "a[", false); + assertMatch("[a][", "a[", false); + assertMatch("*[", "a", false); + assertMatch("[a", "a", false); + assertMatch("[*", "a", false); + assertMatch("[*a", "a", false); + } + + /** + * Single ']' is treated here literally, not as an and of a character group + * + * @throws Exception + */ + @Test + public void testBracketsUnmatched2() throws Exception { + assertMatch("*]", "a", false); + assertMatch("]a", "a", false); + assertMatch("]*", "a", false); + assertMatch("]*a", "a", false); + + assertMatch("]", "]", true); + assertMatch("]*", "]", true); + assertMatch("]*", "]a", true); + assertMatch("*]", "]", true); + assertMatch("*]", "a]", true); + } + + @Test + public void testBracketsRandom() throws Exception { + assertMatch("[\\]", "[$0+//r4a\\d]", false); + assertMatch("[:]]sZX]", "[:]]sZX]", false); + assertMatch("[:]]:]]]", "[:]]:]]]", false); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.ignore.internal; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class StringsTest { + + private void testString(String string, int n, int m) { + assertEquals(string, n, Strings.count(string, '/', false)); + assertEquals(string, m, Strings.count(string, '/', true)); + } + + @Test + public void testCount() { + testString("", 0, 0); + testString("/", 1, 0); + testString("//", 2, 0); + testString("///", 3, 1); + testString("////", 4, 2); + testString("foo", 0, 0); + testString("/foo", 1, 0); + testString("foo/", 1, 0); + testString("/foo/", 2, 0); + testString("foo/bar", 1, 1); + testString("/foo/bar/", 3, 1); + testString("/foo/bar//", 4, 2); + testString("/foo//bar/", 4, 2); + testString(" /foo/ ", 2, 2); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2015 Thomas Wolf + * + * 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.indexdiff; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.IndexDiff; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.SystemReader; +import org.junit.Before; +import org.junit.Test; + +/** + * MacOS-only test for dealing with symlinks in IndexDiff. Recreates using cgit + * a test repository prepared with git 2.2.1 on MacOS 10.7.5 containing some + * symlinks. Examines a symlink pointing to a file named "äéü.txt" (should be + * encoded as UTF-8 NFC), changes it through Java, examines it again to verify + * it's been changed to UTF-8 NFD, and finally calculates an IndexDiff. + */ +public class IndexDiffWithSymlinkTest extends LocalDiskRepositoryTestCase { + + private static final String FILEREPO = "filerepo"; + + private static final String TESTFOLDER = "testfolder"; + + private static final String TESTTARGET = "äéü.txt"; + + private static final String TESTLINK = "aeu.txt"; + + private static final byte[] NFC = // "äéü.txt" in NFC + { -61, -92, -61, -87, -61, -68, 46, 116, 120, 116 }; + + private static final byte[] NFD = // "äéü.txt" in NFD + { 97, -52, -120, 101, -52, -127, 117, -52, -120, 46, 116, 120, 116 }; + + private File testRepoDir; + + @Override + @Before + public void setUp() throws Exception { + assumeTrue(SystemReader.getInstance().isMacOS() + && FS.DETECTED.supportsSymlinks()); + super.setUp(); + File testDir = createTempDirectory(this.getClass().getSimpleName()); + try (InputStream in = this.getClass().getClassLoader() + .getResourceAsStream( + this.getClass().getPackage().getName().replace('.', '/') + '/' + + FILEREPO + ".txt")) { + assertNotNull("Test repo file not found", in); + testRepoDir = restoreGitRepo(in, testDir, FILEREPO); + } + } + + private File restoreGitRepo(InputStream in, File testDir, String name) + throws Exception { + File exportedTestRepo = new File(testDir, name + ".txt"); + copy(in, exportedTestRepo); + // Use CGit to restore + File restoreScript = new File(testDir, name + ".sh"); + try (OutputStream out = new BufferedOutputStream( + new FileOutputStream(restoreScript)); + Writer writer = new OutputStreamWriter(out, UTF_8)) { + writer.write("echo `which git` 1>&2\n"); + writer.write("echo `git --version` 1>&2\n"); + writer.write("git init " + name + " && \\\n"); + writer.write("cd ./" + name + " && \\\n"); + writer.write("git fast-import < ../" + name + ".txt && \\\n"); + writer.write("git checkout -f\n"); + } + String[] cmd = { "/bin/sh", "./" + name + ".sh" }; + int exitCode; + String stdErr; + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.environment().put("HOME", + FS.DETECTED.userHome().getAbsolutePath()); + builder.directory(testDir); + Process process = builder.start(); + try (InputStream stdOutStream = process.getInputStream(); + InputStream stdErrStream = process.getErrorStream(); + OutputStream stdInStream = process.getOutputStream()) { + readStream(stdOutStream); + stdErr = readStream(stdErrStream); + process.waitFor(); + exitCode = process.exitValue(); + } + if (exitCode != 0) { + fail("cgit repo restore returned " + exitCode + '\n' + stdErr); + } + return new File(new File(testDir, name), Constants.DOT_GIT); + } + + private void copy(InputStream from, File to) throws IOException { + try (OutputStream out = new FileOutputStream(to)) { + byte[] buffer = new byte[4096]; + int n; + while ((n = from.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + } + } + + private String readStream(InputStream stream) throws IOException { + try (BufferedReader in = new BufferedReader( + new InputStreamReader(stream))) { + StringBuilder out = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + out.append(line).append('\n'); + } + return out.toString(); + } + } + + @Test + public void testSymlinkWithEncodingDifference() throws Exception { + try (Repository testRepo = FileRepositoryBuilder.create(testRepoDir)) { + File workingTree = testRepo.getWorkTree(); + File symLink = new File(new File(workingTree, TESTFOLDER), + TESTLINK); + // Read the symlink as it was created by cgit + Path linkTarget = Files.readSymbolicLink(symLink.toPath()); + assertEquals("Unexpected link target", TESTTARGET, + linkTarget.toString()); + byte[] raw = rawPath(linkTarget); + if (raw != null) { + assertArrayEquals("Expected an NFC link target", NFC, raw); + } + // Now re-create that symlink through Java + assertTrue("Could not delete symlink", symLink.delete()); + Files.createSymbolicLink(symLink.toPath(), Paths.get(TESTTARGET)); + // Read it again + linkTarget = Files.readSymbolicLink(symLink.toPath()); + assertEquals("Unexpected link target", TESTTARGET, + linkTarget.toString()); + raw = rawPath(linkTarget); + if (raw != null) { + assertArrayEquals("Expected an NFD link target", NFD, raw); + } + // Do the indexdiff + WorkingTreeIterator iterator = new FileTreeIterator(testRepo); + IndexDiff diff = new IndexDiff(testRepo, Constants.HEAD, iterator); + diff.setFilter(PathFilterGroup.createFromStrings( + Collections.singleton(TESTFOLDER + '/' + TESTLINK))); + diff.diff(); + // We're testing that this does NOT throw "EOFException: Short read + // of block." The diff will not report any modified files -- the + // link modification is not visible to JGit, which always works with + // the Java internal NFC encoding. CGit does report the link as an + // unstaged modification here, though. + } + } + + private byte[] rawPath(Path p) { + try { + Method method = p.getClass().getDeclaredMethod("asByteArray"); + if (method != null) { + method.setAccessible(true); + return (byte[]) method.invoke(p); + } + } catch (NoSuchMethodException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + // Ignore and fall through. + } + return null; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,13 +57,14 @@ public class DeltaBaseCacheTest { private static final int SZ = 512; - private DfsPackKey key; + private DfsStreamKey key; private DeltaBaseCache cache; private TestRng rng; @Before public void setUp() { - key = new DfsPackKey(); + DfsRepositoryDescription repo = new DfsRepositoryDescription("test"); + key = DfsStreamKey.of(repo, "test.key", null); cache = new DeltaBaseCache(SZ); rng = new TestRng(getClass().getSimpleName()); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016, Philipp Marx and + * other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.dfs; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.eclipse.jgit.internal.JGitText; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class DfsBlockCacheConfigTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void blockSizeNotPowerOfTwoExpectsException() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(is(JGitText.get().blockSizeNotPowerOf2)); + + new DfsBlockCacheConfig().setBlockSize(1000); + } + + @Test + @SuppressWarnings("boxing") + public void negativeBlockSizeIsConvertedToDefault() { + DfsBlockCacheConfig config = new DfsBlockCacheConfig(); + config.setBlockSize(-1); + + assertThat(config.getBlockSize(), is(512)); + } + + @Test + @SuppressWarnings("boxing") + public void tooSmallBlockSizeIsConvertedToDefault() { + DfsBlockCacheConfig config = new DfsBlockCacheConfig(); + config.setBlockSize(10); + + assertThat(config.getBlockSize(), is(512)); + } + + @Test + @SuppressWarnings("boxing") + public void validBlockSize() { + DfsBlockCacheConfig config = new DfsBlockCacheConfig(); + config.setBlockSize(65536); + + assertThat(config.getBlockSize(), is(65536)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.LongStream; + +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +public class DfsBlockCacheTest { + @Rule + public TestName testName = new TestName(); + private TestRng rng; + private DfsBlockCache cache; + + @Before + public void setUp() { + rng = new TestRng(testName.getMethodName()); + resetCache(); + } + + @SuppressWarnings("resource") + @Test + public void streamKeyReusesBlocks() throws Exception { + DfsRepositoryDescription repo = new DfsRepositoryDescription("test"); + InMemoryRepository r1 = new InMemoryRepository(repo); + byte[] content = rng.nextBytes(424242); + ObjectId id; + try (ObjectInserter ins = r1.newObjectInserter()) { + id = ins.insert(OBJ_BLOB, content); + ins.flush(); + } + + long oldSize = LongStream.of(cache.getCurrentSize()).sum(); + assertTrue(oldSize > 2000); + assertEquals(0, LongStream.of(cache.getHitCount()).sum()); + + List packs = r1.getObjectDatabase().listPacks(); + InMemoryRepository r2 = new InMemoryRepository(repo); + r2.getObjectDatabase().commitPack(packs, Collections.emptyList()); + try (ObjectReader rdr = r2.newObjectReader()) { + byte[] actual = rdr.open(id, OBJ_BLOB).getBytes(); + assertTrue(Arrays.equals(content, actual)); + } + assertEquals(0, LongStream.of(cache.getMissCount()).sum()); + assertEquals(oldSize, LongStream.of(cache.getCurrentSize()).sum()); + } + + @SuppressWarnings("resource") + @Test + public void weirdBlockSize() throws Exception { + DfsRepositoryDescription repo = new DfsRepositoryDescription("test"); + InMemoryRepository r1 = new InMemoryRepository(repo); + + byte[] content1 = rng.nextBytes(4); + byte[] content2 = rng.nextBytes(424242); + ObjectId id1; + ObjectId id2; + try (ObjectInserter ins = r1.newObjectInserter()) { + id1 = ins.insert(OBJ_BLOB, content1); + id2 = ins.insert(OBJ_BLOB, content2); + ins.flush(); + } + + resetCache(); + List packs = r1.getObjectDatabase().listPacks(); + + InMemoryRepository r2 = new InMemoryRepository(repo); + r2.getObjectDatabase().setReadableChannelBlockSizeForTest(500); + r2.getObjectDatabase().commitPack(packs, Collections.emptyList()); + try (ObjectReader rdr = r2.newObjectReader()) { + byte[] actual = rdr.open(id1, OBJ_BLOB).getBytes(); + assertTrue(Arrays.equals(content1, actual)); + } + + InMemoryRepository r3 = new InMemoryRepository(repo); + r3.getObjectDatabase().setReadableChannelBlockSizeForTest(500); + r3.getObjectDatabase().commitPack(packs, Collections.emptyList()); + try (ObjectReader rdr = r3.newObjectReader()) { + byte[] actual = rdr.open(id2, OBJ_BLOB).getBytes(); + assertTrue(Arrays.equals(content2, actual)); + } + } + + private void resetCache() { + DfsBlockCache.reconfigure(new DfsBlockCacheConfig() + .setBlockSize(512) + .setBlockLimit(1 << 20)); + cache = DfsBlockCache.getInstance(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.junit.JGitTestUtil.concat; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.encodeASCII; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.eclipse.jgit.internal.fsck.FsckError; +import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectChecker.ErrorType; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +public class DfsFsckTest { + private TestRepository git; + + private InMemoryRepository repo; + + private ObjectInserter ins; + + @Before + public void setUp() throws IOException { + DfsRepositoryDescription desc = new DfsRepositoryDescription("test"); + git = new TestRepository<>(new InMemoryRepository(desc)); + repo = git.getRepository(); + ins = repo.newObjectInserter(); + } + + @Test + public void testHealthyRepo() throws Exception { + RevCommit commit0 = git.commit().message("0").create(); + RevCommit commit1 = git.commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 0); + assertEquals(errors.getMissingObjects().size(), 0); + assertEquals(errors.getCorruptIndices().size(), 0); + } + + @Test + public void testCommitWithCorruptAuthor() throws Exception { + StringBuilder b = new StringBuilder(); + b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n"); + b.append("author b 0 +0000\n"); + b.append("committer <> 0 +0000\n"); + byte[] data = encodeASCII(b.toString()); + ObjectId id = ins.insert(Constants.OBJ_COMMIT, data); + ins.flush(); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 1); + CorruptObject o = errors.getCorruptObjects().iterator().next(); + assertTrue(o.getId().equals(id)); + assertEquals(o.getErrorType(), ErrorType.BAD_DATE); + } + + @Test + public void testCommitWithoutTree() throws Exception { + StringBuilder b = new StringBuilder(); + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + byte[] data = encodeASCII(b.toString()); + ObjectId id = ins.insert(Constants.OBJ_COMMIT, data); + ins.flush(); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 1); + CorruptObject o = errors.getCorruptObjects().iterator().next(); + assertTrue(o.getId().equals(id)); + assertEquals(o.getErrorType(), ErrorType.MISSING_TREE); + } + + @Test + public void testTagWithoutObject() throws Exception { + StringBuilder b = new StringBuilder(); + b.append("type commit\n"); + b.append("tag test-tag\n"); + b.append("tagger A. U. Thor 1 +0000\n"); + byte[] data = encodeASCII(b.toString()); + ObjectId id = ins.insert(Constants.OBJ_TAG, data); + ins.flush(); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 1); + CorruptObject o = errors.getCorruptObjects().iterator().next(); + assertTrue(o.getId().equals(id)); + assertEquals(o.getErrorType(), ErrorType.MISSING_OBJECT); + } + + @Test + public void testTreeWithNullSha() throws Exception { + byte[] data = concat(encodeASCII("100644 A"), new byte[] { '\0' }, + new byte[OBJECT_ID_LENGTH]); + ObjectId id = ins.insert(Constants.OBJ_TREE, data); + ins.flush(); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 1); + CorruptObject o = errors.getCorruptObjects().iterator().next(); + assertTrue(o.getId().equals(id)); + assertEquals(o.getErrorType(), ErrorType.NULL_SHA1); + } + + @Test + public void testMultipleInvalidObjects() throws Exception { + StringBuilder b = new StringBuilder(); + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + b.append("parent "); + b.append("\n"); + byte[] data = encodeASCII(b.toString()); + ObjectId id1 = ins.insert(Constants.OBJ_COMMIT, data); + + b = new StringBuilder(); + b.append("100644"); + data = encodeASCII(b.toString()); + ObjectId id2 = ins.insert(Constants.OBJ_TREE, data); + + ins.flush(); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + + assertEquals(errors.getCorruptObjects().size(), 2); + for (CorruptObject o : errors.getCorruptObjects()) { + if (o.getId().equals(id1)) { + assertEquals(o.getErrorType(), ErrorType.BAD_PARENT_SHA1); + } else if (o.getId().equals(id2)) { + assertNull(o.getErrorType()); + } else { + fail(); + } + } + } + + @Test + public void testValidConnectivity() throws Exception { + ObjectId blobId = ins + .insert(Constants.OBJ_BLOB, Constants.encode("foo")); + + byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH]; + blobId.copyRawTo(blobIdBytes, 0); + byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes); + ObjectId treeId = ins.insert(Constants.OBJ_TREE, data); + ins.flush(); + + RevCommit commit = git.commit().message("0").setTopLevelTree(treeId) + .create(); + + git.update("master", commit); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + assertEquals(errors.getMissingObjects().size(), 0); + } + + @Test + public void testMissingObject() throws Exception { + ObjectId blobId = ObjectId + .fromString("19102815663d23f8b75a47e7a01965dcdc96468c"); + byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH]; + blobId.copyRawTo(blobIdBytes, 0); + byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes); + ObjectId treeId = ins.insert(Constants.OBJ_TREE, data); + ins.flush(); + + RevCommit commit = git.commit().message("0").setTopLevelTree(treeId) + .create(); + + git.update("master", commit); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + assertEquals(errors.getMissingObjects().size(), 1); + assertEquals(errors.getMissingObjects().iterator().next(), blobId); + } + + @Test + public void testNonCommitHead() throws Exception { + RevCommit commit0 = git.commit().message("0").create(); + StringBuilder b = new StringBuilder(); + b.append("object "); + b.append(commit0.getName()); + b.append('\n'); + b.append("type commit\n"); + b.append("tag test-tag\n"); + b.append("tagger A. U. Thor 1 +0000\n"); + + byte[] data = encodeASCII(b.toString()); + ObjectId tagId = ins.insert(Constants.OBJ_TAG, data); + ins.flush(); + + git.update("master", tagId); + + DfsFsck fsck = new DfsFsck(repo); + FsckError errors = fsck.check(null); + assertEquals(errors.getCorruptObjects().size(), 0); + assertEquals(errors.getNonCommitHeads().size(), 1); + assertEquals(errors.getNonCommitHeads().iterator().next(), + "refs/heads/master"); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,1003 @@ +package org.eclipse.jgit.internal.storage.dfs; + +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE; +import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; +import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; +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; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; +import org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase; +import org.eclipse.jgit.internal.storage.reftable.RefCursor; +import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.internal.storage.reftable.ReftableReader; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter; +import org.eclipse.jgit.junit.MockSystemReader; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.SystemReader; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Tests for pack creation and garbage expiration. */ +public class DfsGarbageCollectorTest { + private TestRepository git; + private InMemoryRepository repo; + private DfsObjDatabase odb; + private MockSystemReader mockSystemReader; + + @Before + public void setUp() throws IOException { + DfsRepositoryDescription desc = new DfsRepositoryDescription("test"); + git = new TestRepository<>(new InMemoryRepository(desc)); + repo = git.getRepository(); + odb = repo.getObjectDatabase(); + mockSystemReader = new MockSystemReader(); + SystemReader.setInstance(mockSystemReader); + } + + @After + public void tearDown() { + SystemReader.setInstance(null); + } + + @Test + public void testCollectionWithNoGarbage() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + assertTrue("commit0 reachable", isReachable(repo, commit0)); + assertTrue("commit1 reachable", isReachable(repo, commit1)); + + // Packs start out as INSERT. + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + assertEquals(INSERT, pack.getPackDescription().getPackSource()); + } + + gcNoTtl(); + + // Single GC pack present with all objects. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(GC, pack.getPackDescription().getPackSource()); + assertTrue("commit0 in pack", isObjectInPack(commit0, pack)); + assertTrue("commit1 in pack", isObjectInPack(commit1, pack)); + } + + @Test + public void testRacyNoReusePrefersSmaller() throws Exception { + StringBuilder msg = new StringBuilder(); + for (int i = 0; i < 100; i++) { + msg.append(i).append(": i am a teapot\n"); + } + RevBlob a = git.blob(msg.toString()); + RevCommit c0 = git.commit() + .add("tea", a) + .message("0") + .create(); + + msg.append("short and stout\n"); + RevBlob b = git.blob(msg.toString()); + RevCommit c1 = git.commit().parent(c0).tick(1) + .add("tea", b) + .message("1") + .create(); + git.update("master", c1); + + PackConfig cfg = new PackConfig(); + cfg.setReuseObjects(false); + cfg.setReuseDeltas(false); + cfg.setDeltaCompress(false); + cfg.setThreads(1); + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL + gc.setPackConfig(cfg); + run(gc); + + assertEquals(1, odb.getPacks().length); + DfsPackDescription large = odb.getPacks()[0].getPackDescription(); + assertSame(PackSource.GC, large.getPackSource()); + + cfg.setDeltaCompress(true); + gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL + gc.setPackConfig(cfg); + run(gc); + + assertEquals(1, odb.getPacks().length); + DfsPackDescription small = odb.getPacks()[0].getPackDescription(); + assertSame(PackSource.GC, small.getPackSource()); + assertTrue( + "delta compression pack is smaller", + small.getFileSize(PACK) < large.getFileSize(PACK)); + assertTrue( + "large pack is older", + large.getLastModified() < small.getLastModified()); + + // Forcefully reinsert the older larger GC pack. + odb.commitPack(Collections.singleton(large), null); + odb.clearCache(); + assertEquals(2, odb.getPacks().length); + + gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL + run(gc); + + assertEquals(1, odb.getPacks().length); + DfsPackDescription rebuilt = odb.getPacks()[0].getPackDescription(); + assertEquals(small.getFileSize(PACK), rebuilt.getFileSize(PACK)); + } + + @Test + public void testCollectionWithGarbage() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + assertTrue("commit0 reachable", isReachable(repo, commit0)); + assertFalse("commit1 garbage", isReachable(repo, commit1)); + gcNoTtl(); + + assertEquals(2, odb.getPacks().length); + DfsPackFile gc = null; + DfsPackFile garbage = null; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gc = pack; + } else if (d.getPackSource() == UNREACHABLE_GARBAGE) { + garbage = pack; + } else { + fail("unexpected " + d.getPackSource()); + } + } + + assertNotNull("created GC pack", gc); + assertTrue(isObjectInPack(commit0, gc)); + + assertNotNull("created UNREACHABLE_GARBAGE pack", garbage); + assertTrue(isObjectInPack(commit1, garbage)); + } + + @Test + public void testCollectionWithGarbageAndGarbagePacksPurged() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + gcWithTtl(); + // The repository should have a GC pack with commit0 and an + // UNREACHABLE_GARBAGE pack with commit1. + assertEquals(2, odb.getPacks().length); + boolean gcPackFound = false; + boolean garbagePackFound = false; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gcPackFound = true; + assertTrue("has commit0", isObjectInPack(commit0, pack)); + assertFalse("no commit1", isObjectInPack(commit1, pack)); + } else if (d.getPackSource() == UNREACHABLE_GARBAGE) { + garbagePackFound = true; + assertFalse("no commit0", isObjectInPack(commit0, pack)); + assertTrue("has commit1", isObjectInPack(commit1, pack)); + } else { + fail("unexpected " + d.getPackSource()); + } + } + assertTrue("gc pack found", gcPackFound); + assertTrue("garbage pack found", garbagePackFound); + + gcWithTtl(); + // The gc operation should have removed UNREACHABLE_GARBAGE pack along with commit1. + DfsPackFile[] packs = odb.getPacks(); + assertEquals(1, packs.length); + + assertEquals(GC, packs[0].getPackDescription().getPackSource()); + assertTrue("has commit0", isObjectInPack(commit0, packs[0])); + assertFalse("no commit1", isObjectInPack(commit1, packs[0])); + } + + @Test + public void testCollectionWithGarbageAndRereferencingGarbage() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + gcWithTtl(); + // The repository should have a GC pack with commit0 and an + // UNREACHABLE_GARBAGE pack with commit1. + assertEquals(2, odb.getPacks().length); + boolean gcPackFound = false; + boolean garbagePackFound = false; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gcPackFound = true; + assertTrue("has commit0", isObjectInPack(commit0, pack)); + assertFalse("no commit1", isObjectInPack(commit1, pack)); + } else if (d.getPackSource() == UNREACHABLE_GARBAGE) { + garbagePackFound = true; + assertFalse("no commit0", isObjectInPack(commit0, pack)); + assertTrue("has commit1", isObjectInPack(commit1, pack)); + } else { + fail("unexpected " + d.getPackSource()); + } + } + assertTrue("gc pack found", gcPackFound); + assertTrue("garbage pack found", garbagePackFound); + + git.update("master", commit1); + + gcWithTtl(); + // The gc operation should have removed the UNREACHABLE_GARBAGE pack and + // moved commit1 into GC pack. + DfsPackFile[] packs = odb.getPacks(); + assertEquals(1, packs.length); + + assertEquals(GC, packs[0].getPackDescription().getPackSource()); + assertTrue("has commit0", isObjectInPack(commit0, packs[0])); + assertTrue("has commit1", isObjectInPack(commit1, packs[0])); + } + + @Test + public void testCollectionWithPureGarbageAndGarbagePacksPurged() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + + gcWithTtl(); + // The repository should have a single UNREACHABLE_GARBAGE pack with commit0 + // and commit1. + DfsPackFile[] packs = odb.getPacks(); + assertEquals(1, packs.length); + + assertEquals(UNREACHABLE_GARBAGE, packs[0].getPackDescription().getPackSource()); + assertTrue("has commit0", isObjectInPack(commit0, packs[0])); + assertTrue("has commit1", isObjectInPack(commit1, packs[0])); + + gcWithTtl(); + // The gc operation should have removed UNREACHABLE_GARBAGE pack along + // with commit0 and commit1. + assertEquals(0, odb.getPacks().length); + } + + @Test + public void testCollectionWithPureGarbageAndRereferencingGarbage() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + + gcWithTtl(); + // The repository should have a single UNREACHABLE_GARBAGE pack with commit0 + // and commit1. + DfsPackFile[] packs = odb.getPacks(); + assertEquals(1, packs.length); + + DfsPackDescription pack = packs[0].getPackDescription(); + assertEquals(UNREACHABLE_GARBAGE, pack.getPackSource()); + assertTrue("has commit0", isObjectInPack(commit0, packs[0])); + assertTrue("has commit1", isObjectInPack(commit1, packs[0])); + + git.update("master", commit0); + + gcWithTtl(); + // The gc operation should have moved commit0 into the GC pack and + // removed UNREACHABLE_GARBAGE along with commit1. + packs = odb.getPacks(); + assertEquals(1, packs.length); + + pack = packs[0].getPackDescription(); + assertEquals(GC, pack.getPackSource()); + assertTrue("has commit0", isObjectInPack(commit0, packs[0])); + assertFalse("no commit1", isObjectInPack(commit1, packs[0])); + } + + @Test + public void testCollectionWithGarbageCoalescence() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + for (int i = 0; i < 3; i++) { + commit1 = commit().message("g" + i).parent(commit1).create(); + + // Make sure we don't have more than 1 UNREACHABLE_GARBAGE pack + // because they're coalesced. + gcNoTtl(); + assertEquals(1, countPacks(UNREACHABLE_GARBAGE)); + } + } + + @Test + public void testCollectionWithGarbageNoCoalescence() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + for (int i = 0; i < 3; i++) { + commit1 = commit().message("g" + i).parent(commit1).create(); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setCoalesceGarbageLimit(0); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); + run(gc); + assertEquals(1 + i, countPacks(UNREACHABLE_GARBAGE)); + } + } + + @Test + public void testCollectionWithGarbageCoalescenceWithShortTtl() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + // Create commits at 1 minute intervals with 1 hour ttl. + for (int i = 0; i < 100; i++) { + mockSystemReader.tick(60); + commit1 = commit().message("g" + i).parent(commit1).create(); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(1, TimeUnit.HOURS); + run(gc); + + // Make sure we don't have more than 4 UNREACHABLE_GARBAGE packs + // because all the packs that are created in a 20 minutes interval + // should be coalesced and the packs older than 60 minutes should be + // removed due to ttl. + int count = countPacks(UNREACHABLE_GARBAGE); + assertTrue("Garbage pack count should not exceed 4, but found " + + count, count <= 4); + } + } + + @Test + public void testCollectionWithGarbageCoalescenceWithLongTtl() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + // Create commits at 1 hour intervals with 2 days ttl. + for (int i = 0; i < 100; i++) { + mockSystemReader.tick(3600); + commit1 = commit().message("g" + i).parent(commit1).create(); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(2, TimeUnit.DAYS); + run(gc); + + // Make sure we don't have more than 3 UNREACHABLE_GARBAGE packs + // because all the packs that are created in a single day should + // be coalesced and the packs older than 2 days should be + // removed due to ttl. + int count = countPacks(UNREACHABLE_GARBAGE); + assertTrue("Garbage pack count should not exceed 3, but found " + + count, count <= 3); + } + } + + @Test + public void testEstimateGcPackSizeInNewRepo() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + // Packs start out as INSERT. + long inputPacksSize = 32; + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + assertEquals(INSERT, pack.getPackDescription().getPackSource()); + inputPacksSize += pack.getPackDescription().getFileSize(PACK) - 32; + } + + gcNoTtl(); + + // INSERT packs are combined into a single GC pack. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(GC, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + @Test + public void testEstimateGcPackSizeWithAnExistingGcPack() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + gcNoTtl(); + + RevCommit commit2 = commit().message("2").parent(commit1).create(); + git.update("master", commit2); + + // There will be one INSERT pack and one GC pack. + assertEquals(2, odb.getPacks().length); + boolean gcPackFound = false; + boolean insertPackFound = false; + long inputPacksSize = 32; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gcPackFound = true; + } else if (d.getPackSource() == INSERT) { + insertPackFound = true; + } else { + fail("unexpected " + d.getPackSource()); + } + inputPacksSize += d.getFileSize(PACK) - 32; + } + assertTrue(gcPackFound); + assertTrue(insertPackFound); + + gcNoTtl(); + + // INSERT pack is combined into the GC pack. + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(GC, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + @Test + public void testEstimateGcRestPackSizeInNewRepo() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("refs/notes/note1", commit1); + + // Packs start out as INSERT. + long inputPacksSize = 32; + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + assertEquals(INSERT, pack.getPackDescription().getPackSource()); + inputPacksSize += pack.getPackDescription().getFileSize(PACK) - 32; + } + + gcNoTtl(); + + // INSERT packs are combined into a single GC_REST pack. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(GC_REST, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + @Test + public void testEstimateGcRestPackSizeWithAnExistingGcPack() + throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("refs/notes/note1", commit1); + + gcNoTtl(); + + RevCommit commit2 = commit().message("2").parent(commit1).create(); + git.update("refs/notes/note2", commit2); + + // There will be one INSERT pack and one GC_REST pack. + assertEquals(2, odb.getPacks().length); + boolean gcRestPackFound = false; + boolean insertPackFound = false; + long inputPacksSize = 32; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC_REST) { + gcRestPackFound = true; + } else if (d.getPackSource() == INSERT) { + insertPackFound = true; + } else { + fail("unexpected " + d.getPackSource()); + } + inputPacksSize += d.getFileSize(PACK) - 32; + } + assertTrue(gcRestPackFound); + assertTrue(insertPackFound); + + gcNoTtl(); + + // INSERT pack is combined into the GC_REST pack. + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(GC_REST, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + @Test + public void testEstimateGcPackSizesWithGcAndGcRestPacks() throws Exception { + RevCommit commit0 = commit().message("0").create(); + git.update("head", commit0); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("refs/notes/note1", commit1); + + gcNoTtl(); + + RevCommit commit2 = commit().message("2").parent(commit1).create(); + git.update("refs/notes/note2", commit2); + + // There will be one INSERT, one GC and one GC_REST packs. + assertEquals(3, odb.getPacks().length); + boolean gcPackFound = false; + boolean gcRestPackFound = false; + boolean insertPackFound = false; + long gcPackSize = 0; + long gcRestPackSize = 0; + long insertPackSize = 0; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gcPackFound = true; + gcPackSize = d.getFileSize(PACK); + } else if (d.getPackSource() == GC_REST) { + gcRestPackFound = true; + gcRestPackSize = d.getFileSize(PACK); + } else if (d.getPackSource() == INSERT) { + insertPackFound = true; + insertPackSize = d.getFileSize(PACK); + } else { + fail("unexpected " + d.getPackSource()); + } + } + assertTrue(gcPackFound); + assertTrue(gcRestPackFound); + assertTrue(insertPackFound); + + gcNoTtl(); + + // In this test INSERT pack would be combined into the GC_REST pack. + // But, as there is no good heuristic to know whether the new packs will + // be combined into a GC pack or GC_REST packs, the new pick size is + // considered while estimating both the GC and GC_REST packs. + assertEquals(2, odb.getPacks().length); + gcPackFound = false; + gcRestPackFound = false; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + gcPackFound = true; + assertEquals(gcPackSize + insertPackSize - 32, + pack.getPackDescription().getEstimatedPackSize()); + } else if (d.getPackSource() == GC_REST) { + gcRestPackFound = true; + assertEquals(gcRestPackSize + insertPackSize - 32, + pack.getPackDescription().getEstimatedPackSize()); + } else { + fail("unexpected " + d.getPackSource()); + } + } + assertTrue(gcPackFound); + assertTrue(gcRestPackFound); + } + + @Test + public void testEstimateUnreachableGarbagePackSize() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit0); + + assertTrue("commit0 reachable", isReachable(repo, commit0)); + assertFalse("commit1 garbage", isReachable(repo, commit1)); + + // Packs start out as INSERT. + long packSize0 = 0; + long packSize1 = 0; + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + assertEquals(INSERT, d.getPackSource()); + if (isObjectInPack(commit0, pack)) { + packSize0 = d.getFileSize(PACK); + } else if (isObjectInPack(commit1, pack)) { + packSize1 = d.getFileSize(PACK); + } else { + fail("expected object not found in the pack"); + } + } + + gcNoTtl(); + + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription d = pack.getPackDescription(); + if (d.getPackSource() == GC) { + // Even though just commit0 will end up in GC pack, because + // there is no good way to know that up front, both the pack + // sizes are considered while computing the estimated size of GC + // pack. + assertEquals(packSize0 + packSize1 - 32, + d.getEstimatedPackSize()); + } else if (d.getPackSource() == UNREACHABLE_GARBAGE) { + // commit1 is moved to UNREACHABLE_GARBAGE pack. + assertEquals(packSize1, d.getEstimatedPackSize()); + } else { + fail("unexpected " + d.getPackSource()); + } + } + } + + @Test + public void testSinglePackForAllRefs() throws Exception { + RevCommit commit0 = commit().message("0").create(); + git.update("head", commit0); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("refs/notes/note1", commit1); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); + gc.getPackConfig().setSinglePack(true); + run(gc); + assertEquals(1, odb.getPacks().length); + + gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); + gc.getPackConfig().setSinglePack(false); + run(gc); + assertEquals(2, odb.getPacks().length); + } + + @SuppressWarnings("boxing") + @Test + public void producesNewReftable() throws Exception { + String master = "refs/heads/master"; + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + + BatchRefUpdate bru = git.getRepository().getRefDatabase() + .newBatchUpdate(); + bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit1, master)); + for (int i = 1; i <= 5100; i++) { + bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit0, + String.format("refs/pulls/%04d", i))); + } + try (RevWalk rw = new RevWalk(git.getRepository())) { + bru.execute(rw, NullProgressMonitor.INSTANCE); + } + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + run(gc); + + // Single GC pack present with all objects. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + DfsPackDescription desc = pack.getPackDescription(); + assertEquals(GC, desc.getPackSource()); + assertTrue("commit0 in pack", isObjectInPack(commit0, pack)); + assertTrue("commit1 in pack", isObjectInPack(commit1, pack)); + + // Sibling REFTABLE is also present. + assertTrue(desc.hasFileExt(REFTABLE)); + ReftableWriter.Stats stats = desc.getReftableStats(); + assertNotNull(stats); + assertTrue(stats.totalBytes() > 0); + assertEquals(5101, stats.refCount()); + assertEquals(1, stats.minUpdateIndex()); + assertEquals(1, stats.maxUpdateIndex()); + + DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = table.open(ctx); + RefCursor rc = rr.seekRef("refs/pulls/5100")) { + assertTrue(rc.next()); + assertEquals(commit0, rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + } + + @Test + public void leavesNonGcReftablesIfNotConfigured() throws Exception { + String master = "refs/heads/master"; + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update(master, commit1); + + DfsPackDescription t1 = odb.newPack(INSERT); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + new ReftableWriter().begin(out).finish(); + t1.addFileExt(REFTABLE); + } + odb.commitPack(Collections.singleton(t1), null); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(null); + run(gc); + + // Single GC pack present with all objects. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + DfsPackDescription desc = pack.getPackDescription(); + assertEquals(GC, desc.getPackSource()); + assertTrue("commit0 in pack", isObjectInPack(commit0, pack)); + assertTrue("commit1 in pack", isObjectInPack(commit1, pack)); + + // A GC and the older INSERT REFTABLE above is present. + DfsReftable[] tables = odb.getReftables(); + assertEquals(2, tables.length); + assertEquals(t1, tables[0].getPackDescription()); + } + + @Test + public void prunesNonGcReftables() throws Exception { + String master = "refs/heads/master"; + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update(master, commit1); + + DfsPackDescription t1 = odb.newPack(INSERT); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + new ReftableWriter().begin(out).finish(); + t1.addFileExt(REFTABLE); + } + odb.commitPack(Collections.singleton(t1), null); + odb.clearCache(); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + run(gc); + + // Single GC pack present with all objects. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + DfsPackDescription desc = pack.getPackDescription(); + assertEquals(GC, desc.getPackSource()); + assertTrue("commit0 in pack", isObjectInPack(commit0, pack)); + assertTrue("commit1 in pack", isObjectInPack(commit1, pack)); + + // Only sibling GC REFTABLE is present. + DfsReftable[] tables = odb.getReftables(); + assertEquals(1, tables.length); + assertEquals(desc, tables[0].getPackDescription()); + assertTrue(desc.hasFileExt(REFTABLE)); + } + + @Test + public void compactsReftables() throws Exception { + String master = "refs/heads/master"; + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update(master, commit1); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + run(gc); + + DfsPackDescription t1 = odb.newPack(INSERT); + Ref next = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, + "refs/heads/next", commit0.copy()); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + ReftableWriter w = new ReftableWriter(); + w.setMinUpdateIndex(42); + w.setMaxUpdateIndex(42); + w.begin(out); + w.sortAndWriteRefs(Collections.singleton(next)); + w.finish(); + t1.addFileExt(REFTABLE); + t1.setReftableStats(w.getStats()); + } + odb.commitPack(Collections.singleton(t1), null); + + gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + run(gc); + + // Single GC pack present with all objects. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + DfsPackDescription desc = pack.getPackDescription(); + assertEquals(GC, desc.getPackSource()); + assertTrue("commit0 in pack", isObjectInPack(commit0, pack)); + assertTrue("commit1 in pack", isObjectInPack(commit1, pack)); + + // Only sibling GC REFTABLE is present. + DfsReftable[] tables = odb.getReftables(); + assertEquals(1, tables.length); + assertEquals(desc, tables[0].getPackDescription()); + assertTrue(desc.hasFileExt(REFTABLE)); + + // GC reftable contains the compaction. + DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = table.open(ctx); + RefCursor rc = rr.allRefs()) { + assertEquals(1, rr.minUpdateIndex()); + assertEquals(42, rr.maxUpdateIndex()); + + assertTrue(rc.next()); + assertEquals(master, rc.getRef().getName()); + assertEquals(commit1, rc.getRef().getObjectId()); + + assertTrue(rc.next()); + assertEquals(next.getName(), rc.getRef().getName()); + assertEquals(commit0, rc.getRef().getObjectId()); + + assertFalse(rc.next()); + } + } + + @Test + public void reftableWithoutTombstoneResurrected() throws Exception { + RevCommit commit0 = commit().message("0").create(); + String NEXT = "refs/heads/next"; + DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase(); + git.update(NEXT, commit0); + Ref next = refdb.exactRef(NEXT); + assertNotNull(next); + assertEquals(commit0, next.getObjectId()); + + git.delete(NEXT); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + gc.setIncludeDeletes(false); + gc.setConvertToReftable(false); + run(gc); + assertEquals(1, odb.getReftables().length); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = odb.getReftables()[0].open(ctx)) { + rr.setIncludeDeletes(true); + assertEquals(1, rr.minUpdateIndex()); + assertEquals(2, rr.maxUpdateIndex()); + assertNull(rr.exactRef(NEXT)); + } + + RevCommit commit1 = commit().message("1").create(); + DfsPackDescription t1 = odb.newPack(INSERT); + Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT, + commit1); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + ReftableWriter w = new ReftableWriter(); + w.setMinUpdateIndex(1); + w.setMaxUpdateIndex(1); + w.begin(out); + w.writeRef(newNext, 1); + w.finish(); + t1.addFileExt(REFTABLE); + t1.setReftableStats(w.getStats()); + } + odb.commitPack(Collections.singleton(t1), null); + assertEquals(2, odb.getReftables().length); + refdb.clearCache(); + newNext = refdb.exactRef(NEXT); + assertNotNull(newNext); + assertEquals(commit1, newNext.getObjectId()); + } + + @Test + public void reftableWithTombstoneNotResurrected() throws Exception { + RevCommit commit0 = commit().message("0").create(); + String NEXT = "refs/heads/next"; + DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase(); + git.update(NEXT, commit0); + Ref next = refdb.exactRef(NEXT); + assertNotNull(next); + assertEquals(commit0, next.getObjectId()); + + git.delete(NEXT); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + gc.setIncludeDeletes(true); + gc.setConvertToReftable(false); + run(gc); + assertEquals(1, odb.getReftables().length); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = odb.getReftables()[0].open(ctx)) { + rr.setIncludeDeletes(true); + assertEquals(1, rr.minUpdateIndex()); + assertEquals(2, rr.maxUpdateIndex()); + next = rr.exactRef(NEXT); + assertNotNull(next); + assertNull(next.getObjectId()); + } + + RevCommit commit1 = commit().message("1").create(); + DfsPackDescription t1 = odb.newPack(INSERT); + Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT, + commit1); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + ReftableWriter w = new ReftableWriter(); + w.setMinUpdateIndex(1); + w.setMaxUpdateIndex(1); + w.begin(out); + w.writeRef(newNext, 1); + w.finish(); + t1.addFileExt(REFTABLE); + t1.setReftableStats(w.getStats()); + } + odb.commitPack(Collections.singleton(t1), null); + assertEquals(2, odb.getReftables().length); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + } + + private TestRepository.CommitBuilder commit() { + return git.commit(); + } + + private void gcNoTtl() throws IOException { + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL + run(gc); + } + + private void gcWithTtl() throws IOException { + // Move the clock forward by 1 minute and use the same as ttl. + mockSystemReader.tick(60); + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setGarbageTtl(1, TimeUnit.MINUTES); + run(gc); + } + + private void run(DfsGarbageCollector gc) throws IOException { + // adjust the current time that will be used by the gc operation. + mockSystemReader.tick(1); + assertTrue("gc repacked", gc.pack(null)); + odb.clearCache(); + } + + private static boolean isReachable(Repository repo, AnyObjectId id) + throws IOException { + try (RevWalk rw = new RevWalk(repo)) { + for (Ref ref : repo.getAllRefs().values()) { + rw.markStart(rw.parseCommit(ref.getObjectId())); + } + for (RevCommit next; (next = rw.next()) != null;) { + if (AnyObjectId.equals(next, id)) { + return true; + } + } + } + return false; + } + + private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack) + throws IOException { + try (DfsReader reader = odb.newReader()) { + return pack.hasObject(reader, id); + } + } + + private int countPacks(PackSource source) throws IOException { + int cnt = 0; + for (DfsPackFile pack : odb.getPacks()) { + if (pack.getPackDescription().getPackSource() == source) { + cnt++; + } + } + return cnt; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,15 +45,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.zip.Deflater; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.TestRng; @@ -94,6 +98,7 @@ assertEquals(0, db.getObjectDatabase().listPacks().size()); ObjectReader reader = ins.newReader(); + assertSame(ins, reader.getCreatedFromInserter()); assertEquals("foo", readString(reader.open(id1))); assertEquals("bar", readString(reader.open(id2))); assertEquals(0, db.getObjectDatabase().listPacks().size()); @@ -115,6 +120,7 @@ assertEquals(0, db.getObjectDatabase().listPacks().size()); ObjectReader reader = ins.newReader(); + assertSame(ins, reader.getCreatedFromInserter()); assertTrue(Arrays.equals(data, readStream(reader.open(id1)))); assertEquals(0, db.getObjectDatabase().listPacks().size()); ins.flush(); @@ -133,6 +139,7 @@ assertEquals(1, db.getObjectDatabase().listPacks().size()); ObjectReader reader = ins.newReader(); + assertSame(ins, reader.getCreatedFromInserter()); assertEquals("foo", readString(reader.open(id1))); assertEquals("bar", readString(reader.open(id2))); assertEquals(1, db.getObjectDatabase().listPacks().size()); @@ -151,6 +158,7 @@ assertFalse(abbr1.equals(abbr2)); ObjectReader reader = ins.newReader(); + assertSame(ins, reader.getCreatedFromInserter()); Collection objs; objs = reader.resolve(AbbreviatedObjectId.fromString(abbr1)); assertEquals(1, objs.size()); @@ -161,6 +169,87 @@ assertEquals(id2, objs.iterator().next()); } + @Test + public void testGarbageSelectivelyVisible() throws IOException { + ObjectInserter ins = db.newObjectInserter(); + ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertEquals(1, db.getObjectDatabase().listPacks().size()); + + // Make pack 0 garbage. + db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE); + + // Default behavior should be that the database has foo, because we allow garbage objects. + assertTrue(db.getObjectDatabase().has(fooId)); + // But we should not be able to see it if we pass the right args. + assertFalse(db.getObjectDatabase().has(fooId, true)); + } + + @Test + public void testInserterIgnoresUnreachable() throws IOException { + ObjectInserter ins = db.newObjectInserter(); + ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertEquals(1, db.getObjectDatabase().listPacks().size()); + + // Make pack 0 garbage. + db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE); + + // We shouldn't be able to see foo because it's garbage. + assertFalse(db.getObjectDatabase().has(fooId, true)); + + // But if we re-insert foo, it should become visible again. + ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertTrue(db.getObjectDatabase().has(fooId, true)); + + // Verify that we have a foo in both packs, and 1 of them is garbage. + DfsReader reader = new DfsReader(db.getObjectDatabase()); + DfsPackFile packs[] = db.getObjectDatabase().getPacks(); + Set pack_sources = new HashSet<>(); + + assertEquals(2, packs.length); + + pack_sources.add(packs[0].getPackDescription().getPackSource()); + pack_sources.add(packs[1].getPackDescription().getPackSource()); + + assertTrue(packs[0].hasObject(reader, fooId)); + assertTrue(packs[1].hasObject(reader, fooId)); + assertTrue(pack_sources.contains(PackSource.UNREACHABLE_GARBAGE)); + assertTrue(pack_sources.contains(PackSource.INSERT)); + } + + @Test + public void testNoCheckExisting() throws IOException { + byte[] contents = Constants.encode("foo"); + ObjectId fooId; + try (ObjectInserter ins = db.newObjectInserter()) { + fooId = ins.insert(Constants.OBJ_BLOB, contents); + ins.flush(); + } + assertEquals(1, db.getObjectDatabase().listPacks().size()); + + try (ObjectInserter ins = db.newObjectInserter()) { + ((DfsInserter) ins).checkExisting(false); + assertEquals(fooId, ins.insert(Constants.OBJ_BLOB, contents)); + ins.flush(); + } + assertEquals(2, db.getObjectDatabase().listPacks().size()); + + // Verify that we have a foo in both INSERT packs. + DfsReader reader = new DfsReader(db.getObjectDatabase()); + DfsPackFile packs[] = db.getObjectDatabase().getPacks(); + + assertEquals(2, packs.length); + DfsPackFile p1 = packs[0]; + assertEquals(PackSource.INSERT, p1.getPackDescription().getPackSource()); + assertTrue(p1.hasObject(reader, fooId)); + + DfsPackFile p2 = packs[1]; + assertEquals(PackSource.INSERT, p2.getPackDescription().getPackSource()); + assertTrue(p2.hasObject(reader, fooId)); + } + private static String readString(ObjectLoader loader) throws IOException { return RawParseUtils.decode(readStream(loader)); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT; +import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +public class DfsPackCompacterTest { + private TestRepository git; + private InMemoryRepository repo; + private DfsObjDatabase odb; + + @Before + public void setUp() throws IOException { + DfsRepositoryDescription desc = new DfsRepositoryDescription("test"); + git = new TestRepository<>(new InMemoryRepository(desc)); + repo = git.getRepository(); + odb = repo.getObjectDatabase(); + } + + @Test + public void testEstimateCompactPackSizeInNewRepo() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + // Packs start out as INSERT. + long inputPacksSize = 32; + assertEquals(2, odb.getPacks().length); + for (DfsPackFile pack : odb.getPacks()) { + assertEquals(INSERT, pack.getPackDescription().getPackSource()); + inputPacksSize += pack.getPackDescription().getFileSize(PACK) - 32; + } + + compact(); + + // INSERT packs are compacted into a single COMPACT pack. + assertEquals(1, odb.getPacks().length); + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(COMPACT, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + @Test + public void testEstimateGcPackSizeWithAnExistingGcPack() throws Exception { + RevCommit commit0 = commit().message("0").create(); + RevCommit commit1 = commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + compact(); + + RevCommit commit2 = commit().message("2").parent(commit1).create(); + git.update("master", commit2); + + // There will be one INSERT pack and one COMPACT pack. + assertEquals(2, odb.getPacks().length); + boolean compactPackFound = false; + boolean insertPackFound = false; + long inputPacksSize = 32; + for (DfsPackFile pack : odb.getPacks()) { + DfsPackDescription packDescription = pack.getPackDescription(); + if (packDescription.getPackSource() == COMPACT) { + compactPackFound = true; + } + if (packDescription.getPackSource() == INSERT) { + insertPackFound = true; + } + inputPacksSize += packDescription.getFileSize(PACK) - 32; + } + assertTrue(compactPackFound); + assertTrue(insertPackFound); + + compact(); + + // INSERT pack is combined into the COMPACT pack. + DfsPackFile pack = odb.getPacks()[0]; + assertEquals(COMPACT, pack.getPackDescription().getPackSource()); + assertEquals(inputPacksSize, + pack.getPackDescription().getEstimatedPackSize()); + } + + private TestRepository.CommitBuilder commit() { + return git.commit(); + } + + private void compact() throws IOException { + DfsPackCompactor compactor = new DfsPackCompactor(repo); + compactor.autoAdd(); + compactor.compact(null); + odb.clearCache(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,6 +50,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -60,9 +61,6 @@ import java.util.List; import org.eclipse.jgit.errors.AmbiguousObjectException; -import org.eclipse.jgit.internal.storage.file.FileRepository; -import org.eclipse.jgit.internal.storage.file.PackIndexWriter; -import org.eclipse.jgit.internal.storage.file.PackIndexWriterV2; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AbbreviatedObjectId; @@ -72,7 +70,6 @@ import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.transport.PackedObjectInfo; import org.eclipse.jgit.util.FileUtils; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -84,14 +81,16 @@ private TestRepository test; + @Override @Before public void setUp() throws Exception { super.setUp(); db = createBareRepository(); reader = db.newObjectReader(); - test = new TestRepository(db); + test = new TestRepository<>(db); } + @Override @After public void tearDown() throws Exception { if (reader != null) { @@ -172,26 +171,26 @@ ObjectId id = id("9d5b926ed164e8ee88d3b8b1e525d699adda01ba"); byte[] idBuf = toByteArray(id); - List objects = new ArrayList(); + List objects = new ArrayList<>(); for (int i = 0; i < 256; i++) { idBuf[9] = (byte) i; objects.add(new PackedObjectInfo(ObjectId.fromRaw(idBuf))); } String packName = "pack-" + id.name(); - File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); + File packDir = db.getObjectDatabase().getPackDirectory(); File idxFile = new File(packDir, packName + ".idx"); File packFile = new File(packDir, packName + ".pack"); FileUtils.mkdir(packDir, true); - OutputStream dst = new SafeBufferedOutputStream(new FileOutputStream( - idxFile)); - try { + try (OutputStream dst = new BufferedOutputStream( + new FileOutputStream(idxFile))) { PackIndexWriter writer = new PackIndexWriterV2(dst); writer.write(objects, new byte[OBJECT_ID_LENGTH]); - } finally { - dst.close(); } - new FileOutputStream(packFile).close(); + + try (FileOutputStream unused = new FileOutputStream(packFile)) { + // unused + } assertEquals(id.abbreviate(20), reader.abbreviate(id, 2)); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017, Matthias Sohn + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.api.errors.NoMessageException; +import org.eclipse.jgit.api.errors.UnmergedPathsException; +import org.eclipse.jgit.api.errors.WrongRepositoryStateException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.junit.Test; + +public class AlternatesTest extends SampleDataRepositoryTestCase { + + private FileRepository db2; + + @Override + public void setUp() throws Exception { + super.setUp(); + db2 = createWorkRepository(); + } + + private void setAlternate(FileRepository from, FileRepository to) + throws IOException { + File alt = new File(from.getObjectDatabase().getDirectory(), + "info/alternates"); + alt.getParentFile().mkdirs(); + File fromDir = from.getObjectDatabase().getDirectory(); + File toDir = to.getObjectDatabase().getDirectory(); + Path relative = fromDir.toPath().relativize(toDir.toPath()); + write(alt, relative.toString() + "\n"); + } + + @Test + public void testAlternate() throws Exception { + setAlternate(db2, db); + RevCommit c = createCommit(); + assertCommit(c); + assertAlternateObjects(db2); + } + + @Test + public void testAlternateCyclic2() throws Exception { + setAlternate(db2, db); + setAlternate(db, db2); + RevCommit c = createCommit(); + assertCommit(c); + assertAlternateObjects(db2); + } + + @Test + public void testAlternateCyclic3() throws Exception { + FileRepository db3 = createBareRepository(); + setAlternate(db2, db3); + setAlternate(db3, db); + setAlternate(db, db2); + RevCommit c = createCommit(); + assertCommit(c); + assertAlternateObjects(db2); + } + + private RevCommit createCommit() throws IOException, GitAPIException, + NoFilepatternException, NoHeadException, NoMessageException, + UnmergedPathsException, ConcurrentRefUpdateException, + WrongRepositoryStateException, AbortedByHookException { + JGitTestUtil.writeTrashFile(db, "test", "test"); + Git git = Git.wrap(db2); + git.add().addFilepattern("test").call(); + RevCommit c = git.commit().setMessage("adding test").call(); + return c; + } + + private void assertCommit(RevCommit c) { + ObjectDirectory od = db2.getObjectDatabase(); + assertTrue("can't find expected commit" + c.name(), + od.has(c.toObjectId())); + } + + private void assertAlternateObjects(FileRepository repo) { + // check some objects in alternate + final ObjectId alternateObjects[] = new ObjectId[] { + ObjectId.fromString("49322bb17d3acc9146f98c97d078513228bbf3c0"), + ObjectId.fromString("d0114ab8ac326bab30e3a657a0397578c5a1af88"), + ObjectId.fromString("f73b95671f326616d66b2afb3bdfcdbbce110b44"), + ObjectId.fromString("6020a3b8d5d636e549ccbd0c53e2764684bb3125"), + ObjectId.fromString("0a3d7772488b6b106fb62813c4d6d627918d9181"), + ObjectId.fromString("da0f8ed91a8f2f0f067b3bdf26265d5ca48cf82c"), + ObjectId.fromString( + "cd4bcfc27da62c6b840de700be1c60a7e69952a5") }; + ObjectDirectory od = repo.getObjectDatabase(); + for (ObjectId o : alternateObjects) { + assertTrue(String.format("can't find object %s in alternate", + o.getName()), od.has(o)); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AutoGcTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AutoGcTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AutoGcTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AutoGcTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016, Matthias Sohn + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.junit.Test; + +public class AutoGcTest extends GcTestCase { + + @Test + public void testNotTooManyLooseObjects() { + assertFalse("should not find too many loose objects", + gc.tooManyLooseObjects()); + } + + @Test + public void testTooManyLooseObjects() throws Exception { + FileBasedConfig c = repo.getConfig(); + c.setInt(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTO, 255); + c.save(); + commitChain(10, 50); + assertTrue("should find too many loose objects", + gc.tooManyLooseObjects()); + } + + @Test + public void testNotTooManyPacks() { + assertFalse("should not find too many packs", gc.tooManyPacks()); + } + + @Test + public void testTooManyPacks() throws Exception { + FileBasedConfig c = repo.getConfig(); + c.setInt(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1); + c.save(); + SampleDataRepositoryTestCase.copyCGitTestPacks(repo); + + assertTrue("should find too many packs", gc.tooManyPacks()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,1083 @@ +/* + * Copyright (C) 2017 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.internal.storage.file; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE; +import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK; +import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT; +import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED; +import static org.eclipse.jgit.lib.ObjectId.zeroId; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; +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.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +import org.eclipse.jgit.events.ListenerHandle; +import org.eclipse.jgit.events.RefsChangedListener; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.StrictWorkMonitor; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CheckoutEntry; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.ReflogReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.After; +import org.junit.Before; +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; + +@SuppressWarnings("boxing") +@RunWith(Parameterized.class) +public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase { + @Parameter + public boolean atomic; + + @Parameters(name = "atomic={0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} }); + } + + private Repository diskRepo; + private TestRepository repo; + private RefDirectory refdir; + private RevCommit A; + private RevCommit B; + + /** + * When asserting the number of RefsChangedEvents you must account for one + * additional event due to the initial ref setup via a number of calls to + * {@link #writeLooseRef(String, AnyObjectId)} (will be fired in execute() + * when it is detected that the on-disk loose refs have changed), or for one + * additional event per {@link #writeRef(String, AnyObjectId)}. + */ + private int refsChangedEvents; + + private ListenerHandle handle; + + private RefsChangedListener refsChangedListener = event -> { + refsChangedEvents++; + }; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + diskRepo = createBareRepository(); + setLogAllRefUpdates(true); + + refdir = (RefDirectory) diskRepo.getRefDatabase(); + refdir.setRetrySleepMs(Arrays.asList(0, 0)); + + repo = new TestRepository<>(diskRepo); + A = repo.commit().create(); + B = repo.commit(repo.getRevWalk().parseCommit(A)); + refsChangedEvents = 0; + handle = diskRepo.getListenerList() + .addRefsChangedListener(refsChangedListener); + } + + @After + public void removeListener() { + handle.remove(); + refsChangedEvents = 0; + } + + @Test + public void packedRefsFileIsSorted() throws IOException { + assumeTrue(atomic); + + for (int i = 0; i < 2; i++) { + BatchRefUpdate bu = diskRepo.getRefDatabase().newBatchUpdate(); + String b1 = String.format("refs/heads/a%d",i); + String b2 = String.format("refs/heads/b%d",i); + bu.setAtomic(atomic); + ReceiveCommand c1 = new ReceiveCommand(ObjectId.zeroId(), A, b1); + ReceiveCommand c2 = new ReceiveCommand(ObjectId.zeroId(), B, b2); + bu.addCommand(c1, c2); + try (RevWalk rw = new RevWalk(diskRepo)) { + bu.execute(rw, NullProgressMonitor.INSTANCE); + } + assertEquals(c1.getResult(), ReceiveCommand.Result.OK); + assertEquals(c2.getResult(), ReceiveCommand.Result.OK); + } + + File packed = new File(diskRepo.getDirectory(), "packed-refs"); + String packedStr = new String(Files.readAllBytes(packed.toPath()), UTF_8); + + int a2 = packedStr.indexOf("refs/heads/a1"); + int b1 = packedStr.indexOf("refs/heads/b0"); + assertTrue(a2 < b1); + } + + @Test + public void simpleNoForce() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD)); + execute(newBatchUpdate(cmds)); + + if (atomic) { + assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD); + assertRefs( + "refs/heads/master", A, + "refs/heads/masters", B); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, OK, REJECTED_NONFASTFORWARD); + assertRefs( + "refs/heads/master", B, + "refs/heads/masters", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void simpleForce() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertResults(cmds, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/masters", A); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + } + + @Test + public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException { + writeLooseRef("refs/heads/master", B); + + List cmds = Arrays.asList( + new ReceiveCommand(B, A, "refs/heads/master", UPDATE_NONFASTFORWARD)); + try (RevWalk rw = new RevWalk(diskRepo) { + @Override + public boolean isMergedInto(RevCommit base, RevCommit tip) { + throw new AssertionError("isMergedInto() should not be called"); + } + }) { + newBatchUpdate(cmds) + .setAllowNonFastForwards(true) + .execute(rw, new StrictWorkMonitor()); + } + + assertResults(cmds, OK); + assertRefs("refs/heads/master", A); + assertEquals(2, refsChangedEvents); + } + + @Test + public void fileDirectoryConflict() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE), + new ReceiveCommand(zeroId(), A, "refs/heads", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + if (atomic) { + // Atomic update sees that master and master/x are conflicting, then marks + // the first one in the list as LOCK_FAILURE and aborts the rest. + assertResults(cmds, + LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED); + assertRefs( + "refs/heads/master", A, + "refs/heads/masters", B); + assertEquals(1, refsChangedEvents); + } else { + // Non-atomic updates are applied in order: master succeeds, then master/x + // fails due to conflict. + assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE); + assertRefs( + "refs/heads/master", B, + "refs/heads/masters", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void conflictThanksToDelete() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE), + new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertResults(cmds, OK, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/masters/x", A); + if (atomic) { + assertEquals(2, refsChangedEvents); + } else { + // The non-atomic case actually produces 5 events, but that's an + // implementation detail. We expect at least 4 events, one for the + // initial read due to writeLooseRef(), and then one for each + // successful ref update. + assertTrue(refsChangedEvents >= 4); + } + } + + @Test + public void updateToMissingObject() throws IOException { + writeLooseRef("refs/heads/master", A); + + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List cmds = Arrays.asList( + new ReceiveCommand(A, bad, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + if (atomic) { + assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, REJECTED_MISSING_OBJECT, OK); + assertRefs( + "refs/heads/master", A, + "refs/heads/foo2", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void addMissingObject() throws IOException { + writeLooseRef("refs/heads/master", A); + + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + if (atomic) { + assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, OK, REJECTED_MISSING_OBJECT); + assertRefs("refs/heads/master", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void oneNonExistentRef() throws IOException { + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + if (atomic) { + assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED); + assertRefs(); + assertEquals(0, refsChangedEvents); + } else { + assertResults(cmds, LOCK_FAILURE, OK); + assertRefs("refs/heads/foo2", B); + assertEquals(1, refsChangedEvents); + } + } + + @Test + public void oneRefWrongOldValue() throws IOException { + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(B, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + if (atomic) { + assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, LOCK_FAILURE, OK); + assertRefs( + "refs/heads/master", A, + "refs/heads/foo2", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void nonExistentRef() throws IOException { + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + if (atomic) { + assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, OK, LOCK_FAILURE); + assertRefs("refs/heads/master", B); + assertEquals(2, refsChangedEvents); + } + } + + @Test + public void noRefLog() throws IOException { + writeRef("refs/heads/master", A); + + Map oldLogs = + getLastReflogs("refs/heads/master", "refs/heads/branch"); + assertEquals(Collections.singleton("refs/heads/master"), oldLogs.keySet()); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertResults(cmds, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch", B); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertReflogUnchanged(oldLogs, "refs/heads/master"); + assertReflogUnchanged(oldLogs, "refs/heads/branch"); + } + + @Test + public void reflogDefaultIdent() throws IOException { + writeRef("refs/heads/master", A); + writeRef("refs/heads/branch2", A); + + Map oldLogs = getLastReflogs( + "refs/heads/master", "refs/heads/branch1", "refs/heads/branch2"); + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE)); + execute( + newBatchUpdate(cmds) + .setAllowNonFastForwards(true) + .setRefLogMessage("a reflog", false)); + + assertResults(cmds, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch1", B, + "refs/heads/branch2", A); + assertEquals(atomic ? 3 : 4, refsChangedEvents); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/branch1")); + assertReflogUnchanged(oldLogs, "refs/heads/branch2"); + } + + @Test + public void reflogAppendStatusNoMessage() throws IOException { + writeRef("refs/heads/master", A); + writeRef("refs/heads/branch1", B); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(B, A, "refs/heads/branch1", UPDATE_NONFASTFORWARD), + new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE)); + execute( + newBatchUpdate(cmds) + .setAllowNonFastForwards(true) + .setRefLogMessage(null, true)); + + assertResults(cmds, OK, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch1", A, + "refs/heads/branch2", A); + assertEquals(atomic ? 3 : 5, refsChangedEvents); + assertReflogEquals( + // Always forced; setAllowNonFastForwards(true) bypasses the check. + reflog(A, B, new PersonIdent(diskRepo), "forced-update"), + getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(B, A, new PersonIdent(diskRepo), "forced-update"), + getLastReflog("refs/heads/branch1")); + assertReflogEquals( + reflog(zeroId(), A, new PersonIdent(diskRepo), "created"), + getLastReflog("refs/heads/branch2")); + } + + @Test + public void reflogAppendStatusFastForward() throws IOException { + writeRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); + execute(newBatchUpdate(cmds).setRefLogMessage(null, true)); + + assertResults(cmds, OK); + assertRefs("refs/heads/master", B); + assertEquals(2, refsChangedEvents); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "fast-forward"), + getLastReflog("refs/heads/master")); + } + + @Test + public void reflogAppendStatusWithMessage() throws IOException { + writeRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE)); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true)); + + assertResults(cmds, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch", A); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"), + getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog: created"), + getLastReflog("refs/heads/branch")); + } + + @Test + public void reflogCustomIdent() throws IOException { + writeRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + PersonIdent ident = new PersonIdent("A Reflog User", "reflog@example.com"); + execute( + newBatchUpdate(cmds) + .setRefLogMessage("a reflog", false) + .setRefLogIdent(ident)); + + assertResults(cmds, OK, OK); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch", B); + assertReflogEquals( + reflog(A, B, ident, "a reflog"), + getLastReflog("refs/heads/master"), + true); + assertReflogEquals( + reflog(zeroId(), B, ident, "a reflog"), + getLastReflog("refs/heads/branch"), + true); + } + + @Test + public void reflogDelete() throws IOException { + writeRef("refs/heads/master", A); + writeRef("refs/heads/branch", A); + assertEquals( + 2, getLastReflogs("refs/heads/master", "refs/heads/branch").size()); + + List cmds = Arrays.asList( + new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE), + new ReceiveCommand(A, B, "refs/heads/branch", UPDATE)); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)); + + assertResults(cmds, OK, OK); + assertRefs("refs/heads/branch", B); + assertEquals(atomic ? 3 : 4, refsChangedEvents); + assertNull(getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/branch")); + } + + @Test + public void reflogFileDirectoryConflict() throws IOException { + writeRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE), + new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE)); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)); + + assertResults(cmds, OK, OK); + assertRefs("refs/heads/master/x", A); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertNull(getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/master/x")); + } + + @Test + public void reflogOnLockFailure() throws IOException { + writeRef("refs/heads/master", A); + + Map oldLogs = + getLastReflogs("refs/heads/master", "refs/heads/branch"); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(A, B, "refs/heads/branch", UPDATE)); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)); + + if (atomic) { + assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); + assertEquals(1, refsChangedEvents); + assertReflogUnchanged(oldLogs, "refs/heads/master"); + assertReflogUnchanged(oldLogs, "refs/heads/branch"); + } else { + assertResults(cmds, OK, LOCK_FAILURE); + assertEquals(2, refsChangedEvents); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/master")); + assertReflogUnchanged(oldLogs, "refs/heads/branch"); + } + } + + @Test + public void overrideRefLogMessage() throws Exception { + writeRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + cmds.get(0).setRefLogMessage("custom log", false); + PersonIdent ident = new PersonIdent(diskRepo); + execute( + newBatchUpdate(cmds) + .setRefLogIdent(ident) + .setRefLogMessage("a reflog", true)); + + assertResults(cmds, OK, OK); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertReflogEquals( + reflog(A, B, ident, "custom log"), + getLastReflog("refs/heads/master"), + true); + assertReflogEquals( + reflog(zeroId(), B, ident, "a reflog: created"), + getLastReflog("refs/heads/branch"), + true); + } + + @Test + public void overrideDisableRefLog() throws Exception { + writeRef("refs/heads/master", A); + + Map oldLogs = + getLastReflogs("refs/heads/master", "refs/heads/branch"); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + cmds.get(0).disableRefLog(); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true)); + + assertResults(cmds, OK, OK); + assertEquals(atomic ? 2 : 3, refsChangedEvents); + assertReflogUnchanged(oldLogs, "refs/heads/master"); + assertReflogEquals( + reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog: created"), + getLastReflog("refs/heads/branch")); + } + + @Test + public void refLogNotWrittenWithoutConfigOption() throws Exception { + setLogAllRefUpdates(false); + writeRef("refs/heads/master", A); + + Map oldLogs = + getLastReflogs("refs/heads/master", "refs/heads/branch"); + assertTrue(oldLogs.isEmpty()); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)); + + assertResults(cmds, OK, OK); + assertReflogUnchanged(oldLogs, "refs/heads/master"); + assertReflogUnchanged(oldLogs, "refs/heads/branch"); + } + + @Test + public void forceRefLogInUpdate() throws Exception { + setLogAllRefUpdates(false); + writeRef("refs/heads/master", A); + assertTrue( + getLastReflogs("refs/heads/master", "refs/heads/branch").isEmpty()); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + execute( + newBatchUpdate(cmds) + .setRefLogMessage("a reflog", false) + .setForceRefLog(true)); + + assertResults(cmds, OK, OK); + assertReflogEquals( + reflog(A, B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/master")); + assertReflogEquals( + reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/branch")); + } + + @Test + public void forceRefLogInCommand() throws Exception { + setLogAllRefUpdates(false); + writeRef("refs/heads/master", A); + + Map oldLogs = + getLastReflogs("refs/heads/master", "refs/heads/branch"); + assertTrue(oldLogs.isEmpty()); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + cmds.get(1).setForceRefLog(true); + execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false)); + + assertResults(cmds, OK, OK); + assertReflogUnchanged(oldLogs, "refs/heads/master"); + assertReflogEquals( + reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"), + getLastReflog("refs/heads/branch")); + } + + @Test + public void packedRefsLockFailure() throws Exception { + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + + LockFile myLock = refdir.lockPackedRefs(); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertFalse(getLockFile("refs/heads/master").exists()); + assertFalse(getLockFile("refs/heads/branch").exists()); + + if (atomic) { + assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + // Only operates on loose refs, doesn't care that packed-refs is locked. + assertResults(cmds, OK, OK); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch", B); + assertEquals(3, refsChangedEvents); + } + } finally { + myLock.unlock(); + } + } + + @Test + public void oneRefLockFailure() throws Exception { + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE), + new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); + + LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master")); + assertTrue(myLock.lock()); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists()); + assertFalse(getLockFile("refs/heads/branch").exists()); + + if (atomic) { + assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); + assertRefs("refs/heads/master", A); + assertEquals(1, refsChangedEvents); + } else { + assertResults(cmds, OK, LOCK_FAILURE); + assertRefs( + "refs/heads/branch", B, + "refs/heads/master", A); + assertEquals(2, refsChangedEvents); + } + } finally { + myLock.unlock(); + } + } + + @Test + public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception { + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); + + LockFile myLock = refdir.lockPackedRefs(); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertFalse(getLockFile("refs/heads/master").exists()); + assertResults(cmds, OK); + assertEquals(2, refsChangedEvents); + assertRefs("refs/heads/master", B); + } finally { + myLock.unlock(); + } + } + + @Test + public void atomicUpdateRespectsInProcessLock() throws Exception { + assumeTrue(atomic); + + writeLooseRef("refs/heads/master", A); + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + + Thread t = new Thread(() -> { + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + ReentrantLock l = refdir.inProcessPackedRefsLock; + l.lock(); + try { + t.start(); + long timeoutSecs = 10; + long startNanos = System.nanoTime(); + + // Hold onto the lock until we observe the worker thread has attempted to + // acquire it. + while (l.getQueueLength() == 0) { + long elapsedNanos = System.nanoTime() - startNanos; + assertTrue( + "timed out waiting for work thread to attempt to acquire lock", + NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs); + Thread.sleep(3); + } + + // Once we unlock, the worker thread should finish the update promptly. + l.unlock(); + t.join(SECONDS.toMillis(timeoutSecs)); + assertFalse(t.isAlive()); + } finally { + if (l.isHeldByCurrentThread()) { + l.unlock(); + } + } + + assertResults(cmds, OK, OK); + assertEquals(2, refsChangedEvents); + assertRefs( + "refs/heads/master", B, + "refs/heads/branch", B); + } + + private void setLogAllRefUpdates(boolean enable) throws Exception { + StoredConfig cfg = diskRepo.getConfig(); + cfg.load(); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable); + cfg.save(); + } + + private void writeLooseRef(String name, AnyObjectId id) throws IOException { + write(new File(diskRepo.getDirectory(), name), id.name() + "\n"); + } + + private void writeRef(String name, AnyObjectId id) throws IOException { + RefUpdate u = diskRepo.updateRef(name); + u.setRefLogMessage(getClass().getSimpleName(), false); + u.setForceUpdate(true); + u.setNewObjectId(id); + RefUpdate.Result r = u.update(); + switch (r) { + case NEW: + case FORCED: + return; + default: + throw new IOException("Got " + r + " while updating " + name); + } + } + + private BatchRefUpdate newBatchUpdate(List cmds) { + BatchRefUpdate u = refdir.newBatchUpdate(); + if (atomic) { + assertTrue(u.isAtomic()); + } else { + u.setAtomic(false); + } + u.addCommand(cmds); + return u; + } + + private void execute(BatchRefUpdate u) throws IOException { + execute(u, false); + } + + private void execute(BatchRefUpdate u, boolean strictWork) throws IOException { + try (RevWalk rw = new RevWalk(diskRepo)) { + u.execute(rw, + strictWork ? new StrictWorkMonitor() : NullProgressMonitor.INSTANCE); + } + } + + private void assertRefs(Object... args) throws IOException { + if (args.length % 2 != 0) { + throw new IllegalArgumentException( + "expected even number of args: " + Arrays.toString(args)); + } + + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < args.length; i += 2) { + expected.put((String) args[i], (AnyObjectId) args[i + 1]); + } + + Map refs = refdir.getRefs(RefDatabase.ALL); + Ref actualHead = refs.remove(Constants.HEAD); + if (actualHead != null) { + String actualLeafName = actualHead.getLeaf().getName(); + assertEquals( + "expected HEAD to point to refs/heads/master, got: " + actualLeafName, + "refs/heads/master", actualLeafName); + AnyObjectId expectedMaster = expected.get("refs/heads/master"); + assertNotNull("expected master ref since HEAD exists", expectedMaster); + assertEquals(expectedMaster, actualHead.getObjectId()); + } + + Map actual = new LinkedHashMap<>(); + refs.forEach((n, r) -> actual.put(n, r.getObjectId())); + + assertEquals(expected.keySet(), actual.keySet()); + actual.forEach((n, a) -> assertEquals(n, expected.get(n), a)); + } + + enum Result { + OK(ReceiveCommand.Result.OK), + LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE), + REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD), + REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT), + TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted); + + final Predicate p; + + private Result(Predicate p) { + this.p = p; + } + + private Result(ReceiveCommand.Result result) { + this(c -> c.getResult() == result); + } + } + + private void assertResults( + List cmds, Result... expected) { + if (expected.length != cmds.size()) { + throw new IllegalArgumentException( + "expected " + cmds.size() + " result args"); + } + for (int i = 0; i < cmds.size(); i++) { + ReceiveCommand c = cmds.get(i); + Result r = expected[i]; + assertTrue( + String.format( + "result of command (%d) should be %s: %s %s%s", + Integer.valueOf(i), r, c, + c.getResult(), + c.getMessage() != null ? " (" + c.getMessage() + ")" : ""), + r.p.test(c)); + } + } + + private Map getLastReflogs(String... names) + throws IOException { + Map result = new LinkedHashMap<>(); + for (String name : names) { + ReflogEntry e = getLastReflog(name); + if (e != null) { + result.put(name, e); + } + } + return result; + } + + private ReflogEntry getLastReflog(String name) throws IOException { + ReflogReader r = diskRepo.getReflogReader(name); + if (r == null) { + return null; + } + return r.getLastEntry(); + } + + private File getLockFile(String refName) { + return LockFile.getLockFile(refdir.fileFor(refName)); + } + + private void assertReflogUnchanged( + Map old, String name) throws IOException { + assertReflogEquals(old.get(name), getLastReflog(name), true); + } + + private static void assertReflogEquals( + ReflogEntry expected, ReflogEntry actual) { + assertReflogEquals(expected, actual, false); + } + + private static void assertReflogEquals( + ReflogEntry expected, ReflogEntry actual, boolean strictTime) { + if (expected == null) { + assertNull(actual); + return; + } + assertNotNull(actual); + assertEquals(expected.getOldId(), actual.getOldId()); + assertEquals(expected.getNewId(), actual.getNewId()); + if (strictTime) { + assertEquals(expected.getWho(), actual.getWho()); + } else { + assertEquals(expected.getWho().getName(), actual.getWho().getName()); + assertEquals( + expected.getWho().getEmailAddress(), + actual.getWho().getEmailAddress()); + } + assertEquals(expected.getComment(), actual.getComment()); + } + + private static ReflogEntry reflog(ObjectId oldId, ObjectId newId, + PersonIdent who, String comment) { + return new ReflogEntry() { + @Override + public ObjectId getOldId() { + return oldId; + } + + @Override + public ObjectId getNewId() { + return newId; + } + + @Override + public PersonIdent getWho() { + return who; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public CheckoutEntry parseCheckout() { + throw new UnsupportedOperationException(); + } + }; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.fail; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -71,25 +72,25 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.FileUtils; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; import org.junit.After; import org.junit.Before; import org.junit.Test; public class ConcurrentRepackTest extends RepositoryTestCase { + @Override @Before public void setUp() throws Exception { WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); windowCacheConfig.setPackedGitOpenFiles(1); - WindowCache.reconfigure(windowCacheConfig); + windowCacheConfig.install(); super.setUp(); } + @Override @After public void tearDown() throws Exception { super.tearDown(); - WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); - WindowCache.reconfigure(windowCacheConfig); + new WindowCacheConfig().install(); } @Test @@ -206,12 +207,14 @@ private static void whackCache() { final WindowCacheConfig config = new WindowCacheConfig(); config.setPackedGitOpenFiles(1); - WindowCache.reconfigure(config); + config.install(); } private RevObject parse(final AnyObjectId id) throws MissingObjectException, IOException { - return new RevWalk(db).parseAny(id); + try (RevWalk rw = new RevWalk(db)) { + return rw.parseAny(id); + } } private File[] pack(final Repository src, final RevObject... list) @@ -234,20 +237,15 @@ throws IOException { final long begin = files[0].getParentFile().lastModified(); NullProgressMonitor m = NullProgressMonitor.INSTANCE; - OutputStream out; - out = new SafeBufferedOutputStream(new FileOutputStream(files[0])); - try { + try (OutputStream out = new BufferedOutputStream( + new FileOutputStream(files[0]))) { pw.writePack(m, m, out); - } finally { - out.close(); } - out = new SafeBufferedOutputStream(new FileOutputStream(files[1])); - try { + try (OutputStream out = new BufferedOutputStream( + new FileOutputStream(files[1]))) { pw.writeIndex(out); - } finally { - out.close(); } touch(begin, files[0].getParentFile()); @@ -274,13 +272,12 @@ } private File fullPackFileName(final ObjectId name, final String suffix) { - final File packdir = new File(db.getObjectDatabase().getDirectory(), "pack"); + final File packdir = db.getObjectDatabase().getPackDirectory(); return new File(packdir, "pack-" + name.name() + suffix); } private RevObject writeBlob(final Repository repo, final String data) throws IOException { - final RevWalk revWalk = new RevWalk(repo); final byte[] bytes = Constants.encode(data); final ObjectId id; try (ObjectInserter inserter = repo.newObjectInserter()) { @@ -293,6 +290,8 @@ } catch (MissingObjectException e) { // Ok } - return revWalk.lookupBlob(id); + try (RevWalk revWalk = new RevWalk(repo)) { + return revWalk.lookupBlob(id); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/DescriptionTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/DescriptionTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/DescriptionTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/DescriptionTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016, 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.internal.storage.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.junit.Test; + +/** Test managing the gitweb description file. */ +public class DescriptionTest extends LocalDiskRepositoryTestCase { + private static final String UNCONFIGURED = "Unnamed repository; edit this file to name it for gitweb."; + + @Test + public void description() throws IOException { + Repository git = createBareRepository(); + File path = new File(git.getDirectory(), "description"); + assertNull("description", git.getGitwebDescription()); + + String desc = "a test repo\nfor jgit"; + git.setGitwebDescription(desc); + assertEquals(desc + '\n', read(path)); + assertEquals(desc, git.getGitwebDescription()); + + git.setGitwebDescription(null); + assertEquals("", read(path)); + + desc = "foo"; + git.setGitwebDescription(desc); + assertEquals(desc + '\n', read(path)); + assertEquals(desc, git.getGitwebDescription()); + + git.setGitwebDescription(""); + assertEquals("", read(path)); + + git.setGitwebDescription(UNCONFIGURED); + assertEquals(UNCONFIGURED + '\n', read(path)); + assertNull("description", git.getGitwebDescription()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -80,7 +80,9 @@ ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, ""); config.save(); - new FileRepository(r.getDirectory()); + try (FileRepository repo = new FileRepository(r.getDirectory())) { + // Unused + } } @Test @@ -91,8 +93,7 @@ ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, "notanumber"); config.save(); - try { - new FileRepository(r.getDirectory()); + try (FileRepository repo = new FileRepository(r.getDirectory())) { fail("IllegalArgumentException not thrown"); } catch (IllegalArgumentException e) { assertNotNull(e.getMessage()); @@ -104,78 +105,78 @@ Repository r = createWorkRepository(); StoredConfig config = r.getConfig(); config.setLong(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1); + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 999999); config.save(); - try { - new FileRepository(r.getDirectory()); + try (FileRepository repo = new FileRepository(r.getDirectory())) { fail("IOException not thrown"); } catch (IOException e) { assertNotNull(e.getMessage()); } } - @SuppressWarnings("resource" /* java 7 */) @Test public void absoluteGitDirRef() throws Exception { Repository repo1 = createWorkRepository(); File dir = createTempDirectory("dir"); File dotGit = new File(dir, Constants.DOT_GIT); - new FileWriter(dotGit).append( - "gitdir: " + repo1.getDirectory().getAbsolutePath()).close(); - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - - builder.setWorkTree(dir); - builder.setMustExist(true); - Repository repo2 = builder.build(); - - assertEquals(repo1.getDirectory().getAbsolutePath(), repo2 - .getDirectory().getAbsolutePath()); - assertEquals(dir, repo2.getWorkTree()); + try (FileWriter writer = new FileWriter(dotGit)) { + writer.append("gitdir: " + repo1.getDirectory().getAbsolutePath()).close(); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + + builder.setWorkTree(dir); + builder.setMustExist(true); + Repository repo2 = builder.build(); + + assertEquals(repo1.getDirectory().getAbsolutePath(), repo2 + .getDirectory().getAbsolutePath()); + assertEquals(dir, repo2.getWorkTree()); + } } - @SuppressWarnings("resource" /* java 7 */) @Test public void relativeGitDirRef() throws Exception { Repository repo1 = createWorkRepository(); File dir = new File(repo1.getWorkTree(), "dir"); assertTrue(dir.mkdir()); File dotGit = new File(dir, Constants.DOT_GIT); - new FileWriter(dotGit).append("gitdir: ../" + Constants.DOT_GIT) - .close(); + try (FileWriter writer = new FileWriter(dotGit)) { + writer.append("gitdir: ../" + Constants.DOT_GIT).close(); - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - builder.setWorkTree(dir); - builder.setMustExist(true); - Repository repo2 = builder.build(); - - // The tmp directory may be a symlink so the actual path - // may not - assertEquals(repo1.getDirectory().getCanonicalPath(), repo2 - .getDirectory().getCanonicalPath()); - assertEquals(dir, repo2.getWorkTree()); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + builder.setWorkTree(dir); + builder.setMustExist(true); + Repository repo2 = builder.build(); + + // The tmp directory may be a symlink so the actual path + // may not + assertEquals(repo1.getDirectory().getCanonicalPath(), repo2 + .getDirectory().getCanonicalPath()); + assertEquals(dir, repo2.getWorkTree()); + } } - @SuppressWarnings("resource" /* java 7 */) @Test public void scanWithGitDirRef() throws Exception { Repository repo1 = createWorkRepository(); File dir = createTempDirectory("dir"); File dotGit = new File(dir, Constants.DOT_GIT); - new FileWriter(dotGit).append( - "gitdir: " + repo1.getDirectory().getAbsolutePath()).close(); - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - - builder.setWorkTree(dir); - builder.findGitDir(dir); - assertEquals(repo1.getDirectory().getAbsolutePath(), builder - .getGitDir().getAbsolutePath()); - builder.setMustExist(true); - Repository repo2 = builder.build(); - - // The tmp directory may be a symlink - assertEquals(repo1.getDirectory().getCanonicalPath(), repo2 - .getDirectory().getCanonicalPath()); - assertEquals(dir, repo2.getWorkTree()); + try (FileWriter writer = new FileWriter(dotGit)) { + writer.append( + "gitdir: " + repo1.getDirectory().getAbsolutePath()).close(); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + + builder.setWorkTree(dir); + builder.findGitDir(dir); + assertEquals(repo1.getDirectory().getAbsolutePath(), builder + .getGitDir().getAbsolutePath()); + builder.setMustExist(true); + Repository repo2 = builder.build(); + + // The tmp directory may be a symlink + assertEquals(repo1.getDirectory().getCanonicalPath(), repo2 + .getDirectory().getCanonicalPath()); + assertEquals(dir, repo2.getWorkTree()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,7 +42,6 @@ */ package org.eclipse.jgit.internal.storage.file; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; @@ -51,7 +50,6 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.util.FileUtils; import org.junit.After; import org.junit.Before; @@ -59,7 +57,7 @@ public class FileSnapshotTest { - private List files = new ArrayList(); + private List files = new ArrayList<>(); private File trash; @@ -99,22 +97,6 @@ } /** - * Create a file, wait long enough and verify that it has not been modified. - * 3.5 seconds mean any difference between file system timestamp and system - * clock should be significant. - * - * @throws Exception - */ - @Test - public void testOldFile() throws Exception { - File f1 = createFile("oldfile"); - waitNextSec(f1); - FileSnapshot save = FileSnapshot.save(f1); - Thread.sleep(3500); - assertFalse(save.isModified(f1)); - } - - /** * Create a file, but don't wait long enough for the difference between file * system clock and system clock to be significant. Assume the file may have * been modified. It may have been, but the clock alone cannot determine @@ -153,11 +135,8 @@ } private static void append(File f, byte b) throws IOException { - FileOutputStream os = new FileOutputStream(f, true); - try { + try (FileOutputStream os = new FileOutputStream(f, true)) { os.write(b); - } finally { - os.close(); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,15 +44,21 @@ package org.eclipse.jgit.internal.storage.file; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.Iterator; +import java.util.List; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.pack.PackConfig; import org.junit.Test; import org.junit.experimental.theories.DataPoints; @@ -85,6 +91,7 @@ assertEquals(4, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackFiles); + assertEquals(0, stats.numberOfBitmaps); } @Theory @@ -102,6 +109,7 @@ assertEquals(0, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(2, stats.numberOfBitmaps); } @Theory @@ -118,6 +126,7 @@ assertEquals(0, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); // Do the gc again and check that it hasn't changed anything gc.gc(); @@ -125,10 +134,12 @@ assertEquals(0, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); } @Theory - public void testPackCommitsAndLooseOne(boolean aggressive) throws Exception { + public void testPackCommitsAndLooseOne(boolean aggressive) + throws Exception { BranchBuilder bb = tr.branch("refs/heads/master"); RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); bb.commit().add("A", "A2").add("B", "B2").create(); @@ -143,6 +154,7 @@ assertEquals(0, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfPackedObjects); assertEquals(2, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); } @Theory @@ -169,14 +181,9 @@ stats = gc.getStatistics(); assertEquals(0, stats.numberOfLooseObjects); - Iterator pIt = repo.getObjectDatabase().getPacks().iterator(); - long c = pIt.next().getObjectCount(); - if (c == 9) - assertEquals(2, pIt.next().getObjectCount()); - else { - assertEquals(2, c); - assertEquals(9, pIt.next().getObjectCount()); - } + List packs = new ArrayList<>( + repo.getObjectDatabase().getPacks()); + assertEquals(11, packs.get(0).getObjectCount()); } @Test @@ -184,16 +191,26 @@ BranchBuilder bb = tr.branch("refs/heads/master"); bb.commit().message("M").add("M", "M").create(); + String tempRef = "refs/heads/soon-to-be-unreferenced"; + BranchBuilder bb2 = tr.branch(tempRef); + bb2.commit().message("M").add("M", "M").create(); + gc.setExpireAgeMillis(0); gc.gc(); stats = gc.getStatistics(); assertEquals(0, stats.numberOfLooseObjects); - assertEquals(3, stats.numberOfPackedObjects); + assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); File oldPackfile = tr.getRepository().getObjectDatabase().getPacks() .iterator().next().getPackFile(); fsTick(); + + // delete the temp ref, orphaning its commit + RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false); + update.setForceUpdate(true); + update.delete(); + bb.commit().message("B").add("B", "Q").create(); // The old packfile is too young to be deleted. We should end up with @@ -204,23 +221,113 @@ assertEquals(0, stats.numberOfLooseObjects); // if objects exist in multiple packFiles then they are counted multiple // times - assertEquals(9, stats.numberOfPackedObjects); + assertEquals(10, stats.numberOfPackedObjects); assertEquals(2, stats.numberOfPackFiles); - // repack again but now without a grace period for packfiles. We should - // end up with one packfile + // repack again but now without a grace period for loose objects. Since + // we don't have loose objects anymore this shouldn't change anything gc.setExpireAgeMillis(0); gc.gc(); stats = gc.getStatistics(); assertEquals(0, stats.numberOfLooseObjects); // if objects exist in multiple packFiles then they are counted multiple // times + assertEquals(10, stats.numberOfPackedObjects); + assertEquals(2, stats.numberOfPackFiles); + + // repack again but now without a grace period for packfiles. We should + // end up with one packfile + gc.setPackExpireAgeMillis(0); + + // we want to keep newly-loosened objects though + gc.setExpireAgeMillis(-1); + + gc.gc(); + stats = gc.getStatistics(); + assertEquals(1, stats.numberOfLooseObjects); + // if objects exist in multiple packFiles then they are counted multiple + // times assertEquals(6, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testImmediatePruning() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().message("M").add("M", "M").create(); + + String tempRef = "refs/heads/soon-to-be-unreferenced"; + BranchBuilder bb2 = tr.branch(tempRef); + bb2.commit().message("M").add("M", "M").create(); + + gc.setExpireAgeMillis(0); + gc.gc(); + stats = gc.getStatistics(); + + fsTick(); + + // delete the temp ref, orphaning its commit + RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false); + update.setForceUpdate(true); + update.delete(); + + bb.commit().message("B").add("B", "Q").create(); + + // We want to immediately prune deleted objects + FileBasedConfig config = repo.getConfig(); + config.setString(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PRUNEEXPIRE, "now"); + config.save(); + + //And we don't want to keep packs full of dead objects + gc.setPackExpireAgeMillis(0); + + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(6, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testPreserveAndPruneOldPacks() throws Exception { + testPreserveOldPacks(); + configureGc(gc, false).setPrunePreserved(true); + gc.gc(); + + assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists()); + } + + private void testPreserveOldPacks() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().message("P").add("P", "P").create(); + + // pack loose object into packfile + gc.setExpireAgeMillis(0); + gc.gc(); + File oldPackfile = tr.getRepository().getObjectDatabase().getPacks() + .iterator().next().getPackFile(); + assertTrue(oldPackfile.exists()); + + fsTick(); + bb.commit().message("B").add("B", "Q").create(); + + // repack again but now without a grace period for packfiles. We should + // end up with a new packfile and the old one should be placed in the + // preserved directory + gc.setPackExpireAgeMillis(0); + configureGc(gc, false).setPreserveOldPacks(true); + gc.gc(); + File oldPackDir = repo.getObjectDatabase().getPreservedDirectory(); + String oldPackFileName = oldPackfile.getName(); + String oldPackName = oldPackFileName.substring(0, + oldPackFileName.lastIndexOf('.')) + ".old-pack"; //$NON-NLS-1$ + File preservePackFile = new File(oldPackDir, oldPackName); + assertTrue(preservePackFile.exists()); } - private void configureGc(GC myGc, boolean aggressive) { + private PackConfig configureGc(GC myGc, boolean aggressive) { PackConfig pconfig = new PackConfig(repo); if (aggressive) { pconfig.setDeltaSearchWindowSize(250); @@ -229,5 +336,6 @@ } else pconfig = new PackConfig(repo); myGc.setPackConfig(pconfig); + return pconfig; } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,19 +45,37 @@ import static java.lang.Integer.valueOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.errors.CancelledException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.EmptyProgressMonitor; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class GcConcurrentTest extends GcTestCase { @@ -68,6 +86,7 @@ class DoRepack extends EmptyProgressMonitor implements Callable { + @Override public void beginTask(String title, int totalWork) { if (title.equals(JGitText.get().writingObjects)) { try { @@ -81,6 +100,7 @@ } /** @return 0 for success, 1 in case of error when writing pack */ + @Override public Integer call() throws Exception { try { gc.setProgressMonitor(this); @@ -116,4 +136,141 @@ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } } + + @Test + public void repackAndGetStats() throws Exception { + TestRepository.BranchBuilder test = tr.branch("test"); + test.commit().add("a", "a").create(); + GC gc1 = new GC(tr.getRepository()); + gc1.setPackExpireAgeMillis(0); + gc1.gc(); + test.commit().add("b", "b").create(); + + // Create a new Repository instance and trigger a gc + // from that instance. Reusing the existing repo instance + // tr.getRepository() would not show the problem. + FileRepository r2 = new FileRepository( + tr.getRepository().getDirectory()); + GC gc2 = new GC(r2); + gc2.setPackExpireAgeMillis(0); + gc2.gc(); + + new GC(tr.getRepository()).getStatistics(); + } + + @Test + public void repackAndUploadPack() throws Exception { + TestRepository.BranchBuilder test = tr.branch("test"); + // RevCommit a = test.commit().add("a", "a").create(); + test.commit().add("a", "a").create(); + + GC gc1 = new GC(tr.getRepository()); + gc1.setPackExpireAgeMillis(0); + gc1.gc(); + + RevCommit b = test.commit().add("b", "b").create(); + + FileRepository r2 = new FileRepository( + tr.getRepository().getDirectory()); + GC gc2 = new GC(r2); + gc2.setPackExpireAgeMillis(0); + gc2.gc(); + + // Simulate parts of an UploadPack. This is the situation on + // server side (e.g. gerrit) when when clients are + // cloning/fetching while the server side repo's + // are gc'ed by an external process (e.g. scheduled + // native git gc) + try (PackWriter pw = new PackWriter(tr.getRepository())) { + pw.setUseBitmaps(true); + pw.preparePack(NullProgressMonitor.INSTANCE, Sets.of(b), + Collections. emptySet()); + new GC(tr.getRepository()).getStatistics(); + } + } + + PackFile getSinglePack(FileRepository r) { + Collection packs = r.getObjectDatabase().getPacks(); + assertEquals(1, packs.size()); + return packs.iterator().next(); + } + + @Test + public void repackAndCheckBitmapUsage() throws Exception { + // create a test repository with one commit and pack all objects. After + // packing create loose objects to trigger creation of a new packfile on + // the next gc + TestRepository.BranchBuilder test = tr.branch("test"); + test.commit().add("a", "a").create(); + FileRepository repository = tr.getRepository(); + GC gc1 = new GC(repository); + gc1.setPackExpireAgeMillis(0); + gc1.gc(); + String oldPackName = getSinglePack(repository).getPackName(); + RevCommit b = test.commit().add("b", "b").create(); + + // start the garbage collection on a new repository instance, + FileRepository repository2 = new FileRepository(repository.getDirectory()); + GC gc2 = new GC(repository2); + gc2.setPackExpireAgeMillis(0); + gc2.gc(); + String newPackName = getSinglePack(repository2).getPackName(); + // make sure gc() has caused creation of a new packfile + assertNotEquals(oldPackName, newPackName); + + // Even when asking again for the set of packfiles outdated data + // will be returned. As long as the repository can work on cached data + // it will do so and not detect that a new packfile exists. + assertNotEquals(getSinglePack(repository).getPackName(), newPackName); + + // Only when accessing object content it is required to rescan the pack + // directory and the new packfile will be detected. + repository.getObjectDatabase().open(b).getSize(); + assertEquals(getSinglePack(repository).getPackName(), newPackName); + assertNotNull(getSinglePack(repository).getBitmapIndex()); + } + + @Test + public void testInterruptGc() throws Exception { + FileBasedConfig c = repo.getConfig(); + c.setInt(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1); + c.save(); + SampleDataRepositoryTestCase.copyCGitTestPacks(repo); + ExecutorService executor = Executors.newSingleThreadExecutor(); + final CountDownLatch latch = new CountDownLatch(1); + Future> result = executor + .submit(new Callable>() { + + @Override + public Collection call() throws Exception { + long start = System.currentTimeMillis(); + System.out.println("starting gc"); + latch.countDown(); + Collection r = gc.gc(); + System.out.println("gc took " + + (System.currentTimeMillis() - start) + " ms"); + return r; + } + }); + try { + latch.await(); + Thread.sleep(5); + executor.shutdownNow(); + result.get(); + fail("thread wasn't interrupted"); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof CancelledException) { + assertEquals(JGitText.get().operationCanceled, + cause.getMessage()); + } else if (cause instanceof IOException) { + Throwable cause2 = cause.getCause(); + assertTrue(cause2 instanceof InterruptedException + || cause2 instanceof ExecutionException); + } else { + fail("unexpected exception " + e); + } + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 Ericsson + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; +import java.time.Instant; + +import org.junit.Before; +import org.junit.Test; + +public class GcDeleteEmptyRefsFoldersTest extends GcTestCase { + private static final String REF_FOLDER_01 = "A/B/01"; + private static final String REF_FOLDER_02 = "C/D/02"; + + private Path refsDir; + private Path heads; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + refsDir = Paths.get(repo.getDirectory().getAbsolutePath()) + .resolve("refs"); + heads = refsDir.resolve("heads"); + } + + @Test + public void emptyRefFoldersAreDeleted() throws Exception { + FileTime fileTime = FileTime.from(Instant.now().minusSeconds(31)); + Path refDir01 = Files.createDirectories(heads.resolve(REF_FOLDER_01)); + Path refDir02 = Files.createDirectories(heads.resolve(REF_FOLDER_02)); + setLastModifiedTime(fileTime, heads, REF_FOLDER_01); + setLastModifiedTime(fileTime, heads, REF_FOLDER_02); + assertTrue(refDir01.toFile().exists()); + assertTrue(refDir02.toFile().exists()); + gc.gc(); + + assertFalse(refDir01.toFile().exists()); + assertFalse(refDir01.getParent().toFile().exists()); + assertFalse(refDir01.getParent().getParent().toFile().exists()); + assertFalse(refDir02.toFile().exists()); + assertFalse(refDir02.getParent().toFile().exists()); + assertFalse(refDir02.getParent().getParent().toFile().exists()); + } + + private void setLastModifiedTime(FileTime fileTime, Path path, String folder) throws IOException { + long numParents = folder.chars().filter(c -> c == '/').count(); + Path folderPath = path.resolve(folder); + for(int folderLevel = 0; folderLevel <= numParents; folderLevel ++ ) { + Files.setLastModifiedTime(folderPath, fileTime); + folderPath = folderPath.getParent(); + } + } + + @Test + public void emptyRefFoldersAreKeptIfTheyAreTooRecent() + throws Exception { + Path refDir01 = Files.createDirectories(heads.resolve(REF_FOLDER_01)); + Path refDir02 = Files.createDirectories(heads.resolve(REF_FOLDER_02)); + assertTrue(refDir01.toFile().exists()); + assertTrue(refDir02.toFile().exists()); + gc.gc(); + + assertTrue(refDir01.toFile().exists()); + assertTrue(refDir02.toFile().exists()); + } + + @Test + public void nonEmptyRefsFoldersAreKept() throws Exception { + Path refDir01 = Files.createDirectories(heads.resolve(REF_FOLDER_01)); + Path refDir02 = Files.createDirectories(heads.resolve(REF_FOLDER_02)); + Path ref01 = Files.createFile(refDir01.resolve("ref01")); + Path ref02 = Files.createFile(refDir01.resolve("ref02")); + assertTrue(refDir01.toFile().exists()); + assertTrue(refDir02.toFile().exists()); + assertTrue(ref01.toFile().exists()); + assertTrue(ref02.toFile().exists()); + gc.gc(); + assertTrue(refDir01.toFile().exists()); + assertTrue(refDir02.toFile().exists()); + assertTrue(ref01.toFile().exists()); + assertTrue(ref02.toFile().exists()); + } +} \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,7 +73,10 @@ .iterator(); PackFile singlePack = packIt.next(); assertFalse(packIt.hasNext()); - File keepFile = new File(singlePack.getPackFile().getPath() + ".keep"); + String packFileName = singlePack.getPackFile().getPath(); + String keepFileName = packFileName.substring(0, + packFileName.lastIndexOf('.')) + ".keep"; + File keepFile = new File(keepFileName); assertFalse(keepFile.exists()); assertTrue(keepFile.createNewFile()); bb.commit().add("A", "A2").add("B", "B2").create(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 Ericsson + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +public class GcOrphanFilesTest extends GcTestCase { + private final static String PACK = "pack"; + + private final static String BITMAP_File_1 = PACK + "-1.bitmap"; + + private final static String IDX_File_2 = PACK + "-2.idx"; + + private final static String IDX_File_malformed = PACK + "-1234idx"; + + private final static String PACK_File_2 = PACK + "-2.pack"; + + private final static String PACK_File_3 = PACK + "-3.pack"; + + private File packDir; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + packDir = repo.getObjectDatabase().getPackDirectory(); + } + + @Test + public void bitmapAndIdxDeletedButPackNot() throws Exception { + createFileInPackFolder(BITMAP_File_1); + createFileInPackFolder(IDX_File_2); + createFileInPackFolder(PACK_File_3); + gc.gc(); + assertFalse(new File(packDir, BITMAP_File_1).exists()); + assertFalse(new File(packDir, IDX_File_2).exists()); + assertTrue(new File(packDir, PACK_File_3).exists()); + } + + @Test + public void bitmapDeletedButIdxAndPackNot() throws Exception { + createFileInPackFolder(BITMAP_File_1); + createFileInPackFolder(IDX_File_2); + createFileInPackFolder(PACK_File_2); + createFileInPackFolder(PACK_File_3); + gc.gc(); + assertFalse(new File(packDir, BITMAP_File_1).exists()); + assertTrue(new File(packDir, IDX_File_2).exists()); + assertTrue(new File(packDir, PACK_File_2).exists()); + assertTrue(new File(packDir, PACK_File_3).exists()); + } + + @Test + public void malformedIdxNotDeleted() throws Exception { + createFileInPackFolder(IDX_File_malformed); + gc.gc(); + assertTrue(new File(packDir, IDX_File_malformed).exists()); + } + + private void createFileInPackFolder(String fileName) throws IOException { + if (!packDir.exists() || !packDir.isDirectory()) { + assertTrue(packDir.mkdirs()); + } + assertTrue(new File(packDir, fileName).createNewFile()); + } + + @Test + public void noSuchPackFolder() throws Exception { + assertTrue(packDir.delete()); + gc.gc(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,18 @@ package org.eclipse.jgit.internal.storage.file; -import static java.lang.Integer.valueOf; +import static org.hamcrest.Matchers.lessThanOrEqualTo; 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.assertThat; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; @@ -70,6 +75,7 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.junit.Test; +@SuppressWarnings("boxing") public class GcPackRefsTest extends GcTestCase { @Test public void looseRefPacked() throws Exception { @@ -77,7 +83,18 @@ tr.lightweightTag("t", a); gc.packRefs(); - assertSame(repo.getRef("t").getStorage(), Storage.PACKED); + assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED); + } + + @Test + public void emptyRefDirectoryDeleted() throws Exception { + String ref = "dir/ref"; + tr.branch(ref).commit().create(); + String name = repo.findRef(ref).getName(); + Path dir = repo.getDirectory().toPath().resolve(name).getParent(); + assertNotNull(dir); + gc.packRefs(); + assertFalse(Files.exists(dir)); } @Test @@ -85,26 +102,23 @@ RevBlob a = tr.blob("a"); tr.lightweightTag("t", a); - final CyclicBarrier syncPoint = new CyclicBarrier(2); + CyclicBarrier syncPoint = new CyclicBarrier(2); - Callable packRefs = new Callable() { - - /** @return 0 for success, 1 in case of error when writing pack */ - public Integer call() throws Exception { - syncPoint.await(); - try { - gc.packRefs(); - return valueOf(0); - } catch (IOException e) { - return valueOf(1); - } + // Returns 0 for success, 1 in case of error when writing pack. + Callable packRefs = () -> { + syncPoint.await(); + try { + gc.packRefs(); + return 0; + } catch (IOException e) { + return 1; } }; ExecutorService pool = Executors.newFixedThreadPool(2); try { Future p1 = pool.submit(packRefs); Future p2 = pool.submit(packRefs); - assertEquals(1, p1.get().intValue() + p2.get().intValue()); + assertThat(p1.get() + p2.get(), lessThanOrEqualTo(1)); } finally { pool.shutdown(); pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); @@ -118,7 +132,7 @@ tr.lightweightTag("t1", a); tr.lightweightTag("t2", a); LockFile refLock = new LockFile(new File(repo.getDirectory(), - "refs/tags/t1"), repo.getFS()); + "refs/tags/t1")); try { refLock.lock(); gc.packRefs(); @@ -126,8 +140,8 @@ refLock.unlock(); } - assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE); - assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED); + assertSame(repo.exactRef("refs/tags/t1").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("refs/tags/t2").getStorage(), Storage.PACKED); } @Test @@ -143,10 +157,11 @@ try { Future result = pool.submit(new Callable() { + @Override public Result call() throws Exception { RefUpdate update = new RefDirectoryUpdate( (RefDirectory) repo.getRefDatabase(), - repo.getRef("refs/tags/t")) { + repo.exactRef("refs/tags/t")) { @Override public boolean isForceUpdate() { try { @@ -167,6 +182,7 @@ }); pool.submit(new Callable() { + @Override public Void call() throws Exception { refUpdateLockedRef.await(); gc.packRefs(); @@ -182,7 +198,7 @@ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } - assertEquals(repo.getRef("refs/tags/t").getObjectId(), b); + assertEquals(repo.exactRef("refs/tags/t").getObjectId(), b); } @Test @@ -194,23 +210,23 @@ // check for the unborn branch master. HEAD should point to master and // master doesn't exist. - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); git.checkout().setName("refs/heads/side").call(); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); // check for detached HEAD git.checkout().setName(first.getName()).call(); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); } @Test @@ -229,20 +245,20 @@ // check for the unborn branch master. HEAD should point to master and // master doesn't exist. - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); // check for non-detached HEAD repo.updateRef(Constants.HEAD).link("refs/heads/side"); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getObjectId(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(), second.getId()); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.File; import java.util.Collections; import java.util.Date; @@ -113,8 +114,24 @@ fsTick(); gc.gc(); stats = gc.getStatistics(); + assertNoEmptyFanoutDirectories(); assertEquals(0, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfPackedObjects); assertEquals(2, stats.numberOfPackFiles); } + + private void assertNoEmptyFanoutDirectories() { + File[] fanout = repo.getObjectsDirectory().listFiles(); + for (File f : fanout) { + if (f.isDirectory()) { + String[] entries = f.list(); + if (entries == null || entries.length == 0) { + assertFalse( + "Found empty fanout directory " + + f.getAbsolutePath() + " after pruning", + f.getName().length() == 2); + } + } + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,6 +75,7 @@ tr.blob("x"); stats = gc.getStatistics(); assertEquals(9, stats.numberOfLooseObjects); + fsTick(); gc.prune(Collections. emptySet()); stats = gc.getStatistics(); assertEquals(8, stats.numberOfLooseObjects); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 Ericsson + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Paths; +import java.time.Instant; + +import org.junit.Before; +import org.junit.Test; + +public class GcTemporaryFilesTest extends GcTestCase { + private static final String TEMP_IDX = "gc_1234567890.idx_tmp"; + + private static final String TEMP_PACK = "gc_1234567890.pack_tmp"; + + private File packDir; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + packDir = Paths.get(repo.getObjectsDirectory().getAbsolutePath(), + "pack").toFile(); //$NON-NLS-1$ + } + + @Test + public void oldTempPacksAndIdxAreDeleted() throws Exception { + File tempIndex = new File(packDir, TEMP_IDX); + File tempPack = new File(packDir, TEMP_PACK); + if (!packDir.exists() || !packDir.isDirectory()) { + assertTrue(packDir.mkdirs()); + } + assertTrue(tempPack.createNewFile()); + assertTrue(tempIndex.createNewFile()); + assertTrue(tempIndex.exists()); + assertTrue(tempPack.exists()); + long _24HoursBefore = Instant.now().toEpochMilli() + - 24 * 60 * 62 * 1000; + tempIndex.setLastModified(_24HoursBefore); + tempPack.setLastModified(_24HoursBefore); + gc.gc(); + assertFalse(tempIndex.exists()); + assertFalse(tempPack.exists()); + } + + @Test + public void recentTempPacksAndIdxAreNotDeleted() throws Exception { + File tempIndex = new File(packDir, TEMP_IDX); + File tempPack = new File(packDir, TEMP_PACK); + if (!packDir.exists() || !packDir.isDirectory()) { + assertTrue(packDir.mkdirs()); + } + assertTrue(tempPack.createNewFile()); + assertTrue(tempIndex.createNewFile()); + assertTrue(tempIndex.exists()); + assertTrue(tempPack.exists()); + gc.gc(); + assertTrue(tempIndex.exists()); + assertTrue(tempPack.exists()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,6 +52,7 @@ import org.eclipse.jgit.junit.TestRepository.CommitBuilder; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.junit.After; import org.junit.Before; @@ -61,14 +62,17 @@ protected GC gc; protected RepoStatistics stats; + @Override @Before public void setUp() throws Exception { super.setUp(); repo = createWorkRepository(); - tr = new TestRepository((repo)); + tr = new TestRepository<>(repo, new RevWalk(repo), + mockSystemReader); gc = new GC(repo); } + @Override @After public void tearDown() throws Exception { super.tearDown(); @@ -103,8 +107,49 @@ return tip; } - protected long lastModified(AnyObjectId objectId) { - return repo.getObjectDatabase().fileFor(objectId).lastModified(); + /** + * Create a chain of commits of given depth with given number of added files + * per commit. + *

        + * Each commit contains {@code files} files as its content. The created + * commit chain is referenced from any ref. + *

        + * A chain will create {@code (2 + files) * depth} objects in Gits object + * database. For each depth level the following objects are created: the + * commit object, the top-level tree object and @code files} blobs for the + * content of the file "a". + * + * @param depth + * the depth of the commit chain. + * @param width + * number of files added per commit + * @return the commit that is the tip of the commit chain + * @throws Exception + */ + protected RevCommit commitChain(int depth, int width) throws Exception { + if (depth <= 0) { + throw new IllegalArgumentException("Chain depth must be > 0"); + } + if (width <= 0) { + throw new IllegalArgumentException("Number of files per commit must be > 0"); + } + CommitBuilder cb = tr.commit(); + RevCommit tip = null; + do { + --depth; + for (int i=0; i < width; i++) { + String id = depth + "-" + i; + cb.add("a" + id, id).message(id); + } + tip = cb.create(); + cb = cb.child(); + } while (depth > 0); + return tip; + } + + protected long lastModified(AnyObjectId objectId) throws IOException { + return repo.getFS().lastModified( + repo.getObjectDatabase().fileFor(objectId)); } protected static void fsTick() throws InterruptedException, IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/InflatingBitSetTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/InflatingBitSetTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/InflatingBitSetTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/InflatingBitSetTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,11 +46,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.googlecode.javaewah.EWAHCompressedBitmap; - -import org.eclipse.jgit.internal.storage.file.InflatingBitSet; import org.junit.Test; +import com.googlecode.javaewah.EWAHCompressedBitmap; + public class InflatingBitSetTest { @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,7 +49,6 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.LockFailedException; -import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; @@ -61,25 +60,26 @@ @Test public void lockFailedExceptionRecovery() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit1 = git.commit().setMessage("create file").call(); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); - assertNotNull(commit1); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - assertNotNull(git.commit().setMessage("edit file").call()); + assertNotNull(commit1); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + assertNotNull(git.commit().setMessage("edit file").call()); - LockFile lf = new LockFile(db.getIndexFile(), db.getFS()); - assertTrue(lf.lock()); - try { - git.checkout().setName(commit1.name()).call(); - fail("JGitInternalException not thrown"); - } catch (JGitInternalException e) { - assertTrue(e.getCause() instanceof LockFailedException); - lf.unlock(); - git.checkout().setName(commit1.name()).call(); + LockFile lf = new LockFile(db.getIndexFile()); + assertTrue(lf.lock()); + try { + git.checkout().setName(commit1.name()).call(); + fail("JGitInternalException not thrown"); + } catch (JGitInternalException e) { + assertTrue(e.getCause() instanceof LockFailedException); + lf.unlock(); + git.checkout().setName(commit1.name()).call(); + } } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,38 +42,185 @@ package org.eclipse.jgit.internal.storage.file; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import org.eclipse.jgit.internal.storage.file.ObjectDirectory; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Assume; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; public class ObjectDirectoryTest extends RepositoryTestCase { + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + @Test public void testConcurrentInsertionOfBlobsToTheSameNewFanOutDirectory() throws Exception { ExecutorService e = Executors.newCachedThreadPool(); for (int i=0; i < 100; ++i) { - ObjectDirectory db = createBareRepository().getObjectDatabase(); - for (Future f : e.invokeAll(blobInsertersForTheSameFanOutDir(db))) { + ObjectDirectory dir = createBareRepository().getObjectDatabase(); + for (Future f : e.invokeAll(blobInsertersForTheSameFanOutDir(dir))) { f.get(); } } } + /** + * Test packfile scanning while a gc is done from the outside (different + * process or different Repository instance). This situation occurs e.g. if + * a gerrit server is serving fetch requests while native git is doing a + * garbage collection. The test shows that when core.trustfolderstat==true + * jgit may miss to detect that a new packfile was created. This situation + * is persistent until a new full rescan of the pack directory is triggered. + * + * The test works with two Repository instances working on the same disk + * location. One (db) for all write operations (creating commits, doing gc) + * and another one (receivingDB) which just reads and which in the end shows + * the bug + * + * @throws Exception + */ + @Test + public void testScanningForPackfiles() throws Exception { + ObjectId unknownID = ObjectId + .fromString("c0ffee09d0b63d694bf49bc1e6847473f42d4a8c"); + GC gc = new GC(db); + gc.setExpireAgeMillis(0); + gc.setPackExpireAgeMillis(0); + + // the default repo db is used to create the objects. The receivingDB + // repo is used to trigger gc's + try (FileRepository receivingDB = new FileRepository( + db.getDirectory())) { + // set trustfolderstat to true. If set to false the test always + // succeeds. + FileBasedConfig cfg = receivingDB.getConfig(); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true); + cfg.save(); + + // setup a repo which has at least one pack file and trigger + // scanning of the packs directory + ObjectId id = commitFile("file.txt", "test", "master").getId(); + gc.gc(); + assertFalse(receivingDB.hasObject(unknownID)); + assertTrue(receivingDB.getObjectDatabase().hasPackedObject(id)); + + // preparations + File packsFolder = receivingDB.getObjectDatabase() + .getPackDirectory(); + // prepare creation of a temporary file in the pack folder. This + // simulates that a native git gc is happening starting to write + // temporary files but has not yet finished + File tmpFile = new File(packsFolder, "1.tmp"); + RevCommit id2 = commitFile("file.txt", "test2", "master"); + // wait until filesystem timer ticks. This raises probability that + // the next statements are executed in the same tick as the + // filesystem timer + fsTick(null); + + // create a Temp file in the packs folder and trigger a rescan of + // the packs folder. This lets receivingDB think it has scanned the + // packs folder at the current fs timestamp t1. The following gc + // will create new files which have the same timestamp t1 but this + // will not update the mtime of the packs folder. Because of that + // JGit will not rescan the packs folder later on and fails to see + // the pack file created during gc. + assertTrue(tmpFile.createNewFile()); + assertFalse(receivingDB.hasObject(unknownID)); + + // trigger a gc. This will create packfiles which have likely the + // same mtime than the packfolder + gc.gc(); + + // To deal with racy-git situations JGit's Filesnapshot class will + // report a file/folder potentially dirty if + // cachedLastReadTime-cachedLastModificationTime < 2500ms. This + // causes JGit to always rescan a file after modification. But: + // this was true only if the difference between current system time + // and cachedLastModification time was less than 2500ms. If the + // modification is more than 2500ms ago we may have reported a + // file/folder to be clean although it has not been rescanned. A + // Bug. To show the bug we sleep for more than 2500ms + Thread.sleep(2600); + + File[] ret = packsFolder.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".pack"); + } + }); + assertTrue(ret != null && ret.length == 1); + Assume.assumeTrue(tmpFile.lastModified() == ret[0].lastModified()); + + // all objects are in a new packfile but we will not detect it + assertFalse(receivingDB.hasObject(unknownID)); + assertTrue(receivingDB.hasObject(id2)); + } + } + + @Test + public void testShallowFile() + throws Exception { + FileRepository repository = createBareRepository(); + ObjectDirectory dir = repository.getObjectDatabase(); + + String commit = "d3148f9410b071edd4a4c85d2a43d1fa2574b0d2"; + try (PrintWriter writer = new PrintWriter( + new File(repository.getDirectory(), Constants.SHALLOW))) { + writer.println(commit); + } + Set shallowCommits = dir.getShallowCommits(); + assertTrue(shallowCommits.remove(ObjectId.fromString(commit))); + assertTrue(shallowCommits.isEmpty()); + } + + @Test + public void testShallowFileCorrupt() + throws Exception { + FileRepository repository = createBareRepository(); + ObjectDirectory dir = repository.getObjectDatabase(); + + String commit = "X3148f9410b071edd4a4c85d2a43d1fa2574b0d2"; + try (PrintWriter writer = new PrintWriter( + new File(repository.getDirectory(), Constants.SHALLOW))) { + writer.println(commit); + } + + expectedEx.expect(IOException.class); + expectedEx.expectMessage(MessageFormat + .format(JGitText.get().badShallowLine, commit)); + dir.getShallowCommits(); + } + private Collection> blobInsertersForTheSameFanOutDir( - final ObjectDirectory db) { + final ObjectDirectory dir) { Callable callable = new Callable() { + @Override public ObjectId call() throws Exception { - return db.newInserter().insert(Constants.OBJ_BLOB, new byte[0]); + return dir.newInserter().insert(Constants.OBJ_BLOB, new byte[0]); } }; return Collections.nCopies(4, callable); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -107,6 +107,7 @@ return rng; } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -116,10 +117,11 @@ cfg.install(); repo = createBareRepository(); - tr = new TestRepository(repo); + tr = new TestRepository<>(repo); wc = (WindowCursor) repo.newObjectReader(); } + @Override @After public void tearDown() throws Exception { if (wc != null) @@ -191,120 +193,144 @@ @Test public void testDelta_SmallObjectChain() throws Exception { - ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); - byte[] data0 = new byte[512]; - Arrays.fill(data0, (byte) 0xf3); - ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); - - TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); - packHeader(pack, 4); - objectHeader(pack, Constants.OBJ_BLOB, data0.length); - deflate(pack, data0); - - byte[] data1 = clone(0x01, data0); - byte[] delta1 = delta(data0, data1); - ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1); - objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length); - id0.copyRawTo(pack); - deflate(pack, delta1); - - byte[] data2 = clone(0x02, data1); - byte[] delta2 = delta(data1, data2); - ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2); - objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length); - id1.copyRawTo(pack); - deflate(pack, delta2); - - byte[] data3 = clone(0x03, data2); - byte[] delta3 = delta(data2, data3); - ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3); - objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length); - id2.copyRawTo(pack); - deflate(pack, delta3); - - digest(pack); - PackParser ip = index(pack.toByteArray()); - ip.setAllowThin(true); - ip.parse(NullProgressMonitor.INSTANCE); - - assertTrue("has blob", wc.has(id3)); - - ObjectLoader ol = wc.open(id3); - assertNotNull("created loader", ol); - assertEquals(Constants.OBJ_BLOB, ol.getType()); - assertEquals(data3.length, ol.getSize()); - assertFalse("is large", ol.isLarge()); - assertNotNull(ol.getCachedBytes()); - assertArrayEquals(data3, ol.getCachedBytes()); - - ObjectStream in = ol.openStream(); - assertNotNull("have stream", in); - assertEquals(Constants.OBJ_BLOB, in.getType()); - assertEquals(data3.length, in.getSize()); - byte[] act = new byte[data3.length]; - IO.readFully(in, act, 0, data3.length); - assertTrue("same content", Arrays.equals(act, data3)); - assertEquals("stream at EOF", -1, in.read()); - in.close(); + try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { + byte[] data0 = new byte[512]; + Arrays.fill(data0, (byte) 0xf3); + ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); + packHeader(pack, 4); + objectHeader(pack, Constants.OBJ_BLOB, data0.length); + deflate(pack, data0); + + byte[] data1 = clone(0x01, data0); + byte[] delta1 = delta(data0, data1); + ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length); + id0.copyRawTo(pack); + deflate(pack, delta1); + + byte[] data2 = clone(0x02, data1); + byte[] delta2 = delta(data1, data2); + ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length); + id1.copyRawTo(pack); + deflate(pack, delta2); + + byte[] data3 = clone(0x03, data2); + byte[] delta3 = delta(data2, data3); + ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length); + id2.copyRawTo(pack); + deflate(pack, delta3); + + digest(pack); + PackParser ip = index(pack.toByteArray()); + ip.setAllowThin(true); + ip.parse(NullProgressMonitor.INSTANCE); + + assertTrue("has blob", wc.has(id3)); + + ObjectLoader ol = wc.open(id3); + assertNotNull("created loader", ol); + assertEquals(Constants.OBJ_BLOB, ol.getType()); + assertEquals(data3.length, ol.getSize()); + assertFalse("is large", ol.isLarge()); + assertNotNull(ol.getCachedBytes()); + assertArrayEquals(data3, ol.getCachedBytes()); + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(Constants.OBJ_BLOB, in.getType()); + assertEquals(data3.length, in.getSize()); + byte[] act = new byte[data3.length]; + IO.readFully(in, act, 0, data3.length); + assertTrue("same content", Arrays.equals(act, data3)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } } @Test public void testDelta_FailsOver2GiB() throws Exception { - ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); - byte[] base = new byte[] { 'a' }; - ObjectId idA = fmt.idFor(Constants.OBJ_BLOB, base); - ObjectId idB = fmt.idFor(Constants.OBJ_BLOB, new byte[] { 'b' }); - - PackedObjectInfo a = new PackedObjectInfo(idA); - PackedObjectInfo b = new PackedObjectInfo(idB); - - TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); - packHeader(pack, 2); - a.setOffset(pack.length()); - objectHeader(pack, Constants.OBJ_BLOB, base.length); - deflate(pack, base); - - ByteArrayOutputStream tmp = new ByteArrayOutputStream(); - DeltaEncoder de = new DeltaEncoder(tmp, base.length, 3L << 30); - de.copy(0, 1); - byte[] delta = tmp.toByteArray(); - b.setOffset(pack.length()); - objectHeader(pack, Constants.OBJ_REF_DELTA, delta.length); - idA.copyRawTo(pack); - deflate(pack, delta); - byte[] footer = digest(pack); - - File dir = new File(repo.getObjectDatabase().getDirectory(), "pack"); - File packName = new File(dir, idA.name() + ".pack"); - File idxName = new File(dir, idA.name() + ".idx"); - - FileOutputStream f = new FileOutputStream(packName); - try { - f.write(pack.toByteArray()); - } finally { - f.close(); + try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { + byte[] base = new byte[] { 'a' }; + ObjectId idA = fmt.idFor(Constants.OBJ_BLOB, base); + ObjectId idB = fmt.idFor(Constants.OBJ_BLOB, new byte[] { 'b' }); + + PackedObjectInfo a = new PackedObjectInfo(idA); + PackedObjectInfo b = new PackedObjectInfo(idB); + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); + packHeader(pack, 2); + a.setOffset(pack.length()); + objectHeader(pack, Constants.OBJ_BLOB, base.length); + deflate(pack, base); + + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + DeltaEncoder de = new DeltaEncoder(tmp, base.length, 3L << 30); + de.copy(0, 1); + byte[] delta = tmp.toByteArray(); + b.setOffset(pack.length()); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta.length); + idA.copyRawTo(pack); + deflate(pack, delta); + byte[] footer = digest(pack); + + File dir = new File(repo.getObjectDatabase().getDirectory(), + "pack"); + File packName = new File(dir, idA.name() + ".pack"); + File idxName = new File(dir, idA.name() + ".idx"); + + FileOutputStream f = new FileOutputStream(packName); + try { + f.write(pack.toByteArray()); + } finally { + f.close(); + } + + f = new FileOutputStream(idxName); + try { + List list = new ArrayList<>(); + list.add(a); + list.add(b); + Collections.sort(list); + new PackIndexWriterV1(f).write(list, footer); + } finally { + f.close(); + } + + PackFile packFile = new PackFile(packName, PackExt.INDEX.getBit()); + try { + packFile.get(wc, b); + fail("expected LargeObjectException.ExceedsByteArrayLimit"); + } catch (LargeObjectException.ExceedsByteArrayLimit bad) { + assertNull(bad.getObjectId()); + } finally { + packFile.close(); + } } + } - f = new FileOutputStream(idxName); - try { - List list = new ArrayList(); - list.add(a); - list.add(b); - Collections.sort(list); - new PackIndexWriterV1(f).write(list, footer); - } finally { - f.close(); - } + @Test + public void testConfigurableStreamFileThreshold() throws Exception { + byte[] data = getRng().nextBytes(300); + RevBlob id = tr.blob(data); + tr.branch("master").commit().add("A", id).create(); + tr.packAndPrune(); + assertTrue("has blob", wc.has(id)); - PackFile packFile = new PackFile(packName, PackExt.INDEX.getBit()); - try { - packFile.get(wc, b); - fail("expected LargeObjectException.ExceedsByteArrayLimit"); - } catch (LargeObjectException.ExceedsByteArrayLimit bad) { - assertNull(bad.getObjectId()); - } finally { - packFile.close(); - } + ObjectLoader ol = wc.open(id); + ObjectStream in = ol.openStream(); + assertTrue(in instanceof ObjectStream.SmallStream); + assertEquals(300, in.available()); + in.close(); + + wc.setStreamFileThreshold(299); + ol = wc.open(id); + in = ol.openStream(); + assertTrue(in instanceof ObjectStream.Filter); + assertEquals(1, in.available()); } private static byte[] clone(int first, byte[] base) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,6 @@ import java.util.NoSuchElementException; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.junit.RepositoryTestCase; import org.junit.Test; @@ -63,6 +62,7 @@ PackIndex denseIdx; + @Override public void setUp() throws Exception { super.setUp(); smallIdx = PackIndex.open(getFileForPack34be9032()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2017, 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.internal.storage.file; + +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.junit.RepositoryTestCase; +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.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.storage.file.WindowCacheConfig; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.util.IO; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("boxing") +public class PackInserterTest extends RepositoryTestCase { + private WindowCacheConfig origWindowCacheConfig; + + private static final Random random = new Random(0); + + @Before + public void setWindowCacheConfig() { + origWindowCacheConfig = new WindowCacheConfig(); + origWindowCacheConfig.install(); + } + + @After + public void resetWindowCacheConfig() { + origWindowCacheConfig.install(); + } + + @Before + public void emptyAtSetUp() throws Exception { + assertEquals(0, listPacks().size()); + assertNoObjects(); + } + + @Test + public void noFlush() throws Exception { + try (PackInserter ins = newInserter()) { + ins.insert(OBJ_BLOB, Constants.encode("foo contents")); + // No flush. + } + assertNoObjects(); + } + + @Test + public void flushEmptyPack() throws Exception { + try (PackInserter ins = newInserter()) { + ins.flush(); + } + assertNoObjects(); + } + + @Test + public void singlePack() throws Exception { + ObjectId blobId; + byte[] blob = Constants.encode("foo contents"); + ObjectId treeId; + ObjectId commitId; + byte[] commit; + try (PackInserter ins = newInserter()) { + blobId = ins.insert(OBJ_BLOB, blob); + + DirCache dc = DirCache.newInCore(); + DirCacheBuilder b = dc.builder(); + DirCacheEntry dce = new DirCacheEntry("foo"); + dce.setFileMode(FileMode.REGULAR_FILE); + dce.setObjectId(blobId); + b.add(dce); + b.finish(); + treeId = dc.writeTree(ins); + + CommitBuilder cb = new CommitBuilder(); + cb.setTreeId(treeId); + cb.setAuthor(author); + cb.setCommitter(committer); + cb.setMessage("Commit message"); + commit = cb.toByteArray(); + commitId = ins.insert(cb); + ins.flush(); + } + + assertPacksOnly(); + List packs = listPacks(); + assertEquals(1, packs.size()); + assertEquals(3, packs.get(0).getObjectCount()); + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId, blob); + + CanonicalTreeParser treeParser = + new CanonicalTreeParser(null, reader, treeId); + assertEquals("foo", treeParser.getEntryPathString()); + assertEquals(blobId, treeParser.getEntryObjectId()); + + ObjectLoader commitLoader = reader.open(commitId); + assertEquals(OBJ_COMMIT, commitLoader.getType()); + assertArrayEquals(commit, commitLoader.getBytes()); + } + } + + @Test + public void multiplePacks() throws Exception { + ObjectId blobId1; + ObjectId blobId2; + byte[] blob1 = Constants.encode("blob1"); + byte[] blob2 = Constants.encode("blob2"); + + try (PackInserter ins = newInserter()) { + blobId1 = ins.insert(OBJ_BLOB, blob1); + ins.flush(); + blobId2 = ins.insert(OBJ_BLOB, blob2); + ins.flush(); + } + + assertPacksOnly(); + List packs = listPacks(); + assertEquals(2, packs.size()); + assertEquals(1, packs.get(0).getObjectCount()); + assertEquals(1, packs.get(1).getObjectCount()); + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId1, blob1); + assertBlob(reader, blobId2, blob2); + } + } + + @Test + public void largeBlob() throws Exception { + ObjectId blobId; + byte[] blob = newLargeBlob(); + try (PackInserter ins = newInserter()) { + assertThat(blob.length, greaterThan(ins.getBufferSize())); + blobId = + ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)); + ins.flush(); + } + + assertPacksOnly(); + Collection packs = listPacks(); + assertEquals(1, packs.size()); + PackFile p = packs.iterator().next(); + assertEquals(1, p.getObjectCount()); + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId, blob); + } + } + + @Test + public void overwriteExistingPack() throws Exception { + ObjectId blobId; + byte[] blob = Constants.encode("foo contents"); + + try (PackInserter ins = newInserter()) { + blobId = ins.insert(OBJ_BLOB, blob); + ins.flush(); + } + + assertPacksOnly(); + List packs = listPacks(); + assertEquals(1, packs.size()); + PackFile pack = packs.get(0); + assertEquals(1, pack.getObjectCount()); + + String inode = getInode(pack.getPackFile()); + + try (PackInserter ins = newInserter()) { + ins.checkExisting(false); + assertEquals(blobId, ins.insert(OBJ_BLOB, blob)); + ins.flush(); + } + + assertPacksOnly(); + packs = listPacks(); + assertEquals(1, packs.size()); + pack = packs.get(0); + assertEquals(1, pack.getObjectCount()); + + if (inode != null) { + // Old file was overwritten with new file, although objects were + // equivalent. + assertNotEquals(inode, getInode(pack.getPackFile())); + } + } + + @Test + public void checkExisting() throws Exception { + ObjectId blobId; + byte[] blob = Constants.encode("foo contents"); + + try (PackInserter ins = newInserter()) { + blobId = ins.insert(OBJ_BLOB, blob); + ins.insert(OBJ_BLOB, Constants.encode("another blob")); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(1, listPacks().size()); + + try (PackInserter ins = newInserter()) { + assertEquals(blobId, ins.insert(OBJ_BLOB, blob)); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(1, listPacks().size()); + + try (PackInserter ins = newInserter()) { + ins.checkExisting(false); + assertEquals(blobId, ins.insert(OBJ_BLOB, blob)); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(2, listPacks().size()); + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId, blob); + } + } + + @Test + public void insertSmallInputStreamRespectsCheckExisting() throws Exception { + ObjectId blobId; + byte[] blob = Constants.encode("foo contents"); + try (PackInserter ins = newInserter()) { + assertThat(blob.length, lessThan(ins.getBufferSize())); + blobId = ins.insert(OBJ_BLOB, blob); + ins.insert(OBJ_BLOB, Constants.encode("another blob")); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(1, listPacks().size()); + + try (PackInserter ins = newInserter()) { + assertEquals(blobId, + ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob))); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(1, listPacks().size()); + } + + @Test + public void insertLargeInputStreamBypassesCheckExisting() throws Exception { + ObjectId blobId; + byte[] blob = newLargeBlob(); + + try (PackInserter ins = newInserter()) { + assertThat(blob.length, greaterThan(ins.getBufferSize())); + blobId = ins.insert(OBJ_BLOB, blob); + ins.insert(OBJ_BLOB, Constants.encode("another blob")); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(1, listPacks().size()); + + try (PackInserter ins = newInserter()) { + assertEquals(blobId, + ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob))); + ins.flush(); + } + + assertPacksOnly(); + assertEquals(2, listPacks().size()); + } + + @Test + public void readBackSmallFiles() throws Exception { + ObjectId blobId1; + ObjectId blobId2; + ObjectId blobId3; + byte[] blob1 = Constants.encode("blob1"); + byte[] blob2 = Constants.encode("blob2"); + byte[] blob3 = Constants.encode("blob3"); + try (PackInserter ins = newInserter()) { + assertThat(blob1.length, lessThan(ins.getBufferSize())); + blobId1 = ins.insert(OBJ_BLOB, blob1); + + try (ObjectReader reader = ins.newReader()) { + assertBlob(reader, blobId1, blob1); + } + + // Read-back should not mess up the file pointer. + blobId2 = ins.insert(OBJ_BLOB, blob2); + ins.flush(); + + blobId3 = ins.insert(OBJ_BLOB, blob3); + } + + assertPacksOnly(); + List packs = listPacks(); + assertEquals(1, packs.size()); + assertEquals(2, packs.get(0).getObjectCount()); + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId1, blob1); + assertBlob(reader, blobId2, blob2); + + try { + reader.open(blobId3); + fail("Expected MissingObjectException"); + } catch (MissingObjectException expected) { + // Expected. + } + } + } + + @Test + public void readBackLargeFile() throws Exception { + ObjectId blobId; + byte[] blob = newLargeBlob(); + + WindowCacheConfig wcc = new WindowCacheConfig(); + wcc.setStreamFileThreshold(1024); + wcc.install(); + try (ObjectReader reader = db.newObjectReader()) { + assertThat(blob.length, greaterThan(reader.getStreamFileThreshold())); + } + + try (PackInserter ins = newInserter()) { + blobId = ins.insert(OBJ_BLOB, blob); + + try (ObjectReader reader = ins.newReader()) { + // Double-check threshold is propagated. + assertThat(blob.length, greaterThan(reader.getStreamFileThreshold())); + assertBlob(reader, blobId, blob); + } + } + + assertPacksOnly(); + // Pack was streamed out to disk and read back from the temp file, but + // ultimately rolled back and deleted. + assertEquals(0, listPacks().size()); + + try (ObjectReader reader = db.newObjectReader()) { + try { + reader.open(blobId); + fail("Expected MissingObjectException"); + } catch (MissingObjectException expected) { + // Expected. + } + } + } + + @Test + public void readBackFallsBackToRepo() throws Exception { + ObjectId blobId; + byte[] blob = Constants.encode("foo contents"); + try (PackInserter ins = newInserter()) { + assertThat(blob.length, lessThan(ins.getBufferSize())); + blobId = ins.insert(OBJ_BLOB, blob); + ins.flush(); + } + + try (PackInserter ins = newInserter(); + ObjectReader reader = ins.newReader()) { + assertBlob(reader, blobId, blob); + } + } + + @Test + public void readBackSmallObjectBeforeLargeObject() throws Exception { + WindowCacheConfig wcc = new WindowCacheConfig(); + wcc.setStreamFileThreshold(1024); + wcc.install(); + + ObjectId blobId1; + ObjectId blobId2; + ObjectId largeId; + byte[] blob1 = Constants.encode("blob1"); + byte[] blob2 = Constants.encode("blob2"); + byte[] largeBlob = newLargeBlob(); + try (PackInserter ins = newInserter()) { + assertThat(blob1.length, lessThan(ins.getBufferSize())); + assertThat(largeBlob.length, greaterThan(ins.getBufferSize())); + + blobId1 = ins.insert(OBJ_BLOB, blob1); + largeId = ins.insert(OBJ_BLOB, largeBlob); + + try (ObjectReader reader = ins.newReader()) { + // A previous bug did not reset the file pointer to EOF after reading + // back. We need to seek to something further back than a full buffer, + // since the read-back code eagerly reads a full buffer's worth of data + // from the file to pass to the inflater. If we seeked back just a small + // amount, this step would consume the rest of the file, so the file + // pointer would coincidentally end up back at EOF, hiding the bug. + assertBlob(reader, blobId1, blob1); + } + + blobId2 = ins.insert(OBJ_BLOB, blob2); + + try (ObjectReader reader = ins.newReader()) { + assertBlob(reader, blobId1, blob1); + assertBlob(reader, blobId2, blob2); + assertBlob(reader, largeId, largeBlob); + } + + ins.flush(); + } + + try (ObjectReader reader = db.newObjectReader()) { + assertBlob(reader, blobId1, blob1); + assertBlob(reader, blobId2, blob2); + assertBlob(reader, largeId, largeBlob); + } + } + + private List listPacks() throws Exception { + List fromOpenDb = listPacks(db); + List reopened; + try (FileRepository db2 = new FileRepository(db.getDirectory())) { + reopened = listPacks(db2); + } + assertEquals(fromOpenDb.size(), reopened.size()); + for (int i = 0 ; i < fromOpenDb.size(); i++) { + PackFile a = fromOpenDb.get(i); + PackFile b = reopened.get(i); + assertEquals(a.getPackName(), b.getPackName()); + assertEquals( + a.getPackFile().getAbsolutePath(), b.getPackFile().getAbsolutePath()); + assertEquals(a.getObjectCount(), b.getObjectCount()); + a.getObjectCount(); + } + return fromOpenDb; + } + + private static List listPacks(FileRepository db) throws Exception { + return db.getObjectDatabase().getPacks().stream() + .sorted(comparing(PackFile::getPackName)).collect(toList()); + } + + private PackInserter newInserter() { + return db.getObjectDatabase().newPackInserter(); + } + + private static byte[] newLargeBlob() { + byte[] blob = new byte[10240]; + random.nextBytes(blob); + return blob; + } + + private static String getInode(File f) throws Exception { + BasicFileAttributes attrs = Files.readAttributes( + f.toPath(), BasicFileAttributes.class); + Object k = attrs.fileKey(); + if (k == null) { + return null; + } + Pattern p = Pattern.compile("^\\(dev=[^,]*,ino=(\\d+)\\)$"); + Matcher m = p.matcher(k.toString()); + return m.matches() ? m.group(1) : null; + } + + private static void assertBlob(ObjectReader reader, ObjectId id, + byte[] expected) throws Exception { + ObjectLoader loader = reader.open(id); + assertEquals(OBJ_BLOB, loader.getType()); + assertEquals(expected.length, loader.getSize()); + try (ObjectStream s = loader.openStream()) { + int n = (int) s.getSize(); + byte[] actual = new byte[n]; + assertEquals(n, IO.readFully(s, actual, 0)); + assertArrayEquals(expected, actual); + } + } + + private void assertPacksOnly() throws Exception { + new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx")) + .assertNoBadFiles(db.getObjectDatabase().getDirectory()); + } + + private void assertNoObjects() throws Exception { + new BadFileCollector(f -> true) + .assertNoBadFiles(db.getObjectDatabase().getDirectory()); + } + + private static class BadFileCollector extends SimpleFileVisitor { + private final Predicate badName; + private List bad; + + BadFileCollector(Predicate badName) { + this.badName = badName; + } + + void assertNoBadFiles(File f) throws IOException { + bad = new ArrayList<>(); + Files.walkFileTree(f.toPath(), this); + if (!bad.isEmpty()) { + fail("unexpected files in object directory: " + bad); + } + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + Path fileName = file.getFileName(); + if (fileName != null) { + String name = fileName.toString(); + if (!attrs.isDirectory() && badName.test(name)) { + bad.add(name); + } + } + return FileVisitResult.CONTINUE; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,8 +50,6 @@ import static org.junit.Assert.fail; import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.internal.storage.file.PackIndex; -import org.eclipse.jgit.internal.storage.file.PackReverseIndex; import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -67,6 +65,7 @@ /** * Set up tested class instance, test constructor by the way. */ + @Override @Before public void setUp() throws Exception { super.setUp(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.internal.storage.file; +import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -66,19 +68,23 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.internal.storage.pack.PackWriter; -import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; -import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Sets; +import org.eclipse.jgit.revwalk.DepthWalk; +import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; 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.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.transport.PackParser; import org.junit.After; @@ -87,12 +93,12 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { - private static final Set EMPTY_SET_OBJECT = Collections - . emptySet(); - private static final List EMPTY_LIST_REVS = Collections . emptyList(); + private static final Set EMPTY_ID_SET = Collections + . emptySet(); + private PackConfig config; private PackWriter writer; @@ -105,6 +111,27 @@ private FileRepository dst; + private RevBlob contentA; + + private RevBlob contentB; + + private RevBlob contentC; + + private RevBlob contentD; + + private RevBlob contentE; + + private RevCommit c1; + + private RevCommit c2; + + private RevCommit c3; + + private RevCommit c4; + + private RevCommit c5; + + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -117,6 +144,7 @@ write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n"); } + @Override @After public void tearDown() throws Exception { if (writer != null) { @@ -170,7 +198,7 @@ */ @Test public void testWriteEmptyPack1() throws IOException { - createVerifyOpenPack(EMPTY_SET_OBJECT, EMPTY_SET_OBJECT, false, false); + createVerifyOpenPack(NONE, NONE, false, false); assertEquals(0, writer.getObjectCount()); assertEquals(0, pack.getObjectCount()); @@ -203,8 +231,7 @@ final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); try { - createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton( - nonExisting), false, false); + createVerifyOpenPack(NONE, haves(nonExisting), false, false); fail("Should have thrown MissingObjectException"); } catch (MissingObjectException x) { // expected @@ -220,8 +247,7 @@ public void testIgnoreNonExistingObjects() throws IOException { final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); - createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton( - nonExisting), false, true); + createVerifyOpenPack(NONE, haves(nonExisting), false, true); // shouldn't throw anything } @@ -239,8 +265,7 @@ final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); new GC(db).gc(); - createVerifyOpenPack(EMPTY_SET_OBJECT, - Collections.singleton(nonExisting), false, true, true); + createVerifyOpenPack(NONE, haves(nonExisting), false, true, true); // shouldn't throw anything } @@ -312,7 +337,7 @@ */ @Test public void testWritePack2DeltasCRC32Copy() throws IOException { - final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); + final File packDir = db.getObjectDatabase().getPackDirectory(); final File crc32Pack = new File(packDir, "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); final File crc32Idx = new File(packDir, @@ -341,14 +366,15 @@ ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; - final RevWalk parser = new RevWalk(db); - final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length]; - for (int i = 0; i < forcedOrder.length; i++) - forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]); + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") , + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; + try (final RevWalk parser = new RevWalk(db)) { + final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length]; + for (int i = 0; i < forcedOrder.length; i++) + forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]); - createVerifyOpenPack(Arrays.asList(forcedOrderRevs)); + createVerifyOpenPack(Arrays.asList(forcedOrderRevs)); + } assertEquals(forcedOrder.length, writer.getObjectCount()); verifyObjectsOrder(forcedOrder); @@ -388,6 +414,8 @@ */ @Test public void testWritePack2SizeDeltasVsNoDeltas() throws Exception { + config.setReuseDeltas(false); + config.setDeltaCompress(false); testWritePack2(); final long sizePack2NoDeltas = os.size(); tearDown(); @@ -438,6 +466,38 @@ } @Test + public void testDeltaStatistics() throws Exception { + config.setDeltaCompress(true); + FileRepository repo = createBareRepository(); + TestRepository testRepo = new TestRepository<>(repo); + ArrayList blobs = new ArrayList<>(); + blobs.add(testRepo.blob(genDeltableData(1000))); + blobs.add(testRepo.blob(genDeltableData(1005))); + + try (PackWriter pw = new PackWriter(repo)) { + NullProgressMonitor m = NullProgressMonitor.INSTANCE; + pw.preparePack(blobs.iterator()); + pw.writePack(m, m, os); + PackStatistics stats = pw.getStatistics(); + assertEquals(1, stats.getTotalDeltas()); + assertTrue("Delta bytes not set.", + stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0); + } + } + + // Generate consistent junk data for building files that delta well + private String genDeltableData(int length) { + assertTrue("Generated data must have a length > 0", length > 0); + char[] data = {'a', 'b', 'c', '\n'}; + StringBuilder builder = new StringBuilder(length); + for (int i = 0; i < length; i++) { + builder.append(data[i % 4]); + } + return builder.toString(); + } + + + @Test public void testWriteIndex() throws Exception { config.setIndexVersion(2); writeVerifyPack4(false); @@ -478,23 +538,21 @@ public void testExclude() throws Exception { FileRepository repo = createBareRepository(); - TestRepository testRepo = new TestRepository( + TestRepository testRepo = new TestRepository<>( repo); BranchBuilder bb = testRepo.branch("refs/heads/master"); - RevBlob contentA = testRepo.blob("A"); - RevCommit c1 = bb.commit().add("f", contentA).create(); + contentA = testRepo.blob("A"); + c1 = bb.commit().add("f", contentA).create(); testRepo.getRevWalk().parseHeaders(c1); - PackIndex pf1 = writePack(repo, Collections.singleton(c1), - Collections. emptySet()); + PackIndex pf1 = writePack(repo, wants(c1), EMPTY_ID_SET); assertContent( pf1, Arrays.asList(c1.getId(), c1.getTree().getId(), contentA.getId())); - RevBlob contentB = testRepo.blob("B"); - RevCommit c2 = bb.commit().add("f", contentB).create(); + contentB = testRepo.blob("B"); + c2 = bb.commit().add("f", contentB).create(); testRepo.getRevWalk().parseHeaders(c2); - PackIndex pf2 = writePack(repo, Collections.singleton(c2), - Collections.singleton(objectIdSet(pf1))); + PackIndex pf2 = writePack(repo, wants(c2), Sets.of((ObjectIdSet) pf1)); assertContent( pf2, Arrays.asList(c2.getId(), c2.getTree().getId(), @@ -511,18 +569,151 @@ expected.contains(pi.getObjectId(i))); } + @Test + public void testShallowIsMinimalDepth1() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 1, wants(c2), NONE, NONE); + assertContent(idx, Arrays.asList(c2.getId(), c2.getTree().getId(), + contentA.getId(), contentB.getId())); + + // Client already has blobs A and B, verify those are not packed. + idx = writeShallowPack(repo, 1, wants(c5), haves(c2), shallows(c2)); + assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(), + contentC.getId(), contentD.getId(), contentE.getId())); + } + + @Test + public void testShallowIsMinimalDepth2() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 2, wants(c2), NONE, NONE); + assertContent(idx, + Arrays.asList(c1.getId(), c2.getId(), c1.getTree().getId(), + c2.getTree().getId(), contentA.getId(), + contentB.getId())); + + // Client already has blobs A and B, verify those are not packed. + idx = writeShallowPack(repo, 2, wants(c5), haves(c1, c2), shallows(c1)); + assertContent(idx, + Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(), + c5.getTree().getId(), contentC.getId(), + contentD.getId(), contentE.getId())); + } + + @Test + public void testShallowFetchShallowParentDepth1() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE); + assertContent(idx, + Arrays.asList(c5.getId(), c5.getTree().getId(), + contentA.getId(), contentB.getId(), contentC.getId(), + contentD.getId(), contentE.getId())); + + idx = writeShallowPack(repo, 1, wants(c4), haves(c5), shallows(c5)); + assertContent(idx, Arrays.asList(c4.getId(), c4.getTree().getId())); + } + + @Test + public void testShallowFetchShallowParentDepth2() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE); + assertContent(idx, + Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(), + c5.getTree().getId(), contentA.getId(), + contentB.getId(), contentC.getId(), contentD.getId(), + contentE.getId())); + + idx = writeShallowPack(repo, 2, wants(c3), haves(c4, c5), shallows(c4)); + assertContent(idx, Arrays.asList(c2.getId(), c3.getId(), + c2.getTree().getId(), c3.getTree().getId())); + } + + @Test + public void testShallowFetchShallowAncestorDepth1() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE); + assertContent(idx, + Arrays.asList(c5.getId(), c5.getTree().getId(), + contentA.getId(), contentB.getId(), contentC.getId(), + contentD.getId(), contentE.getId())); + + idx = writeShallowPack(repo, 1, wants(c3), haves(c5), shallows(c5)); + assertContent(idx, Arrays.asList(c3.getId(), c3.getTree().getId())); + } + + @Test + public void testShallowFetchShallowAncestorDepth2() throws Exception { + FileRepository repo = setupRepoForShallowFetch(); + + PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE); + assertContent(idx, + Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(), + c5.getTree().getId(), contentA.getId(), + contentB.getId(), contentC.getId(), contentD.getId(), + contentE.getId())); + + idx = writeShallowPack(repo, 2, wants(c2), haves(c4, c5), shallows(c4)); + assertContent(idx, Arrays.asList(c1.getId(), c2.getId(), + c1.getTree().getId(), c2.getTree().getId())); + } + + private FileRepository setupRepoForShallowFetch() throws Exception { + FileRepository repo = createBareRepository(); + TestRepository r = new TestRepository<>(repo); + BranchBuilder bb = r.branch("refs/heads/master"); + contentA = r.blob("A"); + contentB = r.blob("B"); + contentC = r.blob("C"); + contentD = r.blob("D"); + contentE = r.blob("E"); + c1 = bb.commit().add("a", contentA).create(); + c2 = bb.commit().add("b", contentB).create(); + c3 = bb.commit().add("c", contentC).create(); + c4 = bb.commit().add("d", contentD).create(); + c5 = bb.commit().add("e", contentE).create(); + r.getRevWalk().parseHeaders(c5); // fully initialize the tip RevCommit + return repo; + } + private static PackIndex writePack(FileRepository repo, Set want, Set excludeObjects) - throws IOException { + throws IOException { + RevWalk walk = new RevWalk(repo); + return writePack(repo, walk, 0, want, NONE, excludeObjects); + } + + private static PackIndex writeShallowPack(FileRepository repo, int depth, + Set want, Set have, + Set shallow) throws IOException { + // During negotiation, UploadPack would have set up a DepthWalk and + // marked the client's "shallow" commits. Emulate that here. + DepthWalk.RevWalk walk = new DepthWalk.RevWalk(repo, depth - 1); + walk.assumeShallow(shallow); + return writePack(repo, walk, depth, want, have, EMPTY_ID_SET); + } + + private static PackIndex writePack(FileRepository repo, RevWalk walk, + int depth, Set want, + Set have, Set excludeObjects) + throws IOException { try (PackWriter pw = new PackWriter(repo)) { pw.setDeltaBaseAsOffset(true); pw.setReuseDeltaCommits(false); - for (ObjectIdSet idx : excludeObjects) + for (ObjectIdSet idx : excludeObjects) { pw.excludeObjects(idx); - pw.preparePack(NullProgressMonitor.INSTANCE, want, - Collections. emptySet()); + } + if (depth > 0) { + pw.setShallowPack(depth, null); + } + ObjectWalk ow = walk.toObjectWalkWithSameObjects(); + + pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have, NONE); String id = pw.computeName().getName(); - File packdir = new File(repo.getObjectsDirectory(), "pack"); + File packdir = repo.getObjectDatabase().getPackDirectory(); File packFile = new File(packdir, "pack-" + id + ".pack"); FileOutputStream packOS = new FileOutputStream(packFile); pw.writePack(NullProgressMonitor.INSTANCE, @@ -540,10 +731,10 @@ // TODO: testWritePackDeltasDepth() private void writeVerifyPack1() throws IOException { - final HashSet interestings = new HashSet(); + final HashSet interestings = new HashSet<>(); interestings.add(ObjectId .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - createVerifyOpenPack(interestings, EMPTY_SET_OBJECT, false, false); + createVerifyOpenPack(interestings, NONE, false, false); final ObjectId expectedOrder[] = new ObjectId[] { ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), @@ -552,8 +743,8 @@ ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; assertEquals(expectedOrder.length, writer.getObjectCount()); verifyObjectsOrder(expectedOrder); @@ -563,10 +754,10 @@ private void writeVerifyPack2(boolean deltaReuse) throws IOException { config.setReuseDeltas(deltaReuse); - final HashSet interestings = new HashSet(); + final HashSet interestings = new HashSet<>(); interestings.add(ObjectId .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - final HashSet uninterestings = new HashSet(); + final HashSet uninterestings = new HashSet<>(); uninterestings.add(ObjectId .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab")); createVerifyOpenPack(interestings, uninterestings, false, false); @@ -576,13 +767,11 @@ ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; - if (deltaReuse) { - // objects order influenced (swapped) by delta-base first rule - ObjectId temp = expectedOrder[4]; - expectedOrder[4] = expectedOrder[5]; - expectedOrder[5] = temp; + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") , + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; + if (!config.isReuseDeltas() && !config.isDeltaCompress()) { + // If no deltas are in the file the final two entries swap places. + swap(expectedOrder, 4, 5); } assertEquals(expectedOrder.length, writer.getObjectCount()); verifyObjectsOrder(expectedOrder); @@ -590,11 +779,17 @@ .computeName().name()); } + private static void swap(ObjectId[] arr, int a, int b) { + ObjectId tmp = arr[a]; + arr[a] = arr[b]; + arr[b] = tmp; + } + private void writeVerifyPack4(final boolean thin) throws IOException { - final HashSet interestings = new HashSet(); + final HashSet interestings = new HashSet<>(); interestings.add(ObjectId .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - final HashSet uninterestings = new HashSet(); + final HashSet uninterestings = new HashSet<>(); uninterestings.add(ObjectId .fromString("c59759f143fb1fe21c197981df75a7ee00290799")); createVerifyOpenPack(interestings, uninterestings, thin, false); @@ -683,12 +878,13 @@ } private void verifyObjectsOrder(final ObjectId objectsOrder[]) { - final List entries = new ArrayList(); + final List entries = new ArrayList<>(); for (MutableEntry me : pack) { entries.add(me.cloneEntry()); } Collections.sort(entries, new Comparator() { + @Override public int compare(MutableEntry o1, MutableEntry o2) { return Long.signum(o1.getOffset() - o2.getOffset()); } @@ -700,11 +896,15 @@ } } - private static ObjectIdSet objectIdSet(final PackIndex idx) { - return new ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return idx.hasObject(objectId); - } - }; + private static Set haves(ObjectId... objects) { + return Sets.of(objects); + } + + private static Set wants(ObjectId... objects) { + return Sets.of(objects); + } + + private static Set shallows(ObjectId... objects) { + return Sets.of(objects); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,32 +61,27 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.events.RefsChangedEvent; import org.eclipse.jgit.events.RefsChangedListener; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.BatchRefUpdate; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.ReceiveCommand; -import org.eclipse.jgit.transport.ReceiveCommand.Type; import org.junit.Before; import org.junit.Test; +@SuppressWarnings("boxing") public class RefDirectoryTest extends LocalDiskRepositoryTestCase { private Repository diskRepo; @@ -100,6 +95,7 @@ private RevTag v1_0; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -107,7 +103,7 @@ diskRepo = createBareRepository(); refdir = (RefDirectory) diskRepo.getRefDatabase(); - repo = new TestRepository(diskRepo); + repo = new TestRepository<>(diskRepo); A = repo.commit().create(); B = repo.commit(repo.getRevWalk().parseCommit(A)); v1_0 = repo.tag("v1_0", B); @@ -547,6 +543,7 @@ ListenerHandle listener = Repository.getGlobalListenerList() .addRefsChangedListener(new RefsChangedListener() { + @Override public void onRefsChanged(RefsChangedEvent event) { count[0]++; } @@ -858,6 +855,36 @@ } @Test + public void testGetRef_CycleInSymbolicRef() throws IOException { + Ref r; + + writeLooseRef("refs/1", "ref: refs/2\n"); + writeLooseRef("refs/2", "ref: refs/3\n"); + writeLooseRef("refs/3", "ref: refs/4\n"); + writeLooseRef("refs/4", "ref: refs/5\n"); + writeLooseRef("refs/5", "ref: refs/end\n"); + writeLooseRef("refs/end", A); + + r = refdir.getRef("1"); + assertEquals("refs/1", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + writeLooseRef("refs/5", "ref: refs/6\n"); + writeLooseRef("refs/6", "ref: refs/end\n"); + + r = refdir.getRef("1"); + assertNull("missing 1 due to cycle", r); + + writeLooseRef("refs/heads/1", B); + + r = refdir.getRef("1"); + assertEquals("refs/heads/1", r.getName()); + assertEquals(B, r.getObjectId()); + assertFalse(r.isSymbolic()); + } + + @Test public void testGetRefs_PackedNotPeeled_Sorted() throws IOException { Map all; @@ -991,7 +1018,7 @@ assertEquals(v0_1.getId(), all.get("refs/tags/v0.1").getObjectId()); all = refdir.getRefs(RefDatabase.ALL); - refdir.pack(new ArrayList(all.keySet())); + refdir.pack(new ArrayList<>(all.keySet())); all = refdir.getRefs(RefDatabase.ALL); assertEquals(5, all.size()); @@ -1235,12 +1262,13 @@ final RefDatabase refDb = newRepo.getRefDatabase(); File packedRefs = new File(newRepo.getDirectory(), "packed-refs"); assertTrue(packedRefs.createNewFile()); - final AtomicReference error = new AtomicReference(); - final AtomicReference exception = new AtomicReference(); + final AtomicReference error = new AtomicReference<>(); + final AtomicReference exception = new AtomicReference<>(); final AtomicInteger changeCount = new AtomicInteger(); newRepo.getListenerList().addRefsChangedListener( new RefsChangedListener() { + @Override public void onRefsChanged(RefsChangedEvent event) { try { refDb.getRefs("ref"); @@ -1260,125 +1288,20 @@ } @Test - public void testBatchRefUpdateSimpleNoForce() throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - newCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - newCommand(B, A, "refs/heads/masters", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands - .get(1).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateSimpleForce() throws IOException { + public void testPackedRefsLockFailure() throws Exception { writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - newCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - newCommand(B, A, "refs/heads/masters", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck() - throws IOException { - writeLooseRef("refs/heads/master", B); - List commands = Arrays.asList( - newCommand(B, A, "refs/heads/master", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo) { - @Override - public boolean isMergedInto(RevCommit base, RevCommit tip) { - throw new AssertionError("isMergedInto() should not be called"); - } - }, new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefUpdateConflict() throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - newCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - newCommand(null, A, "refs/heads/master/x", - ReceiveCommand.Type.CREATE), - newCommand(null, A, "refs/heads", ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate - .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1) - .getResult()); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2) - .getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateConflictThanksToDelete() throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - newCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - newCommand(null, A, "refs/heads/masters/x", - ReceiveCommand.Type.CREATE), - newCommand(B, null, "refs/heads/masters", - ReceiveCommand.Type.DELETE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); - } - - private static ReceiveCommand newCommand(RevCommit a, RevCommit b, - String string, Type update) { - return new ReceiveCommand(a != null ? a.getId() : null, - b != null ? b.getId() : null, string, update); + refdir.setRetrySleepMs(Arrays.asList(0, 0)); + LockFile myLock = refdir.lockPackedRefs(); + try { + refdir.pack(Arrays.asList("refs/heads/master")); + fail("expected LockFailedException"); + } catch (LockFailedException e) { + assertEquals(refdir.packedRefsFile.getPath(), e.getFile().getPath()); + } finally { + myLock.unlock(); + } + Ref ref = refdir.getRef("refs/heads/master"); + assertEquals(Storage.LOOSE, ref.getStorage()); } private void writeLooseRef(String name, AnyObjectId id) throws IOException { @@ -1406,29 +1329,4 @@ File path = new File(diskRepo.getDirectory(), name); assertTrue("deleted " + name, path.delete()); } - - private static final class StrictWorkMonitor implements ProgressMonitor { - private int lastWork, totalWork; - - public void start(int totalTasks) { - // empty - } - - public void beginTask(String title, int totalWork) { - this.totalWork = totalWork; - lastWork = 0; - } - - public void update(int completed) { - lastWork += completed; - } - - public void endTask() { - assertEquals("Units of work recorded", totalWork, lastWork); - } - - public boolean isCancelled() { - return false; - } - } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -270,19 +270,16 @@ private void setupReflog(String logName, byte[] data) throws FileNotFoundException, IOException { - File logfile = new File(db.getDirectory(), logName); - if (!logfile.getParentFile().mkdirs() - && !logfile.getParentFile().isDirectory()) { - throw new IOException( - "oops, cannot create the directory for the test reflog file" - + logfile); - } - FileOutputStream fileOutputStream = new FileOutputStream(logfile); - try { - fileOutputStream.write(data); - } finally { - fileOutputStream.close(); - } - } + File logfile = new File(db.getDirectory(), logName); + if (!logfile.getParentFile().mkdirs() + && !logfile.getParentFile().isDirectory()) { + throw new IOException( + "oops, cannot create the directory for the test reflog file" + + logfile); + } + try (FileOutputStream fileOutputStream = new FileOutputStream(logfile)) { + fileOutputStream.write(data); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -61,7 +61,8 @@ @Test public void shouldFilterLineFeedFromMessage() throws Exception { - ReflogWriter writer = new ReflogWriter(db); + ReflogWriter writer = + new ReflogWriter((RefDirectory) db.getRefDatabase()); PersonIdent ident = new PersonIdent("John Doe", "john@doe.com", 1243028200000L, 120); ObjectId oldId = ObjectId @@ -86,11 +87,8 @@ "oops, cannot create the directory for the test reflog file" + logfile); } - FileInputStream fileInputStream = new FileInputStream(logfile); - try { + try (FileInputStream fileInputStream = new FileInputStream(logfile)) { fileInputStream.read(buffer); - } finally { - fileInputStream.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,9 @@ package org.eclipse.jgit.internal.storage.file; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.junit.Assert.assertEquals; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -64,6 +66,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefRename; @@ -104,9 +107,14 @@ private void delete(final RefUpdate ref, final Result expected, final boolean exists, final boolean removed) throws IOException { - assertEquals(exists, db.getAllRefs().containsKey(ref.getName())); + delete(db, ref, expected, exists, removed); + } + + private void delete(Repository repo, final RefUpdate ref, final Result expected, + final boolean exists, final boolean removed) throws IOException { + assertEquals(exists, repo.getAllRefs().containsKey(ref.getName())); assertEquals(expected, ref.delete()); - assertEquals(!removed, db.getAllRefs().containsKey(ref.getName())); + assertEquals(!removed, repo.getAllRefs().containsKey(ref.getName())); } @Test @@ -232,6 +240,76 @@ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); } + @Test + public void testDeleteHeadInBareRepo() throws IOException { + Repository bareRepo = createBareRepository(); + String master = "refs/heads/master"; + Ref head = bareRepo.exactRef(Constants.HEAD); + assertNotNull(head); + assertTrue(head.isSymbolic()); + assertEquals(master, head.getLeaf().getName()); + assertNull(head.getObjectId()); + assertNull(bareRepo.exactRef(master)); + + ObjectId blobId; + try (ObjectInserter ins = bareRepo.newObjectInserter()) { + blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8)); + ins.flush(); + } + + // Create master via HEAD, so we delete it. + RefUpdate ref = bareRepo.updateRef(Constants.HEAD); + ref.setNewObjectId(blobId); + assertEquals(Result.NEW, ref.update()); + + head = bareRepo.exactRef(Constants.HEAD); + assertTrue(head.isSymbolic()); + assertEquals(master, head.getLeaf().getName()); + assertEquals(blobId, head.getLeaf().getObjectId()); + assertEquals(blobId, bareRepo.exactRef(master).getObjectId()); + + // Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD + // back in a dangling state. + ref = bareRepo.updateRef(Constants.HEAD); + ref.setExpectedOldObjectId(blobId); + ref.setForceUpdate(true); + delete(bareRepo, ref, Result.FORCED, true, true); + + head = bareRepo.exactRef(Constants.HEAD); + assertNotNull(head); + assertTrue(head.isSymbolic()); + assertEquals(master, head.getLeaf().getName()); + assertNull(head.getObjectId()); + assertNull(bareRepo.exactRef(master)); + } + + @Test + public void testDeleteSymref() throws IOException { + RefUpdate dst = updateRef("refs/heads/abc"); + assertEquals(Result.NEW, dst.update()); + ObjectId id = dst.getNewObjectId(); + + RefUpdate u = db.updateRef("refs/symref"); + assertEquals(Result.NEW, u.link(dst.getName())); + + Ref ref = db.exactRef(u.getName()); + assertNotNull(ref); + assertTrue(ref.isSymbolic()); + assertEquals(dst.getName(), ref.getLeaf().getName()); + assertEquals(id, ref.getLeaf().getObjectId()); + + u = db.updateRef(u.getName()); + u.setDetachingSymbolicRef(); + u.setForceUpdate(true); + assertEquals(Result.FORCED, u.delete()); + + assertNull(db.exactRef(u.getName())); + ref = db.exactRef(dst.getName()); + assertNotNull(ref); + assertFalse(ref.isSymbolic()); + assertEquals(id, ref.getObjectId()); + } + /** * Delete a loose ref and make sure the directory in refs is deleted too, * and the reflog dir too @@ -347,7 +425,7 @@ Result update = updateRef.update(); assertEquals(Result.FORCED, update); assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals("HEAD", ref.getName()); assertTrue("is detached", !ref.isSymbolic()); @@ -377,7 +455,7 @@ Result update = updateRef.update(); assertEquals(Result.NEW, update); assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals("HEAD", ref.getName()); assertTrue("is detached", !ref.isSymbolic()); @@ -558,13 +636,15 @@ assertEquals(ppid, db.resolve("refs/heads/master")); // real test - RevCommit old = new RevWalk(db).parseCommit(ppid); - RefUpdate updateRef2 = db.updateRef("refs/heads/master"); - updateRef2.setExpectedOldObjectId(old); - updateRef2.setNewObjectId(pid); - Result update2 = updateRef2.update(); - assertEquals(Result.FAST_FORWARD, update2); - assertEquals(pid, db.resolve("refs/heads/master")); + try (RevWalk rw = new RevWalk(db)) { + RevCommit old = rw.parseCommit(ppid); + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setExpectedOldObjectId(old); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } } /** @@ -579,14 +659,13 @@ RefUpdate updateRef = db.updateRef("refs/heads/master"); updateRef.setNewObjectId(pid); LockFile lockFile1 = new LockFile(new File(db.getDirectory(), - "refs/heads/master"), db.getFS()); + "refs/heads/master")); try { assertTrue(lockFile1.lock()); // precondition to test Result update = updateRef.update(); assertEquals(Result.LOCK_FAILURE, update); assertEquals(opid, db.resolve("refs/heads/master")); - LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"), - db.getFS()); + LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); assertFalse(lockFile2.lock()); // was locked, still is } finally { lockFile1.unlock(); @@ -681,13 +760,13 @@ public void testRenameBranchAlsoInPack() throws IOException { ObjectId rb = db.resolve("refs/heads/b"); ObjectId rb2 = db.resolve("refs/heads/b~1"); - assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage()); + assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage()); RefUpdate updateRef = db.updateRef("refs/heads/b"); updateRef.setNewObjectId(rb2); updateRef.setForceUpdate(true); Result update = updateRef.update(); assertEquals("internal check new ref is loose", Result.FORCED, update); - assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage()); + assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage()); writeReflog(db, rb, "Just a message", "refs/heads/b"); assertTrue("log on old branch", new File(db.getDirectory(), "logs/refs/heads/b").exists()); @@ -707,9 +786,10 @@ // Create new Repository instance, to reread caches and make sure our // assumptions are persistent. - Repository ndb = new FileRepository(db.getDirectory()); - assertEquals(rb2, ndb.resolve("refs/heads/new/name")); - assertNull(ndb.resolve("refs/heads/b")); + try (Repository ndb = new FileRepository(db.getDirectory())) { + assertEquals(rb2, ndb.resolve("refs/heads/new/name")); + assertNull(ndb.resolve("refs/heads/b")); + } } public void tryRenameWhenLocked(String toLock, String fromName, @@ -728,8 +808,7 @@ "logs/" + fromName).exists()); // "someone" has branch X locked - LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock), - db.getFS()); + LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock)); try { assertTrue(lockFile.lock()); @@ -741,17 +820,17 @@ // Check that the involved refs are the same despite the failure assertExists(false, toName); if (!toLock.equals(toName)) - assertExists(false, toName + ".lock"); - assertExists(true, toLock + ".lock"); + assertExists(false, toName + LOCK_SUFFIX); + assertExists(true, toLock + LOCK_SUFFIX); if (!toLock.equals(fromName)) - assertExists(false, "logs/" + fromName + ".lock"); - assertExists(false, "logs/" + toName + ".lock"); + assertExists(false, "logs/" + fromName + LOCK_SUFFIX); + assertExists(false, "logs/" + toName + LOCK_SUFFIX); assertEquals(oldHeadId, db.resolve(Constants.HEAD)); assertEquals(oldfromId, db.resolve(fromName)); assertNull(db.resolve(toName)); assertEquals(oldFromLog.toString(), db.getReflogReader(fromName) .getReverseEntries().toString()); - if (oldHeadId != null) + if (oldHeadId != null && oldHeadLog != null) assertEquals(oldHeadLog.toString(), db.getReflogReader( Constants.HEAD).getReverseEntries().toString()); } finally { @@ -881,12 +960,66 @@ "HEAD").getReverseEntries().get(0).getComment()); } + @Test + public void testCreateMissingObject() throws IOException { + String name = "refs/heads/abc"; + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + RefUpdate ru = db.updateRef(name); + ru.setNewObjectId(bad); + Result update = ru.update(); + assertEquals(Result.REJECTED_MISSING_OBJECT, update); + + Ref ref = db.exactRef(name); + assertNull(ref); + } + + @Test + public void testUpdateMissingObject() throws IOException { + String name = "refs/heads/abc"; + RefUpdate ru = updateRef(name); + Result update = ru.update(); + assertEquals(Result.NEW, update); + ObjectId oldId = ru.getNewObjectId(); + + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + ru = db.updateRef(name); + ru.setNewObjectId(bad); + update = ru.update(); + assertEquals(Result.REJECTED_MISSING_OBJECT, update); + + Ref ref = db.exactRef(name); + assertNotNull(ref); + assertEquals(oldId, ref.getObjectId()); + } + + @Test + public void testForceUpdateMissingObject() throws IOException { + String name = "refs/heads/abc"; + RefUpdate ru = updateRef(name); + Result update = ru.update(); + assertEquals(Result.NEW, update); + ObjectId oldId = ru.getNewObjectId(); + + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + ru = db.updateRef(name); + ru.setNewObjectId(bad); + update = ru.forceUpdate(); + assertEquals(Result.REJECTED_MISSING_OBJECT, update); + + Ref ref = db.exactRef(name); + assertNotNull(ref); + assertEquals(oldId, ref.getObjectId()); + } + private static void writeReflog(Repository db, ObjectId newId, String msg, String refName) throws IOException { RefDirectory refs = (RefDirectory) db.getRefDatabase(); RefDirectoryUpdate update = refs.newUpdate(refName, true); update.setNewObjectId(newId); - refs.log(update, msg, true); + refs.log(false, update, msg, true); } private static class SubclassedId extends ObjectId { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,13 +73,14 @@ public void testIsBare_CreateRepositoryFromArbitraryGitDir() throws Exception { File gitDir = getFile("workdir"); - assertTrue(new FileRepository(gitDir).isBare()); + Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); + assertTrue(repo.isBare()); } @Test public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new FileRepository(gitDir); + Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir"); assertGitdirPath(repo, "workdir", Constants.DOT_GIT); @@ -89,7 +90,7 @@ public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new FileRepository(gitDir); + Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); String workdir = repo.getWorkTree().getName(); assertEquals(workdir, "workdir"); } @@ -157,8 +158,8 @@ @Test public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).getWorkTree(); + try (Repository repo = new FileRepository(gitDir)) { + repo.getWorkTree(); fail("Expected NoWorkTreeException missing"); } catch (NoWorkTreeException e) { // expected @@ -168,8 +169,8 @@ @Test public void testExceptionThrown_BareRepoGetIndex() throws Exception { File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).readDirCache(); + try (Repository repo = new FileRepository(gitDir)) { + repo.readDirCache(); fail("Expected NoWorkTreeException missing"); } catch (NoWorkTreeException e) { // expected @@ -179,8 +180,8 @@ @Test public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).getIndexFile(); + try (Repository repo = new FileRepository(gitDir)) { + repo.getIndexFile(); fail("Expected NoWorkTreeException missing"); } catch (NoWorkTreeException e) { // expected diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,6 +46,8 @@ package org.eclipse.jgit.internal.storage.file; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -55,7 +57,6 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -67,7 +68,6 @@ import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.FileTreeEntry; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -75,7 +75,6 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; -import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; @@ -84,10 +83,14 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; -@SuppressWarnings("deprecation") public class T0003_BasicTest extends SampleDataRepositoryTestCase { + @Rule + public ExpectedException expectedException = ExpectedException.none(); @Test public void test001_Initalize() { @@ -329,6 +332,17 @@ } @Test + public void test002_CreateBadTree() throws Exception { + // We won't create a tree entry with an empty filename + // + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(JGitText.get().invalidTreeZeroLengthName); + final TreeFormatter formatter = new TreeFormatter(); + formatter.append("", FileMode.TREE, + ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + } + + @Test public void test006_ReadUglyConfig() throws IOException, ConfigInvalidException { final File cfg = new File(db.getDirectory(), Constants.CONFIG); @@ -348,20 +362,25 @@ assertEquals("a many line\ncomment\n to test", c.getString("user", null, "defaultCheckInComment")); c.save(); - final FileReader fr = new FileReader(cfg); - final char[] cbuf = new char[configStr.length()]; - fr.read(cbuf); - fr.close(); - assertEquals(configStr, new String(cbuf)); + + // Saving normalizes out the weird "\\n\\\n" to a single escaped newline, + // and quotes the whole string. + final String expectedStr = " [core];comment\n\tfilemode = yes\n" + + "[user]\n" + + " email = A U Thor # Just an example...\n" + + " name = \"A Thor \\\\ \\\"\\t \"\n" + + " defaultCheckInComment = a many line\\ncomment\\n to test\n"; + assertEquals(expectedStr, new String(IO.readFully(cfg), Constants.CHARSET)); } @Test public void test007_Open() throws IOException { - final FileRepository db2 = new FileRepository(db.getDirectory()); - assertEquals(db.getDirectory(), db2.getDirectory()); - assertEquals(db.getObjectDatabase().getDirectory(), db2 - .getObjectDatabase().getDirectory()); - assertNotSame(db.getConfig(), db2.getConfig()); + try (final FileRepository db2 = new FileRepository(db.getDirectory())) { + assertEquals(db.getDirectory(), db2.getDirectory()); + assertEquals(db.getObjectDatabase().getDirectory(), db2 + .getObjectDatabase().getDirectory()); + assertNotSame(db.getConfig(), db2.getConfig()); + } } @Test @@ -372,8 +391,7 @@ + badvers + "\n"; write(cfg, configStr); - try { - new FileRepository(db.getDirectory()); + try (FileRepository unused = new FileRepository(db.getDirectory())) { fail("incorrectly opened a bad repository"); } catch (IllegalArgumentException ioe) { assertNotNull(ioe.getMessage()); @@ -419,29 +437,6 @@ } @Test - public void test012_SubtreeExternalSorting() throws IOException { - final ObjectId emptyBlob = insertEmptyBlob(); - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-"); - final FileTreeEntry e1 = t.addFile("a-b"); - final FileTreeEntry e2 = t.addFile("a/b"); - final FileTreeEntry e3 = t.addFile("a="); - final FileTreeEntry e4 = t.addFile("a=b"); - - e0.setId(emptyBlob); - e1.setId(emptyBlob); - e2.setId(emptyBlob); - e3.setId(emptyBlob); - e4.setId(emptyBlob); - - final Tree a = (Tree) t.findTreeMember("a"); - a.setId(insertTree(a)); - assertEquals(ObjectId - .fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), - insertTree(t)); - } - - @Test public void test020_createBlobTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); final TagBuilder t = new TagBuilder(); @@ -464,9 +459,8 @@ @Test public void test021_createTreeTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE); @@ -488,9 +482,8 @@ @Test public void test022_createCommitTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final CommitBuilder almostEmptyCommit = new CommitBuilder(); almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, @@ -520,9 +513,8 @@ @Test public void test023_createCommitNonAnullii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -530,7 +522,7 @@ 4294967295000L, 60)); commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com", 4294967295000L, 60)); - commit.setEncoding("UTF-8"); + commit.setEncoding(UTF_8); commit.setMessage("\u00dcbergeeks"); ObjectId cid = insertCommit(commit); assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name()); @@ -542,9 +534,8 @@ @Test public void test024_createCommitNonAscii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -559,12 +550,13 @@ } @Test - public void test025_computeSha1NoStore() throws IOException { + public void test025_computeSha1NoStore() { byte[] data = "test025 some data, more than 16 bytes to get good coverage" - .getBytes("ISO-8859-1"); - final ObjectId id = new ObjectInserter.Formatter().idFor( - Constants.OBJ_BLOB, data); - assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name()); + .getBytes(ISO_8859_1); + try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) { + final ObjectId id = formatter.idFor(Constants.OBJ_BLOB, data); + assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name()); + } } @Test @@ -675,33 +667,39 @@ @Test public void test028_LockPackedRef() throws IOException { + ObjectId id1; + ObjectId id2; + try (ObjectInserter ins = db.newObjectInserter()) { + id1 = ins.insert( + Constants.OBJ_BLOB, "contents1".getBytes(Constants.CHARSET)); + id2 = ins.insert( + Constants.OBJ_BLOB, "contents2".getBytes(Constants.CHARSET)); + ins.flush(); + } + writeTrashFile(".git/packed-refs", - "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar"); + id1.name() + " refs/heads/foobar"); writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n"); BUG_WorkAroundRacyGitIssues("packed-refs"); BUG_WorkAroundRacyGitIssues("HEAD"); ObjectId resolve = db.resolve("HEAD"); - assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name()); + assertEquals(id1, resolve); RefUpdate lockRef = db.updateRef("HEAD"); - ObjectId newId = ObjectId - .fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def"); - lockRef.setNewObjectId(newId); + lockRef.setNewObjectId(id2); assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate()); assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); - assertEquals(newId, db.resolve("refs/heads/foobar")); + assertEquals(id2, db.resolve("refs/heads/foobar")); // Again. The ref already exists RefUpdate lockRef2 = db.updateRef("HEAD"); - ObjectId newId2 = ObjectId - .fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7"); - lockRef2.setNewObjectId(newId2); + lockRef2.setNewObjectId(id1); assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate()); assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); - assertEquals(newId2, db.resolve("refs/heads/foobar")); + assertEquals(id1, db.resolve("refs/heads/foobar")); } @Test @@ -746,14 +744,6 @@ return emptyId; } - private ObjectId insertTree(Tree tree) throws IOException { - try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); - oi.flush(); - return id; - } - } - private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { ObjectId id = oi.insert(tree); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,6 +93,7 @@ return rng; } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -105,6 +106,7 @@ wc = (WindowCursor) repo.newObjectReader(); } + @Override @After public void tearDown() throws Exception { if (wc != null) @@ -143,7 +145,7 @@ public void testStandardFormat_LargeObject() throws Exception { final int type = Constants.OBJ_BLOB; byte[] data = getRng().nextBytes(streamThreshold + 5); - ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + ObjectId id = getId(type, data); write(id, compressStandardFormat(type, data)); ObjectLoader ol; @@ -306,7 +308,7 @@ throws Exception { final int type = Constants.OBJ_BLOB; byte[] data = getRng().nextBytes(streamThreshold + 5); - ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + ObjectId id = getId(type, data); byte[] gz = compressStandardFormat(type, data); gz[gz.length - 1] = 0; gz[gz.length - 2] = 0; @@ -344,7 +346,7 @@ throws Exception { final int type = Constants.OBJ_BLOB; byte[] data = getRng().nextBytes(streamThreshold + 5); - ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + ObjectId id = getId(type, data); byte[] gz = compressStandardFormat(type, data); byte[] tr = new byte[gz.length - 1]; System.arraycopy(gz, 0, tr, 0, tr.length); @@ -379,7 +381,7 @@ throws Exception { final int type = Constants.OBJ_BLOB; byte[] data = getRng().nextBytes(streamThreshold + 5); - ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + ObjectId id = getId(type, data); byte[] gz = compressStandardFormat(type, data); byte[] tr = new byte[gz.length + 1]; System.arraycopy(gz, 0, tr, 0, gz.length); @@ -438,7 +440,7 @@ public void testPackFormat_LargeObject() throws Exception { final int type = Constants.OBJ_BLOB; byte[] data = getRng().nextBytes(streamThreshold + 5); - ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + ObjectId id = getId(type, data); write(id, compressPackFormat(type, data)); ObjectLoader ol; @@ -578,4 +580,10 @@ out.close(); } } + + private ObjectId getId(int type, byte[] data) { + try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) { + return formatter.idFor(type, data); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,7 +73,7 @@ public void setUp() throws Exception { super.setUp(); - toLoad = new ArrayList(); + toLoad = new ArrayList<>(); final BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream(JGitTestUtil .getTestResourceFile("all_packed_objects.txt")), diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/DeltaIndexTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/DeltaIndexTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/DeltaIndexTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/DeltaIndexTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,9 +51,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import org.eclipse.jgit.internal.storage.pack.BinaryDelta; -import org.eclipse.jgit.internal.storage.pack.DeltaEncoder; -import org.eclipse.jgit.internal.storage.pack.DeltaIndex; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,284 @@ +/* + * 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.internal.storage.pack; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.internal.storage.file.GcTestCase; +import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; +import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapPreparer.BitmapCommit; +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.junit.Test; + +public class GcCommitSelectionTest extends GcTestCase { + + @Test + public void testBitmapSpansNoMerges() throws Exception { + testBitmapSpansNoMerges(false); + } + + @Test + public void testBitmapSpansNoMergesWithTags() throws Exception { + testBitmapSpansNoMerges(true); + } + + private void testBitmapSpansNoMerges(boolean withTags) throws Exception { + /* + * Commit counts -> expected bitmap counts for history without merges. + * The top 100 contiguous commits should always have bitmaps, and the + * "recent" bitmaps beyond that are spaced out every 100-200 commits. + * (Starting at 100, the next 100 commits are searched for a merge + * commit. Since one is not found, the spacing between commits is 200. + */ + int[][] bitmapCounts = { // + { 1, 1 }, { 50, 50 }, { 99, 99 }, { 100, 100 }, { 101, 100 }, + { 200, 100 }, { 201, 100 }, { 299, 100 }, { 300, 101 }, + { 301, 101 }, { 401, 101 }, { 499, 101 }, { 500, 102 }, }; + int currentCommits = 0; + BranchBuilder bb = tr.branch("refs/heads/main"); + + for (int[] counts : bitmapCounts) { + int nextCommitCount = counts[0]; + int expectedBitmapCount = counts[1]; + assertTrue(nextCommitCount > currentCommits); // programming error + for (int i = currentCommits; i < nextCommitCount; i++) { + String str = "A" + i; + RevCommit rc = bb.commit().message(str).add(str, str).create(); + if (withTags) { + tr.lightweightTag(str, rc); + } + } + currentCommits = nextCommitCount; + + gc.setPackExpireAgeMillis(0); // immediately delete old packs + gc.setExpireAgeMillis(0); + gc.gc(); + assertEquals(currentCommits * 3, // commit/tree/object + gc.getStatistics().numberOfPackedObjects); + assertEquals(currentCommits + " commits: ", expectedBitmapCount, + gc.getStatistics().numberOfBitmaps); + } + } + + @Test + public void testBitmapSpansWithMerges() throws Exception { + /* + * Commits that are merged. Since 55 is in the oldest history it is + * never considered. Searching goes from oldest to newest so 115 is the + * first merge commit found. After that the range 116-216 is ignored so + * 175 is never considered. + */ + List merges = Arrays.asList(Integer.valueOf(55), + Integer.valueOf(115), Integer.valueOf(175), + Integer.valueOf(235)); + /* + * Commit counts -> expected bitmap counts for history with merges. The + * top 100 contiguous commits should always have bitmaps, and the + * "recent" bitmaps beyond that are spaced out every 100-200 commits. + * Merges in the < 100 range have no effect and merges in the > 100 + * range will only be considered for commit counts > 200. + */ + int[][] bitmapCounts = { // + { 1, 1 }, { 55, 55 }, { 56, 57 }, // +1 bitmap from branch A55 + { 99, 100 }, // still +1 branch @55 + { 100, 100 }, // 101 commits, only 100 newest + { 116, 100 }, // @55 still in 100 newest bitmaps + { 176, 101 }, // @55 branch tip is not in 100 newest + { 213, 101 }, // 216 commits, @115&@175 in 100 newest + { 214, 102 }, // @55 branch tip, merge @115, @177 in newest + { 236, 102 }, // all 4 merge points in history + { 273, 102 }, // 277 commits, @175&@235 in newest + { 274, 103 }, // @55, @115, merge @175, @235 in newest + { 334, 103 }, // @55,@115,@175, @235 in newest + { 335, 104 }, // @55,@115,@175, merge @235 + { 435, 104 }, // @55,@115,@175,@235 tips + { 436, 104 }, // force @236 + }; + + int currentCommits = 0; + BranchBuilder bb = tr.branch("refs/heads/main"); + + for (int[] counts : bitmapCounts) { + int nextCommitCount = counts[0]; + int expectedBitmapCount = counts[1]; + assertTrue(nextCommitCount > currentCommits); // programming error + for (int i = currentCommits; i < nextCommitCount; i++) { + String str = "A" + i; + if (!merges.contains(Integer.valueOf(i))) { + bb.commit().message(str).add(str, str).create(); + } else { + BranchBuilder bbN = tr.branch("refs/heads/A" + i); + bb.commit().message(str).add(str, str) + .parent(bbN.commit().create()).create(); + } + } + currentCommits = nextCommitCount; + + gc.setPackExpireAgeMillis(0); // immediately delete old packs + gc.setExpireAgeMillis(0); + gc.gc(); + assertEquals(currentCommits + " commits: ", expectedBitmapCount, + gc.getStatistics().numberOfBitmaps); + } + } + + @Test + public void testBitmapsForExcessiveBranches() throws Exception { + int oneDayInSeconds = 60 * 60 * 24; + + // All of branch A is committed on day1 + BranchBuilder bbA = tr.branch("refs/heads/A"); + for (int i = 0; i < 1001; i++) { + String msg = "A" + i; + bbA.commit().message(msg).add(msg, msg).create(); + } + // All of in branch B is committed on day91 + tr.tick(oneDayInSeconds * 90); + BranchBuilder bbB = tr.branch("refs/heads/B"); + for (int i = 0; i < 1001; i++) { + String msg = "B" + i; + bbB.commit().message(msg).add(msg, msg).create(); + } + // Create 100 other branches with a single commit + for (int i = 0; i < 100; i++) { + BranchBuilder bb = tr.branch("refs/heads/N" + i); + String msg = "singlecommit" + i; + bb.commit().message(msg).add(msg, msg).create(); + } + // now is day92 + tr.tick(oneDayInSeconds); + + // Since there are no merges, commits in recent history are selected + // every 200 commits. + final int commitsForSparseBranch = 1 + (1001 / 200); + final int commitsForFullBranch = 100 + (901 / 200); + final int commitsForShallowBranches = 100; + + // Excessive branch history pruning, one old branch. + gc.setPackExpireAgeMillis(0); // immediately delete old packs + gc.setExpireAgeMillis(0); + gc.gc(); + assertEquals( + commitsForSparseBranch + commitsForFullBranch + + commitsForShallowBranches, + gc.getStatistics().numberOfBitmaps); + } + + @Test + public void testSelectionOrderingWithChains() throws Exception { + /*- + * Create a history like this, where 'N' is the number of seconds from + * the first commit in the branch: + * + * ---o---o---o commits b3,b5,b7 + * / \ + * o--o--o---o---o---o--o commits m0,m1,m2,m4,m6,m8,m9 + */ + BranchBuilder bb = tr.branch("refs/heads/main"); + RevCommit m0 = addCommit(bb, "m0"); + RevCommit m1 = addCommit(bb, "m1", m0); + RevCommit m2 = addCommit(bb, "m2", m1); + RevCommit b3 = addCommit(bb, "b3", m1); + RevCommit m4 = addCommit(bb, "m4", m2); + RevCommit b5 = addCommit(bb, "m5", b3); + RevCommit m6 = addCommit(bb, "m6", m4); + RevCommit b7 = addCommit(bb, "m7", b5); + RevCommit m8 = addCommit(bb, "m8", m6, b7); + RevCommit m9 = addCommit(bb, "m9", m8); + + List commits = Arrays.asList(m0, m1, m2, b3, m4, b5, m6, b7, + m8, m9); + PackWriterBitmapPreparer preparer = newPeparer(m9, commits); + List selection = new ArrayList<>( + preparer.selectCommits(commits.size(), PackWriter.NONE)); + + // Verify that the output is ordered by the separate "chains" + String[] expected = { m0.name(), m1.name(), m2.name(), m4.name(), + m6.name(), m8.name(), m9.name(), b3.name(), b5.name(), + b7.name() }; + assertEquals(expected.length, selection.size()); + for (int i = 0; i < expected.length; i++) { + assertEquals("Entry " + i, expected[i], selection.get(i).getName()); + } + } + + private RevCommit addCommit(BranchBuilder bb, String msg, + RevCommit... parents) throws Exception { + CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1) + .noParents(); + for (RevCommit parent : parents) { + commit.parent(parent); + } + return commit.create(); + } + + private PackWriterBitmapPreparer newPeparer(RevCommit want, + List commits) + throws IOException { + List objects = new ArrayList<>(commits.size()); + for (RevCommit commit : commits) { + objects.add(new ObjectToPack(commit, Constants.OBJ_COMMIT)); + } + Set wants = Collections.singleton((ObjectId) want); + PackConfig config = new PackConfig(); + PackBitmapIndexBuilder builder = new PackBitmapIndexBuilder(objects); + return new PackWriterBitmapPreparer( + tr.getRepository().newObjectReader(), builder, + NullProgressMonitor.INSTANCE, wants, config); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,136 @@ +package org.eclipse.jgit.internal.storage.pack; + +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_DISTANT_COMMIT_SPAN; +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_COUNT; +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_SPAN; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.junit.Test; + +/** Tests for the {@link PackWriterBitmapPreparer}. */ +public class PackWriterBitmapPreparerTest { + private static class StubObjectReader extends ObjectReader { + @Override + public ObjectReader newReader() { + return null; + } + + @Override + public Collection resolve(AbbreviatedObjectId id) + throws IOException { + return null; + } + + @Override + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return null; + } + + @Override + public Set getShallowCommits() throws IOException { + return null; + } + + @Override + public void close() { + // stub + } + } + + @Test + public void testNextSelectionDistanceForActiveBranch() throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 }, + { 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 }, + { 22200, 2200 }, { 24999, 4999 }, { 25000, 5000 }, + { 50000, 5000 }, { 1000000, 5000 }, }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithFewerRecentCommits() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer(1000, + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 1000, 100 }, + { 1100, 100 }, { 1111, 111 }, { 2000, 1000 }, { 5999, 4999 }, + { 6000, 5000 }, { 10000, 5000 }, { 50000, 5000 }, + { 1000000, 5000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithSmallerRecentSpan() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + 10, // recent span + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 10 }, { 100, 10 }, { 10000, 10 }, + { 20000, 10 }, { 20010, 10 }, { 20012, 12 }, { 20050, 50 }, + { 20200, 200 }, { 22200, 2200 }, { 24999, 4999 }, + { 25000, 5000 }, { 50000, 5000 }, { 1000000, 5000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithSmallerDistantSpan() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + 1000); + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 }, + { 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 }, + { 20999, 999 }, { 21000, 1000 }, { 22000, 1000 }, + { 25000, 1000 }, { 50000, 1000 }, { 1000000, 1000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + private PackWriterBitmapPreparer newPeparer(int recentCount, int recentSpan, + int distantSpan) throws IOException { + List objects = Collections.emptyList(); + Set wants = Collections.emptySet(); + PackConfig config = new PackConfig(); + config.setBitmapRecentCommitCount(recentCount); + config.setBitmapRecentCommitSpan(recentSpan); + config.setBitmapDistantCommitSpan(distantSpan); + PackBitmapIndexBuilder indexBuilder = new PackBitmapIndexBuilder( + objects); + return new PackWriterBitmapPreparer(new StubObjectReader(), + indexBuilder, null, wants, config); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.junit.Test; + +public class MergedReftableTest { + @Test + public void noTables() throws IOException { + MergedReftable mr = merge(new byte[0][]); + try (RefCursor rc = mr.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(HEAD)) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(R_HEADS)) { + assertFalse(rc.next()); + } + } + + @Test + public void oneEmptyTable() throws IOException { + MergedReftable mr = merge(write()); + try (RefCursor rc = mr.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(HEAD)) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(R_HEADS)) { + assertFalse(rc.next()); + } + } + + @Test + public void twoEmptyTables() throws IOException { + MergedReftable mr = merge(write(), write()); + try (RefCursor rc = mr.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(HEAD)) { + assertFalse(rc.next()); + } + try (RefCursor rc = mr.seekRef(R_HEADS)) { + assertFalse(rc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void oneTableScan() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 567; i++) { + refs.add(ref(String.format("refs/heads/%03d", i), i)); + } + + MergedReftable mr = merge(write(refs)); + try (RefCursor rc = mr.allRefs()) { + for (Ref exp : refs) { + assertTrue("has " + exp.getName(), rc.next()); + Ref act = rc.getRef(); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + } + assertFalse(rc.next()); + } + } + + @Test + public void deleteIsHidden() throws IOException { + List delta1 = Arrays.asList( + ref("refs/heads/apple", 1), + ref("refs/heads/master", 2)); + List delta2 = Arrays.asList(delete("refs/heads/apple")); + + MergedReftable mr = merge(write(delta1), write(delta2)); + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/master", rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + } + + @Test + public void twoTableSeek() throws IOException { + List delta1 = Arrays.asList( + ref("refs/heads/apple", 1), + ref("refs/heads/master", 2)); + List delta2 = Arrays.asList(ref("refs/heads/banana", 3)); + + MergedReftable mr = merge(write(delta1), write(delta2)); + try (RefCursor rc = mr.seekRef("refs/heads/master")) { + assertTrue(rc.next()); + assertEquals("refs/heads/master", rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + } + + @Test + public void twoTableById() throws IOException { + List delta1 = Arrays.asList( + ref("refs/heads/apple", 1), + ref("refs/heads/master", 2)); + List delta2 = Arrays.asList(ref("refs/heads/banana", 3)); + + MergedReftable mr = merge(write(delta1), write(delta2)); + try (RefCursor rc = mr.byObjectId(id(2))) { + assertTrue(rc.next()); + assertEquals("refs/heads/master", rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void fourTableScan() throws IOException { + List base = new ArrayList<>(); + for (int i = 1; i <= 567; i++) { + base.add(ref(String.format("refs/heads/%03d", i), i)); + } + + List delta1 = Arrays.asList( + ref("refs/heads/next", 4), + ref(String.format("refs/heads/%03d", 55), 4096)); + List delta2 = Arrays.asList( + delete("refs/heads/next"), + ref(String.format("refs/heads/%03d", 55), 8192)); + List delta3 = Arrays.asList( + ref("refs/heads/master", 4242), + ref(String.format("refs/heads/%03d", 42), 5120), + ref(String.format("refs/heads/%03d", 98), 6120)); + + List expected = merge(base, delta1, delta2, delta3); + MergedReftable mr = merge( + write(base), + write(delta1), + write(delta2), + write(delta3)); + try (RefCursor rc = mr.allRefs()) { + for (Ref exp : expected) { + assertTrue("has " + exp.getName(), rc.next()); + Ref act = rc.getRef(); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + } + assertFalse(rc.next()); + } + } + + @Test + public void scanDuplicates() throws IOException { + List delta1 = Arrays.asList( + ref("refs/heads/apple", 1), + ref("refs/heads/banana", 2)); + List delta2 = Arrays.asList( + ref("refs/heads/apple", 3), + ref("refs/heads/apple", 4)); + + MergedReftable mr = merge(write(delta1, 1000), write(delta2, 2000)); + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/apple", rc.getRef().getName()); + assertEquals(id(3), rc.getRef().getObjectId()); + assertTrue(rc.next()); + assertEquals("refs/heads/banana", rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + } + + @Test + public void scanIncludeDeletes() throws IOException { + List delta1 = Arrays.asList(ref("refs/heads/next", 4)); + List delta2 = Arrays.asList(delete("refs/heads/next")); + List delta3 = Arrays.asList(ref("refs/heads/master", 8)); + + MergedReftable mr = merge(write(delta1), write(delta2), write(delta3)); + mr.setIncludeDeletes(true); + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + Ref r = rc.getRef(); + assertEquals("refs/heads/master", r.getName()); + assertEquals(id(8), r.getObjectId()); + + assertTrue(rc.next()); + r = rc.getRef(); + assertEquals("refs/heads/next", r.getName()); + assertEquals(NEW, r.getStorage()); + assertNull(r.getObjectId()); + + assertFalse(rc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void oneTableSeek() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 567; i++) { + refs.add(ref(String.format("refs/heads/%03d", i), i)); + } + + MergedReftable mr = merge(write(refs)); + for (Ref exp : refs) { + try (RefCursor rc = mr.seekRef(exp.getName())) { + assertTrue("has " + exp.getName(), rc.next()); + Ref act = rc.getRef(); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + assertFalse(rc.next()); + } + } + } + + @Test + public void missedUpdate() throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(3) + .begin(buf); + writer.writeRef(ref("refs/heads/a", 1), 1); + writer.writeRef(ref("refs/heads/c", 3), 3); + writer.finish(); + byte[] base = buf.toByteArray(); + + byte[] delta = write(Arrays.asList( + ref("refs/heads/b", 2), + ref("refs/heads/c", 4)), + 2); + MergedReftable mr = merge(base, delta); + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/a", rc.getRef().getName()); + assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(1, rc.getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals("refs/heads/b", rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(2, rc.getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals("refs/heads/c", rc.getRef().getName()); + assertEquals(id(3), rc.getRef().getObjectId()); + assertEquals(3, rc.getUpdateIndex()); + } + } + + @Test + public void compaction() throws IOException { + List delta1 = Arrays.asList( + ref("refs/heads/next", 4), + ref("refs/heads/master", 1)); + List delta2 = Arrays.asList(delete("refs/heads/next")); + List delta3 = Arrays.asList(ref("refs/heads/master", 8)); + + ReftableCompactor compactor = new ReftableCompactor(); + compactor.addAll(Arrays.asList( + read(write(delta1)), + read(write(delta2)), + read(write(delta3)))); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + compactor.compact(out); + byte[] table = out.toByteArray(); + + ReftableReader reader = read(table); + try (RefCursor rc = reader.allRefs()) { + assertTrue(rc.next()); + Ref r = rc.getRef(); + assertEquals("refs/heads/master", r.getName()); + assertEquals(id(8), r.getObjectId()); + assertFalse(rc.next()); + } + } + + private static MergedReftable merge(byte[]... table) { + List stack = new ArrayList<>(table.length); + for (byte[] b : table) { + stack.add(read(b)); + } + return new MergedReftable(stack); + } + + private static ReftableReader read(byte[] table) { + return new ReftableReader(BlockSource.from(table)); + } + + private static Ref ref(String name, int id) { + return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id)); + } + + private static Ref delete(String name) { + return new ObjectIdRef.Unpeeled(NEW, name, null); + } + + private static ObjectId id(int i) { + byte[] buf = new byte[OBJECT_ID_LENGTH]; + buf[0] = (byte) (i & 0xff); + buf[1] = (byte) ((i >>> 8) & 0xff); + buf[2] = (byte) ((i >>> 16) & 0xff); + buf[3] = (byte) (i >>> 24); + return ObjectId.fromRaw(buf); + } + + private byte[] write(Ref... refs) throws IOException { + return write(Arrays.asList(refs)); + } + + private byte[] write(Collection refs) throws IOException { + return write(refs, 1); + } + + private byte[] write(Collection refs, long updateIndex) + throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + new ReftableWriter() + .setMinUpdateIndex(updateIndex) + .setMaxUpdateIndex(updateIndex) + .begin(buffer) + .sortAndWriteRefs(refs) + .finish(); + return buffer.toByteArray(); + } + + @SafeVarargs + private static List merge(List... tables) { + Map expect = new HashMap<>(); + for (List t : tables) { + for (Ref r : t) { + if (r.getStorage() == NEW && r.getObjectId() == null) { + expect.remove(r.getName()); + } else { + expect.put(r.getName(), r); + } + } + } + + List expected = new ArrayList<>(expect.values()); + Collections.sort(expected, RefComparator.INSTANCE); + return expected; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.junit.Test; + +public class ReftableCompactorTest { + private static final String MASTER = "refs/heads/master"; + private static final String NEXT = "refs/heads/next"; + + @Test + public void noTables() throws IOException { + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + compactor.compact(out); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(0, stats.maxUpdateIndex()); + assertEquals(0, stats.refCount()); + } + + @Test + public void oneTable() throws IOException { + byte[] inTab; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(0) + .setMaxUpdateIndex(0) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 1)); + writer.finish(); + inTab = inBuf.toByteArray(); + } + + byte[] outTab; + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) { + compactor.tryAddFirst(read(inTab)); + compactor.compact(outBuf); + outTab = outBuf.toByteArray(); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(0, stats.maxUpdateIndex()); + assertEquals(1, stats.refCount()); + + ReftableReader rr = read(outTab); + try (RefCursor rc = rr.allRefs()) { + assertTrue(rc.next()); + assertEquals(MASTER, rc.getRef().getName()); + assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(0, rc.getUpdateIndex()); + } + } + + @Test + public void twoTablesOneRef() throws IOException { + byte[] inTab1; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(0) + .setMaxUpdateIndex(0) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 1)); + writer.finish(); + inTab1 = inBuf.toByteArray(); + } + + byte[] inTab2; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 2)); + writer.finish(); + inTab2 = inBuf.toByteArray(); + } + + byte[] outTab; + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) { + compactor.addAll(Arrays.asList(read(inTab1), read(inTab2))); + compactor.compact(outBuf); + outTab = outBuf.toByteArray(); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(1, stats.maxUpdateIndex()); + assertEquals(1, stats.refCount()); + + ReftableReader rr = read(outTab); + try (RefCursor rc = rr.allRefs()) { + assertTrue(rc.next()); + assertEquals(MASTER, rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1, rc.getUpdateIndex()); + } + } + + @Test + public void twoTablesTwoRefs() throws IOException { + byte[] inTab1; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(0) + .setMaxUpdateIndex(0) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 1)); + writer.writeRef(ref(NEXT, 2)); + writer.finish(); + inTab1 = inBuf.toByteArray(); + } + + byte[] inTab2; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 3)); + writer.finish(); + inTab2 = inBuf.toByteArray(); + } + + byte[] outTab; + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) { + compactor.addAll(Arrays.asList(read(inTab1), read(inTab2))); + compactor.compact(outBuf); + outTab = outBuf.toByteArray(); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(1, stats.maxUpdateIndex()); + assertEquals(2, stats.refCount()); + + ReftableReader rr = read(outTab); + try (RefCursor rc = rr.allRefs()) { + assertTrue(rc.next()); + assertEquals(MASTER, rc.getRef().getName()); + assertEquals(id(3), rc.getRef().getObjectId()); + assertEquals(1, rc.getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals(NEXT, rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(0, rc.getUpdateIndex()); + } + } + + @Test + public void twoTablesIncludeOneDelete() throws IOException { + byte[] inTab1; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(0) + .setMaxUpdateIndex(0) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 1)); + writer.finish(); + inTab1 = inBuf.toByteArray(); + } + + byte[] inTab2; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(inBuf); + + writer.writeRef(tombstone(MASTER)); + writer.finish(); + inTab2 = inBuf.toByteArray(); + } + + byte[] outTab; + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) { + compactor.setIncludeDeletes(true); + compactor.addAll(Arrays.asList(read(inTab1), read(inTab2))); + compactor.compact(outBuf); + outTab = outBuf.toByteArray(); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(1, stats.maxUpdateIndex()); + assertEquals(1, stats.refCount()); + + ReftableReader rr = read(outTab); + try (RefCursor rc = rr.allRefs()) { + assertFalse(rc.next()); + } + } + + @Test + public void twoTablesNotIncludeOneDelete() throws IOException { + byte[] inTab1; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(0) + .setMaxUpdateIndex(0) + .begin(inBuf); + + writer.writeRef(ref(MASTER, 1)); + writer.finish(); + inTab1 = inBuf.toByteArray(); + } + + byte[] inTab2; + try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) { + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(inBuf); + + writer.writeRef(tombstone(MASTER)); + writer.finish(); + inTab2 = inBuf.toByteArray(); + } + + byte[] outTab; + ReftableCompactor compactor = new ReftableCompactor(); + try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) { + compactor.setIncludeDeletes(false); + compactor.addAll(Arrays.asList(read(inTab1), read(inTab2))); + compactor.compact(outBuf); + outTab = outBuf.toByteArray(); + } + Stats stats = compactor.getStats(); + assertEquals(0, stats.minUpdateIndex()); + assertEquals(1, stats.maxUpdateIndex()); + assertEquals(0, stats.refCount()); + + ReftableReader rr = read(outTab); + try (RefCursor rc = rr.allRefs()) { + assertFalse(rc.next()); + } + } + + private static Ref ref(String name, int id) { + return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id)); + } + + private static Ref tombstone(String name) { + return new ObjectIdRef.Unpeeled(NEW, name, null); + } + + private static ObjectId id(int i) { + byte[] buf = new byte[OBJECT_ID_LENGTH]; + buf[0] = (byte) (i & 0xff); + buf[1] = (byte) ((i >>> 8) & 0xff); + buf[2] = (byte) ((i >>> 16) & 0xff); + buf[3] = (byte) (i >>> 24); + return ObjectId.fromRaw(buf); + } + + private static ReftableReader read(byte[] table) { + return new ReftableReader(BlockSource.from(table)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2017, 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.internal.storage.reftable; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +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; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.io.BlockSource; +import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.SymbolicRef; +import org.junit.Test; + +public class ReftableTest { + private static final String MASTER = "refs/heads/master"; + private static final String NEXT = "refs/heads/next"; + private static final String V1_0 = "refs/tags/v1.0"; + + private Stats stats; + + @Test + public void emptyTable() throws IOException { + byte[] table = write(); + assertEquals(92 /* header, footer */, table.length); + assertEquals('R', table[0]); + assertEquals('E', table[1]); + assertEquals('F', table[2]); + assertEquals('T', table[3]); + assertEquals(0x01, table[4]); + assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8)); + assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92)); + + Reftable t = read(table); + try (RefCursor rc = t.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef(HEAD)) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef(R_HEADS)) { + assertFalse(rc.next()); + } + try (LogCursor rc = t.allLogs()) { + assertFalse(rc.next()); + } + } + + @Test + public void emptyVirtualTableFromRefs() throws IOException { + Reftable t = Reftable.from(Collections.emptyList()); + try (RefCursor rc = t.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef(HEAD)) { + assertFalse(rc.next()); + } + try (LogCursor rc = t.allLogs()) { + assertFalse(rc.next()); + } + } + + @Test + public void estimateCurrentBytesOneRef() throws IOException { + Ref exp = ref(MASTER, 1); + int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68; + + byte[] table; + ReftableConfig cfg = new ReftableConfig(); + cfg.setIndexObjects(false); + ReftableWriter writer = new ReftableWriter().setConfig(cfg); + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + writer.begin(buf); + assertEquals(92, writer.estimateTotalBytes()); + writer.writeRef(exp); + assertEquals(expBytes, writer.estimateTotalBytes()); + writer.finish(); + table = buf.toByteArray(); + } + assertEquals(expBytes, table.length); + } + + @SuppressWarnings("boxing") + @Test + public void estimateCurrentBytesWithIndex() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 5670; i++) { + refs.add(ref(String.format("refs/heads/%04d", i), i)); + } + + ReftableConfig cfg = new ReftableConfig(); + cfg.setIndexObjects(false); + cfg.setMaxIndexLevels(1); + + int expBytes = 147860; + byte[] table; + ReftableWriter writer = new ReftableWriter().setConfig(cfg); + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + writer.begin(buf); + writer.sortAndWriteRefs(refs); + assertEquals(expBytes, writer.estimateTotalBytes()); + writer.finish(); + stats = writer.getStats(); + table = buf.toByteArray(); + } + assertEquals(1, stats.refIndexLevels()); + assertEquals(expBytes, table.length); + } + + @Test + public void oneIdRef() throws IOException { + Ref exp = ref(MASTER, 1); + byte[] table = write(exp); + assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertTrue(rc.next()); + Ref act = rc.getRef(); + assertNotNull(act); + assertEquals(PACKED, act.getStorage()); + assertTrue(act.isPeeled()); + assertFalse(act.isSymbolic()); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + assertNull(act.getPeeledObjectId()); + assertFalse(rc.wasDeleted()); + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef(MASTER)) { + assertTrue(rc.next()); + Ref act = rc.getRef(); + assertNotNull(act); + assertEquals(exp.getName(), act.getName()); + assertFalse(rc.next()); + } + } + + @Test + public void oneTagRef() throws IOException { + Ref exp = tag(V1_0, 1, 2); + byte[] table = write(exp); + assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertTrue(rc.next()); + Ref act = rc.getRef(); + assertNotNull(act); + assertEquals(PACKED, act.getStorage()); + assertTrue(act.isPeeled()); + assertFalse(act.isSymbolic()); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId()); + } + } + + @Test + public void oneSymbolicRef() throws IOException { + Ref exp = sym(HEAD, MASTER); + byte[] table = write(exp); + assertEquals( + 24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68, + table.length); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertTrue(rc.next()); + Ref act = rc.getRef(); + assertNotNull(act); + assertTrue(act.isSymbolic()); + assertEquals(exp.getName(), act.getName()); + assertNotNull(act.getLeaf()); + assertEquals(MASTER, act.getTarget().getName()); + assertNull(act.getObjectId()); + } + } + + @Test + public void resolveSymbolicRef() throws IOException { + Reftable t = read(write( + sym(HEAD, "refs/heads/tmp"), + sym("refs/heads/tmp", MASTER), + ref(MASTER, 1))); + + Ref head = t.exactRef(HEAD); + assertNull(head.getObjectId()); + assertEquals("refs/heads/tmp", head.getTarget().getName()); + + head = t.resolve(head); + assertNotNull(head); + assertEquals(id(1), head.getObjectId()); + + Ref master = t.exactRef(MASTER); + assertNotNull(master); + assertSame(master, t.resolve(master)); + } + + @Test + public void failDeepChainOfSymbolicRef() throws IOException { + Reftable t = read(write( + sym(HEAD, "refs/heads/1"), + sym("refs/heads/1", "refs/heads/2"), + sym("refs/heads/2", "refs/heads/3"), + sym("refs/heads/3", "refs/heads/4"), + sym("refs/heads/4", "refs/heads/5"), + sym("refs/heads/5", MASTER), + ref(MASTER, 1))); + + Ref head = t.exactRef(HEAD); + assertNull(head.getObjectId()); + assertNull(t.resolve(head)); + } + + @Test + public void oneDeletedRef() throws IOException { + String name = "refs/heads/gone"; + Ref exp = newRef(name); + byte[] table = write(exp); + assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertFalse(rc.next()); + } + + t.setIncludeDeletes(true); + try (RefCursor rc = t.allRefs()) { + assertTrue(rc.next()); + Ref act = rc.getRef(); + assertNotNull(act); + assertFalse(act.isSymbolic()); + assertEquals(name, act.getName()); + assertEquals(NEW, act.getStorage()); + assertNull(act.getObjectId()); + assertTrue(rc.wasDeleted()); + } + } + + @Test + public void seekNotFound() throws IOException { + Ref exp = ref(MASTER, 1); + ReftableReader t = read(write(exp)); + try (RefCursor rc = t.seekRef("refs/heads/a")) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef("refs/heads/n")) { + assertFalse(rc.next()); + } + } + + @Test + public void namespaceNotFound() throws IOException { + Ref exp = ref(MASTER, 1); + ReftableReader t = read(write(exp)); + try (RefCursor rc = t.seekRef("refs/changes/")) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef("refs/tags/")) { + assertFalse(rc.next()); + } + } + + @Test + public void namespaceHeads() throws IOException { + Ref master = ref(MASTER, 1); + Ref next = ref(NEXT, 2); + Ref v1 = tag(V1_0, 3, 4); + + ReftableReader t = read(write(master, next, v1)); + try (RefCursor rc = t.seekRef("refs/tags/")) { + assertTrue(rc.next()); + assertEquals(V1_0, rc.getRef().getName()); + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef("refs/heads/")) { + assertTrue(rc.next()); + assertEquals(MASTER, rc.getRef().getName()); + + assertTrue(rc.next()); + assertEquals(NEXT, rc.getRef().getName()); + + assertFalse(rc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void indexScan() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 5670; i++) { + refs.add(ref(String.format("refs/heads/%04d", i), i)); + } + + byte[] table = write(refs); + assertTrue(stats.refIndexLevels() > 0); + assertTrue(stats.refIndexSize() > 0); + assertScan(refs, read(table)); + } + + @SuppressWarnings("boxing") + @Test + public void indexSeek() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 5670; i++) { + refs.add(ref(String.format("refs/heads/%04d", i), i)); + } + + byte[] table = write(refs); + assertTrue(stats.refIndexLevels() > 0); + assertTrue(stats.refIndexSize() > 0); + assertSeek(refs, read(table)); + } + + @SuppressWarnings("boxing") + @Test + public void noIndexScan() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 567; i++) { + refs.add(ref(String.format("refs/heads/%03d", i), i)); + } + + byte[] table = write(refs); + assertEquals(0, stats.refIndexLevels()); + assertEquals(0, stats.refIndexSize()); + assertEquals(table.length, stats.totalBytes()); + assertScan(refs, read(table)); + } + + @SuppressWarnings("boxing") + @Test + public void noIndexSeek() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 567; i++) { + refs.add(ref(String.format("refs/heads/%03d", i), i)); + } + + byte[] table = write(refs); + assertEquals(0, stats.refIndexLevels()); + assertSeek(refs, read(table)); + } + + @Test + public void withReflog() throws IOException { + Ref master = ref(MASTER, 1); + Ref next = ref(NEXT, 2); + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + String msg = "test"; + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(buffer); + + writer.writeRef(master); + writer.writeRef(next); + + writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg); + writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg); + + writer.finish(); + byte[] table = buffer.toByteArray(); + assertEquals(247, table.length); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertTrue(rc.next()); + assertEquals(MASTER, rc.getRef().getName()); + assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(1, rc.getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals(NEXT, rc.getRef().getName()); + assertEquals(id(2), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + try (LogCursor lc = t.allLogs()) { + assertTrue(lc.next()); + assertEquals(MASTER, lc.getRefName()); + assertEquals(1, lc.getUpdateIndex()); + assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId()); + assertEquals(id(1), lc.getReflogEntry().getNewId()); + assertEquals(who, lc.getReflogEntry().getWho()); + assertEquals(msg, lc.getReflogEntry().getComment()); + + assertTrue(lc.next()); + assertEquals(NEXT, lc.getRefName()); + assertEquals(1, lc.getUpdateIndex()); + assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId()); + assertEquals(id(2), lc.getReflogEntry().getNewId()); + assertEquals(who, lc.getReflogEntry().getWho()); + assertEquals(msg, lc.getReflogEntry().getComment()); + + assertFalse(lc.next()); + } + } + + @Test + public void onlyReflog() throws IOException { + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + String msg = "test"; + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter() + .setMinUpdateIndex(1) + .setMaxUpdateIndex(1) + .begin(buffer); + writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg); + writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg); + writer.finish(); + byte[] table = buffer.toByteArray(); + stats = writer.getStats(); + assertEquals(170, table.length); + assertEquals(0, stats.refCount()); + assertEquals(0, stats.refBytes()); + assertEquals(0, stats.refIndexLevels()); + + ReftableReader t = read(table); + try (RefCursor rc = t.allRefs()) { + assertFalse(rc.next()); + } + try (RefCursor rc = t.seekRef("refs/heads/")) { + assertFalse(rc.next()); + } + try (LogCursor lc = t.allLogs()) { + assertTrue(lc.next()); + assertEquals(MASTER, lc.getRefName()); + assertEquals(1, lc.getUpdateIndex()); + assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId()); + assertEquals(id(1), lc.getReflogEntry().getNewId()); + assertEquals(who, lc.getReflogEntry().getWho()); + assertEquals(msg, lc.getReflogEntry().getComment()); + + assertTrue(lc.next()); + assertEquals(NEXT, lc.getRefName()); + assertEquals(1, lc.getUpdateIndex()); + assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId()); + assertEquals(id(2), lc.getReflogEntry().getNewId()); + assertEquals(who, lc.getReflogEntry().getWho()); + assertEquals(msg, lc.getReflogEntry().getComment()); + + assertFalse(lc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void logScan() throws IOException { + ReftableConfig cfg = new ReftableConfig(); + cfg.setRefBlockSize(256); + cfg.setLogBlockSize(2048); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter(cfg); + writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin(buffer); + + List refs = new ArrayList<>(); + for (int i = 1; i <= 5670; i++) { + Ref ref = ref(String.format("refs/heads/%03d", i), i); + refs.add(ref); + writer.writeRef(ref); + } + + PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + for (Ref ref : refs) { + writer.writeLog(ref.getName(), 1, who, + ObjectId.zeroId(), ref.getObjectId(), + "create " + ref.getName()); + } + writer.finish(); + stats = writer.getStats(); + assertTrue(stats.logBytes() > 4096); + byte[] table = buffer.toByteArray(); + + ReftableReader t = read(table); + try (LogCursor lc = t.allLogs()) { + for (Ref exp : refs) { + assertTrue("has " + exp.getName(), lc.next()); + assertEquals(exp.getName(), lc.getRefName()); + ReflogEntry entry = lc.getReflogEntry(); + assertNotNull(entry); + assertEquals(who, entry.getWho()); + assertEquals(ObjectId.zeroId(), entry.getOldId()); + assertEquals(exp.getObjectId(), entry.getNewId()); + assertEquals("create " + exp.getName(), entry.getComment()); + } + assertFalse(lc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void byObjectIdOneRefNoIndex() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 200; i++) { + refs.add(ref(String.format("refs/heads/%02d", i), i)); + } + refs.add(ref("refs/heads/master", 100)); + + ReftableReader t = read(write(refs)); + assertEquals(0, stats.objIndexSize()); + + try (RefCursor rc = t.byObjectId(id(42))) { + assertTrue("has 42", rc.next()); + assertEquals("refs/heads/42", rc.getRef().getName()); + assertEquals(id(42), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + try (RefCursor rc = t.byObjectId(id(100))) { + assertTrue("has 100", rc.next()); + assertEquals("refs/heads/100", rc.getRef().getName()); + assertEquals(id(100), rc.getRef().getObjectId()); + + assertTrue("has master", rc.next()); + assertEquals("refs/heads/master", rc.getRef().getName()); + assertEquals(id(100), rc.getRef().getObjectId()); + + assertFalse(rc.next()); + } + } + + @SuppressWarnings("boxing") + @Test + public void byObjectIdOneRefWithIndex() throws IOException { + List refs = new ArrayList<>(); + for (int i = 1; i <= 5200; i++) { + refs.add(ref(String.format("refs/heads/%02d", i), i)); + } + refs.add(ref("refs/heads/master", 100)); + + ReftableReader t = read(write(refs)); + assertTrue(stats.objIndexSize() > 0); + + try (RefCursor rc = t.byObjectId(id(42))) { + assertTrue("has 42", rc.next()); + assertEquals("refs/heads/42", rc.getRef().getName()); + assertEquals(id(42), rc.getRef().getObjectId()); + assertFalse(rc.next()); + } + try (RefCursor rc = t.byObjectId(id(100))) { + assertTrue("has 100", rc.next()); + assertEquals("refs/heads/100", rc.getRef().getName()); + assertEquals(id(100), rc.getRef().getObjectId()); + + assertTrue("has master", rc.next()); + assertEquals("refs/heads/master", rc.getRef().getName()); + assertEquals(id(100), rc.getRef().getObjectId()); + + assertFalse(rc.next()); + } + } + + @Test + public void unpeeledDoesNotWrite() { + try { + write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1))); + fail("expected IOException"); + } catch (IOException e) { + assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage()); + } + } + + @Test + public void nameTooLongDoesNotWrite() throws IOException { + try { + ReftableConfig cfg = new ReftableConfig(); + cfg.setRefBlockSize(64); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter(cfg).begin(buffer); + writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1)); + writer.finish(); + fail("expected BlockSizeTooSmallException"); + } catch (BlockSizeTooSmallException e) { + assertEquals(85, e.getMinimumBlockSize()); + } + } + + @Test + public void badCrc32() throws IOException { + byte[] table = write(); + table[table.length - 1] = 0x42; + + try { + read(table).seekRef(HEAD); + fail("expected IOException"); + } catch (IOException e) { + assertEquals(JGitText.get().invalidReftableCRC, e.getMessage()); + } + } + + + private static void assertScan(List refs, Reftable t) + throws IOException { + try (RefCursor rc = t.allRefs()) { + for (Ref exp : refs) { + assertTrue("has " + exp.getName(), rc.next()); + Ref act = rc.getRef(); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + } + assertFalse(rc.next()); + } + } + + private static void assertSeek(List refs, Reftable t) + throws IOException { + for (Ref exp : refs) { + try (RefCursor rc = t.seekRef(exp.getName())) { + assertTrue("has " + exp.getName(), rc.next()); + Ref act = rc.getRef(); + assertEquals(exp.getName(), act.getName()); + assertEquals(exp.getObjectId(), act.getObjectId()); + assertFalse(rc.next()); + } + } + } + + private static Ref ref(String name, int id) { + return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id)); + } + + private static Ref tag(String name, int id1, int id2) { + return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2)); + } + + private static Ref sym(String name, String target) { + return new SymbolicRef(name, newRef(target)); + } + + private static Ref newRef(String name) { + return new ObjectIdRef.Unpeeled(NEW, name, null); + } + + private static ObjectId id(int i) { + byte[] buf = new byte[OBJECT_ID_LENGTH]; + buf[0] = (byte) (i & 0xff); + buf[1] = (byte) ((i >>> 8) & 0xff); + buf[2] = (byte) ((i >>> 16) & 0xff); + buf[3] = (byte) (i >>> 24); + return ObjectId.fromRaw(buf); + } + + private static ReftableReader read(byte[] table) { + return new ReftableReader(BlockSource.from(table)); + } + + private byte[] write(Ref... refs) throws IOException { + return write(Arrays.asList(refs)); + } + + private byte[] write(Collection refs) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + stats = new ReftableWriter() + .begin(buffer) + .sortAndWriteRefs(refs) + .finish() + .getStats(); + return buffer.toByteArray(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.ORIG_HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.junit.Before; +import org.junit.Test; + +public class LocalDiskRefTreeDatabaseTest extends LocalDiskRepositoryTestCase { + private FileRepository repo; + private RefTreeDatabase refdb; + private RefDatabase bootstrap; + + private TestRepository testRepo; + private RevCommit A; + private RevCommit B; + + @Override + @Before + public void setUp() throws Exception { + FileRepository init = createWorkRepository(); + FileBasedConfig cfg = init.getConfig(); + cfg.setInt("core", null, "repositoryformatversion", 1); + cfg.setString("extensions", null, "refStorage", "reftree"); + cfg.save(); + + repo = (FileRepository) new FileRepositoryBuilder() + .setGitDir(init.getDirectory()) + .build(); + refdb = (RefTreeDatabase) repo.getRefDatabase(); + bootstrap = refdb.getBootstrap(); + addRepoToClose(repo); + + RefUpdate head = refdb.newUpdate(HEAD, true); + head.link(R_HEADS + MASTER); + + testRepo = new TestRepository<>(init); + A = testRepo.commit().create(); + B = testRepo.commit(testRepo.getRevWalk().parseCommit(A)); + } + + @Test + public void testHeadOrigHead() throws IOException { + RefUpdate master = refdb.newUpdate(HEAD, false); + master.setExpectedOldObjectId(ObjectId.zeroId()); + master.setNewObjectId(A); + assertEquals(RefUpdate.Result.NEW, master.update()); + assertEquals(A, refdb.exactRef(HEAD).getObjectId()); + + RefUpdate orig = refdb.newUpdate(ORIG_HEAD, true); + orig.setNewObjectId(B); + assertEquals(RefUpdate.Result.NEW, orig.update()); + + File origFile = new File(repo.getDirectory(), ORIG_HEAD); + assertEquals(B.name() + '\n', read(origFile)); + assertEquals(B, bootstrap.exactRef(ORIG_HEAD).getObjectId()); + assertEquals(B, refdb.exactRef(ORIG_HEAD).getObjectId()); + assertFalse(refdb.getRefs(ALL).containsKey(ORIG_HEAD)); + + List addl = refdb.getAdditionalRefs(); + assertEquals(2, addl.size()); + assertEquals(ORIG_HEAD, addl.get(1).getName()); + assertEquals(B, addl.get(1).getObjectId()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,720 @@ +/* + * Copyright (C) 2010, 2013, 2016 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.ORIG_HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +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.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Before; +import org.junit.Test; + +public class RefTreeDatabaseTest { + private InMemRefTreeRepo repo; + private RefTreeDatabase refdb; + private RefDatabase bootstrap; + + private TestRepository testRepo; + private RevCommit A; + private RevCommit B; + private RevTag v1_0; + + @Before + public void setUp() throws Exception { + repo = new InMemRefTreeRepo(new DfsRepositoryDescription("test")); + bootstrap = refdb.getBootstrap(); + + testRepo = new TestRepository<>(repo); + A = testRepo.commit().create(); + B = testRepo.commit(testRepo.getRevWalk().parseCommit(A)); + v1_0 = testRepo.tag("v1_0", B); + testRepo.getRevWalk().parseBody(v1_0); + } + + @Test + public void testSupportsAtomic() { + assertTrue(refdb.performsAtomicTransactions()); + } + + @Test + public void testGetRefs_EmptyDatabase() throws IOException { + assertTrue("no references", refdb.getRefs(ALL).isEmpty()); + assertTrue("no references", refdb.getRefs(R_HEADS).isEmpty()); + assertTrue("no references", refdb.getRefs(R_TAGS).isEmpty()); + assertTrue("no references", refdb.getAdditionalRefs().isEmpty()); + } + + @Test + public void testGetAdditionalRefs() throws IOException { + update("refs/heads/master", A); + + List addl = refdb.getAdditionalRefs(); + assertEquals(1, addl.size()); + assertEquals("refs/txn/committed", addl.get(0).getName()); + assertEquals(getTxnCommitted(), addl.get(0).getObjectId()); + } + + @Test + public void testGetRefs_HeadOnOneBranch() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + + Map all = refdb.getRefs(ALL); + assertEquals(2, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + assertTrue("has master", all.containsKey("refs/heads/master")); + + Ref head = all.get(HEAD); + Ref master = all.get("refs/heads/master"); + + assertEquals(HEAD, head.getName()); + assertTrue(head.isSymbolic()); + assertSame(LOOSE, head.getStorage()); + assertSame("uses same ref as target", master, head.getTarget()); + + assertEquals("refs/heads/master", master.getName()); + assertFalse(master.isSymbolic()); + assertSame(PACKED, master.getStorage()); + assertEquals(A, master.getObjectId()); + } + + @Test + public void testGetRefs_DetachedHead() throws IOException { + update(HEAD, A); + + Map all = refdb.getRefs(ALL); + assertEquals(1, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + + Ref head = all.get(HEAD); + assertEquals(HEAD, head.getName()); + assertFalse(head.isSymbolic()); + assertSame(PACKED, head.getStorage()); + assertEquals(A, head.getObjectId()); + } + + @Test + public void testGetRefs_DeeplyNestedBranch() throws IOException { + String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k"; + update(name, A); + + Map all = refdb.getRefs(ALL); + assertEquals(1, all.size()); + + Ref r = all.get(name); + assertEquals(name, r.getName()); + assertFalse(r.isSymbolic()); + assertSame(PACKED, r.getStorage()); + assertEquals(A, r.getObjectId()); + } + + @Test + public void testGetRefs_HeadBranchNotBorn() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + + Map all = refdb.getRefs(ALL); + assertEquals(2, all.size()); + assertFalse("no HEAD", all.containsKey(HEAD)); + + Ref a = all.get("refs/heads/A"); + Ref b = all.get("refs/heads/B"); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + } + + @Test + public void testGetRefs_HeadsOnly() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + update("refs/tags/v1.0", v1_0); + + Map heads = refdb.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + Ref a = heads.get("A"); + Ref b = heads.get("B"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + } + + @Test + public void testGetRefs_TagsOnly() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + update("refs/tags/v1.0", v1_0); + + Map tags = refdb.getRefs(R_TAGS); + assertEquals(1, tags.size()); + + Ref a = tags.get("v1.0"); + assertEquals("refs/tags/v1.0", a.getName()); + assertEquals(v1_0, a.getObjectId()); + assertTrue(a.isPeeled()); + assertEquals(v1_0.getObject(), a.getPeeledObjectId()); + } + + @Test + public void testGetRefs_HeadsSymref() throws IOException { + symref("refs/heads/other", "refs/heads/master"); + update("refs/heads/master", A); + + Map heads = refdb.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + Ref master = heads.get("master"); + Ref other = heads.get("other"); + + assertEquals("refs/heads/master", master.getName()); + assertEquals(A, master.getObjectId()); + + assertEquals("refs/heads/other", other.getName()); + assertEquals(A, other.getObjectId()); + assertSame(master, other.getTarget()); + } + + @Test + public void testGetRefs_InvalidPrefixes() throws IOException { + update("refs/heads/A", A); + + assertTrue("empty refs/heads", refdb.getRefs("refs/heads").isEmpty()); + assertTrue("empty objects", refdb.getRefs("objects").isEmpty()); + assertTrue("empty objects/", refdb.getRefs("objects/").isEmpty()); + } + + @Test + public void testGetRefs_DiscoversNew() throws IOException { + update("refs/heads/master", A); + Map orig = refdb.getRefs(ALL); + + update("refs/heads/next", B); + Map next = refdb.getRefs(ALL); + + assertEquals(1, orig.size()); + assertEquals(2, next.size()); + + assertFalse(orig.containsKey("refs/heads/next")); + assertTrue(next.containsKey("refs/heads/next")); + + assertEquals(A, next.get("refs/heads/master").getObjectId()); + assertEquals(B, next.get("refs/heads/next").getObjectId()); + } + + @Test + public void testGetRefs_DiscoversModified() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + + Map all = refdb.getRefs(ALL); + assertEquals(A, all.get(HEAD).getObjectId()); + + update("refs/heads/master", B); + all = refdb.getRefs(ALL); + assertEquals(B, all.get(HEAD).getObjectId()); + assertEquals(B, refdb.exactRef(HEAD).getObjectId()); + } + + @Test + public void testGetRefs_CycleInSymbolicRef() throws IOException { + symref("refs/1", "refs/2"); + symref("refs/2", "refs/3"); + symref("refs/3", "refs/4"); + symref("refs/4", "refs/5"); + symref("refs/5", "refs/end"); + update("refs/end", A); + + Map all = refdb.getRefs(ALL); + Ref r = all.get("refs/1"); + assertNotNull("has 1", r); + + assertEquals("refs/1", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/2", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/3", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/4", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/5", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/end", r.getName()); + assertEquals(A, r.getObjectId()); + assertFalse(r.isSymbolic()); + + symref("refs/5", "refs/6"); + symref("refs/6", "refs/end"); + all = refdb.getRefs(ALL); + assertNull("mising 1 due to cycle", all.get("refs/1")); + assertEquals(A, all.get("refs/2").getObjectId()); + assertEquals(A, all.get("refs/3").getObjectId()); + assertEquals(A, all.get("refs/4").getObjectId()); + assertEquals(A, all.get("refs/5").getObjectId()); + assertEquals(A, all.get("refs/6").getObjectId()); + assertEquals(A, all.get("refs/end").getObjectId()); + } + + @Test + public void testGetRef_NonExistingBranchConfig() throws IOException { + assertNull("find branch config", refdb.getRef("config")); + assertNull("find branch config", refdb.getRef("refs/heads/config")); + } + + @Test + public void testGetRef_FindBranchConfig() throws IOException { + update("refs/heads/config", A); + + for (String t : new String[] { "config", "refs/heads/config" }) { + Ref r = refdb.getRef(t); + assertNotNull("find branch config (" + t + ")", r); + assertEquals("for " + t, "refs/heads/config", r.getName()); + assertEquals("for " + t, A, r.getObjectId()); + } + } + + @Test + public void testFirstExactRef() throws IOException { + update("refs/heads/A", A); + update("refs/tags/v1.0", v1_0); + + Ref a = refdb.firstExactRef("refs/heads/A", "refs/tags/v1.0"); + Ref one = refdb.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 testExactRef_DiscoversModified() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + assertEquals(A, refdb.exactRef(HEAD).getObjectId()); + + update("refs/heads/master", B); + assertEquals(B, refdb.exactRef(HEAD).getObjectId()); + } + + @Test + public void testIsNameConflicting() throws IOException { + update("refs/heads/a/b", A); + update("refs/heads/q", B); + + // new references cannot replace an existing container + assertTrue(refdb.isNameConflicting("refs")); + assertTrue(refdb.isNameConflicting("refs/heads")); + assertTrue(refdb.isNameConflicting("refs/heads/a")); + + // existing reference is not conflicting + assertFalse(refdb.isNameConflicting("refs/heads/a/b")); + + // new references are not conflicting + assertFalse(refdb.isNameConflicting("refs/heads/a/d")); + assertFalse(refdb.isNameConflicting("refs/heads/master")); + + // existing reference must not be used as a container + assertTrue(refdb.isNameConflicting("refs/heads/a/b/c")); + assertTrue(refdb.isNameConflicting("refs/heads/q/master")); + + // refs/txn/ names always conflict. + assertTrue(refdb.isNameConflicting(refdb.getTxnCommitted())); + assertTrue(refdb.isNameConflicting("refs/txn/foo")); + } + + @Test + public void testUpdate_RefusesRefsTxnNamespace() throws IOException { + ObjectId txnId = getTxnCommitted(); + + RefUpdate u = refdb.newUpdate("refs/txn/tmp", false); + u.setNewObjectId(B); + assertEquals(RefUpdate.Result.LOCK_FAILURE, u.update()); + assertEquals(txnId, getTxnCommitted()); + + ReceiveCommand cmd = command(null, B, "refs/txn/tmp"); + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addCommand(cmd); + batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + + assertEquals(REJECTED_OTHER_REASON, cmd.getResult()); + assertEquals(MessageFormat.format(JGitText.get().invalidRefName, + "refs/txn/tmp"), cmd.getMessage()); + assertEquals(txnId, getTxnCommitted()); + } + + @Test + public void testUpdate_RefusesDotLockInRefName() throws IOException { + ObjectId txnId = getTxnCommitted(); + + RefUpdate u = refdb.newUpdate("refs/heads/pu.lock", false); + u.setNewObjectId(B); + assertEquals(RefUpdate.Result.REJECTED, u.update()); + assertEquals(txnId, getTxnCommitted()); + + ReceiveCommand cmd = command(null, B, "refs/heads/pu.lock"); + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addCommand(cmd); + batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + + assertEquals(REJECTED_OTHER_REASON, cmd.getResult()); + assertEquals(JGitText.get().funnyRefname, cmd.getMessage()); + assertEquals(txnId, getTxnCommitted()); + } + + @Test + public void testUpdate_RefusesOrigHeadOnBare() throws IOException { + assertTrue(refdb.getRepository().isBare()); + ObjectId txnId = getTxnCommitted(); + + RefUpdate orig = refdb.newUpdate(ORIG_HEAD, true); + orig.setNewObjectId(B); + assertEquals(RefUpdate.Result.LOCK_FAILURE, orig.update()); + assertEquals(txnId, getTxnCommitted()); + + ReceiveCommand cmd = command(null, B, ORIG_HEAD); + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addCommand(cmd); + batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertEquals(REJECTED_OTHER_REASON, cmd.getResult()); + assertEquals( + MessageFormat.format(JGitText.get().invalidRefName, ORIG_HEAD), + cmd.getMessage()); + assertEquals(txnId, getTxnCommitted()); + } + + @Test + public void testBatchRefUpdate_NonFastForwardAborts() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(B, A, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertEquals(txnId, getTxnCommitted()); + + assertEquals(REJECTED_NONFASTFORWARD, + commands.get(1).getResult()); + assertEquals(REJECTED_OTHER_REASON, + commands.get(0).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(0).getMessage()); + } + + @Test + public void testBatchRefUpdate_ForceUpdate() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(B, A, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + Map refs = refdb.getRefs(ALL); + assertEquals(OK, commands.get(0).getResult()); + assertEquals(OK, commands.get(1).getResult()); + assertEquals( + "[refs/heads/master, refs/heads/masters]", + refs.keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId()); + } + + @Test + public void testBatchRefUpdate_NonFastForwardDoesNotDoExpensiveMergeCheck() + throws IOException { + update("refs/heads/master", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(B, A, "refs/heads/master")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo) { + @Override + public boolean isMergedInto(RevCommit base, RevCommit tip) { + fail("isMergedInto() should not be called"); + return false; + } + }, NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + Map refs = refdb.getRefs(ALL); + assertEquals(OK, commands.get(0).getResult()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } + + @Test + public void testBatchRefUpdate_ConflictCausesAbort() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(null, A, "refs/heads/master/x"), + command(null, A, "refs/heads")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertEquals(txnId, getTxnCommitted()); + + assertEquals(LOCK_FAILURE, commands.get(0).getResult()); + + assertEquals(REJECTED_OTHER_REASON, commands.get(1).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(1).getMessage()); + + assertEquals(REJECTED_OTHER_REASON, commands.get(2).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(2).getMessage()); + } + + @Test + public void testBatchRefUpdate_NoConflictIfDeleted() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(null, A, "refs/heads/masters/x"), + command(B, null, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + assertEquals(OK, commands.get(0).getResult()); + assertEquals(OK, commands.get(1).getResult()); + assertEquals(OK, commands.get(2).getResult()); + + Map refs = refdb.getRefs(ALL); + assertEquals( + "[refs/heads/master, refs/heads/masters/x]", + refs.keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); + } + + private ObjectId getTxnCommitted() throws IOException { + Ref r = bootstrap.exactRef(refdb.getTxnCommitted()); + if (r != null && r.getObjectId() != null) { + return r.getObjectId(); + } + return ObjectId.zeroId(); + } + + private static ReceiveCommand command(AnyObjectId a, AnyObjectId b, + String name) { + return new ReceiveCommand( + a != null ? a.copy() : ObjectId.zeroId(), + b != null ? b.copy() : ObjectId.zeroId(), + name); + } + + private void symref(final String name, final String dst) + throws IOException { + commit(new Function() { + @Override + public boolean apply(ObjectReader reader, RefTree tree) + throws IOException { + Ref old = tree.exactRef(reader, name); + Command n = new Command( + old, + new SymbolicRef( + name, + new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null))); + return tree.apply(Collections.singleton(n)); + } + }); + } + + private void update(final String name, final ObjectId id) + throws IOException { + commit(new Function() { + @Override + public boolean apply(ObjectReader reader, RefTree tree) + throws IOException { + Ref old = tree.exactRef(reader, name); + Command n; + try (RevWalk rw = new RevWalk(repo)) { + n = new Command(old, + Command.toRef(rw, id, null, name, true)); + } + return tree.apply(Collections.singleton(n)); + } + }); + } + + interface Function { + boolean apply(ObjectReader reader, RefTree tree) throws IOException; + } + + private void commit(Function fun) throws IOException { + try (ObjectReader reader = repo.newObjectReader(); + ObjectInserter inserter = repo.newObjectInserter(); + RevWalk rw = new RevWalk(reader)) { + RefUpdate u = bootstrap.newUpdate(refdb.getTxnCommitted(), false); + CommitBuilder cb = new CommitBuilder(); + testRepo.setAuthorAndCommitter(cb); + + Ref ref = bootstrap.exactRef(refdb.getTxnCommitted()); + RefTree tree; + if (ref != null && ref.getObjectId() != null) { + tree = RefTree.read(reader, rw.parseTree(ref.getObjectId())); + cb.setParentId(ref.getObjectId()); + u.setExpectedOldObjectId(ref.getObjectId()); + } else { + tree = RefTree.newEmptyTree(); + u.setExpectedOldObjectId(ObjectId.zeroId()); + } + + assertTrue(fun.apply(reader, tree)); + cb.setTreeId(tree.writeTree(inserter)); + u.setNewObjectId(inserter.insert(cb)); + inserter.flush(); + switch (u.update(rw)) { + case NEW: + case FAST_FORWARD: + break; + default: + fail("Expected " + u.getName() + " to update"); + } + } + } + + private class InMemRefTreeRepo extends InMemoryRepository { + private final RefTreeDatabase refs; + + InMemRefTreeRepo(DfsRepositoryDescription repoDesc) { + super(repoDesc); + refs = new RefTreeDatabase(this, super.getRefDatabase(), + "refs/txn/committed"); + RefTreeDatabaseTest.this.refdb = refs; + } + + @Override + public RefDatabase getRefDatabase() { + return refs; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016, 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +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; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Before; +import org.junit.Test; + +public class RefTreeTest { + private static final String R_MASTER = R_HEADS + "master"; + private InMemoryRepository repo; + private TestRepository git; + + @Before + public void setUp() throws IOException { + repo = new InMemoryRepository(new DfsRepositoryDescription("RefTree")); + git = new TestRepository<>(repo); + } + + @Test + public void testEmptyTree() throws IOException { + RefTree tree = RefTree.newEmptyTree(); + try (ObjectReader reader = repo.newObjectReader()) { + assertNull(HEAD, tree.exactRef(reader, HEAD)); + assertNull("master", tree.exactRef(reader, R_MASTER)); + } + } + + @Test + public void testApplyThenReadMaster() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, id)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertSame(NOT_ATTEMPTED, cmd.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, R_MASTER); + assertNotNull(R_MASTER, m); + assertEquals(R_MASTER, m.getName()); + assertEquals(id, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + } + } + + @Test + public void testUpdateMaster() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id1 = git.blob("A"); + Command cmd1 = new Command(null, ref(R_MASTER, id1)); + assertTrue(tree.apply(Collections.singletonList(cmd1))); + assertSame(NOT_ATTEMPTED, cmd1.getResult()); + + RevBlob id2 = git.blob("B"); + Command cmd2 = new Command(ref(R_MASTER, id1), ref(R_MASTER, id2)); + assertTrue(tree.apply(Collections.singletonList(cmd2))); + assertSame(NOT_ATTEMPTED, cmd2.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, R_MASTER); + assertNotNull(R_MASTER, m); + assertEquals(R_MASTER, m.getName()); + assertEquals(id2, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + } + } + + @Test + public void testHeadSymref() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + Command cmd1 = new Command(null, ref(R_MASTER, id)); + Command cmd2 = new Command(null, symref(HEAD, R_MASTER)); + assertTrue(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(NOT_ATTEMPTED, cmd1.getResult()); + assertSame(NOT_ATTEMPTED, cmd2.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, HEAD); + assertNotNull(HEAD, m); + assertEquals(HEAD, m.getName()); + assertTrue("symbolic", m.isSymbolic()); + assertNotNull(m.getTarget()); + assertEquals(R_MASTER, m.getTarget().getName()); + assertEquals(id, m.getTarget().getObjectId()); + } + + // Writing flushes some buffers, re-read from blob. + ObjectId newId = write(tree); + try (ObjectReader reader = repo.newObjectReader(); + RevWalk rw = new RevWalk(reader)) { + tree = RefTree.read(reader, rw.parseTree(newId)); + Ref m = tree.exactRef(reader, HEAD); + assertEquals(R_MASTER, m.getTarget().getName()); + } + } + + @Test + public void testTagIsPeeled() throws Exception { + String name = "v1.0"; + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + RevTag tag = git.tag(name, id); + + String ref = R_TAGS + name; + Command cmd = create(ref, tag); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertSame(NOT_ATTEMPTED, cmd.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, ref); + assertNotNull(ref, m); + assertEquals(ref, m.getName()); + assertEquals(tag, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + assertEquals(id, m.getPeeledObjectId()); + } + } + + @Test + public void testApplyAlreadyExists() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create(R_MASTER, b); + Command cmd2 = create(R_MASTER, b); + assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertSame(REJECTED_OTHER_REASON, cmd2.getResult()); + assertEquals(JGitText.get().transactionAborted, cmd2.getMessage()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyWrongOldId() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + RevBlob c = git.blob("C"); + Command cmd1 = update(R_MASTER, b, c); + Command cmd2 = create(R_MASTER, b); + assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertSame(REJECTED_OTHER_REASON, cmd2.getResult()); + assertEquals(JGitText.get().transactionAborted, cmd2.getMessage()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyWrongOldIdButAlreadyCurrentIsNoOp() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + cmd = update(R_MASTER, b, a); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyCannotCreateSubdirectory() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create(R_MASTER + "/fail", b); + assertFalse(tree.apply(Collections.singletonList(cmd1))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyCannotCreateParentRef() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create("refs/heads", b); + assertFalse(tree.apply(Collections.singletonList(cmd1))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertEquals(treeId, write(tree)); + } + + private static Ref ref(String name, ObjectId id) { + return new ObjectIdRef.PeeledNonTag(LOOSE, name, id); + } + + private static Ref symref(String name, String dest) { + Ref d = new ObjectIdRef.PeeledNonTag(NEW, dest, null); + return new SymbolicRef(name, d); + } + + private Command create(String name, ObjectId id) + throws MissingObjectException, IOException { + return update(name, ObjectId.zeroId(), id); + } + + private Command update(String name, ObjectId oldId, ObjectId newId) + throws MissingObjectException, IOException { + try (RevWalk rw = new RevWalk(repo)) { + return new Command(rw, new ReceiveCommand(oldId, newId, name)); + } + } + + private ObjectId write(RefTree tree) throws IOException { + try (ObjectInserter ins = repo.newObjectInserter()) { + ObjectId id = tree.writeTree(ins); + ins.flush(); + return id; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,6 +62,7 @@ import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; @@ -127,25 +128,25 @@ @Test public void resetFromSymref() throws Exception { repo.updateRef("HEAD").link("refs/heads/master"); - Ref head = repo.getRef("HEAD"); + Ref head = repo.exactRef("HEAD"); RevCommit master = tr.branch("master").commit().create(); RevCommit branch = tr.branch("branch").commit().create(); RevCommit detached = tr.commit().create(); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); - assertEquals(master, repo.getRef("refs/heads/master").getObjectId()); - assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId()); + assertEquals(master, repo.exactRef("refs/heads/master").getObjectId()); + assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId()); // Reset to branches preserves symref. tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(master, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); tr.reset("branch"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(branch, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); @@ -153,50 +154,50 @@ // Reset to a SHA-1 detaches. tr.reset(detached); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached.name()); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); // Reset back to a branch remains detached. tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(lastHeadBeforeDetach, head.getObjectId()); assertFalse(head.isSymbolic()); } @Test public void resetFromDetachedHead() throws Exception { - Ref head = repo.getRef("HEAD"); + Ref head = repo.exactRef("HEAD"); RevCommit master = tr.branch("master").commit().create(); RevCommit branch = tr.branch("branch").commit().create(); RevCommit detached = tr.commit().create(); assertNull(head); - assertEquals(master, repo.getRef("refs/heads/master").getObjectId()); - assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId()); + assertEquals(master, repo.exactRef("refs/heads/master").getObjectId()); + assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId()); tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(master, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset("branch"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(branch, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached.name()); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); } @@ -222,7 +223,7 @@ .tick(3) .add("bar", "fixed bar contents") .create(); - assertEquals(amended, repo.getRef("refs/heads/master").getObjectId()); + assertEquals(amended, repo.exactRef("refs/heads/master").getObjectId()); rw.parseBody(amended); assertEquals(1, amended.getParentCount()); @@ -257,7 +258,7 @@ .add("foo", "fixed foo contents") .create(); - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); assertEquals(amended, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); @@ -291,7 +292,7 @@ public void commitToUnbornHead() throws Exception { repo.updateRef("HEAD").link("refs/heads/master"); RevCommit root = tr.branch("HEAD").commit().create(); - Ref ref = repo.getRef(Constants.HEAD); + Ref ref = repo.exactRef(Constants.HEAD); assertEquals(root, ref.getObjectId()); assertTrue(ref.isSymbolic()); assertEquals("refs/heads/master", ref.getTarget().getName()); @@ -307,16 +308,16 @@ RevCommit toPick = tr.commit() .parent(tr.commit().create()) // Can't cherry-pick root. .author(new PersonIdent("Cherrypick Author", "cpa@example.com", - tr.getClock(), tr.getTimeZone())) + tr.getDate(), tr.getTimeZone())) .author(new PersonIdent("Cherrypick Committer", "cpc@example.com", - tr.getClock(), tr.getTimeZone())) + tr.getDate(), tr.getTimeZone())) .message("message to cherry-pick") .add("bar", "bar contents\n") .create(); RevCommit result = tr.cherryPick(toPick); rw.parseBody(result); - Ref headRef = tr.getRepository().getRef("HEAD"); + Ref headRef = tr.getRepository().exactRef("HEAD"); assertEquals(result, headRef.getObjectId()); assertTrue(headRef.isSymbolic()); assertEquals("refs/heads/master", headRef.getLeaf().getName()); @@ -371,7 +372,45 @@ .create(); assertNotEquals(head, toPick); assertNull(tr.cherryPick(toPick)); - assertEquals(head, repo.getRef("HEAD").getObjectId()); + assertEquals(head, repo.exactRef("HEAD").getObjectId()); + } + + @Test + public void reattachToMaster_Race() throws Exception { + RevCommit commit = tr.branch("master").commit().create(); + tr.branch("master").update(commit); + tr.branch("other").update(commit); + repo.updateRef("HEAD").link("refs/heads/master"); + + // Create a detached HEAD that is not an . + tr.reset(commit); + Ref head = repo.exactRef("HEAD"); + assertEquals(commit, head.getObjectId()); + assertFalse(head.isSymbolic()); + + // Try to reattach to master. + RefUpdate refUpdate = repo.updateRef("HEAD"); + + // Make a change during reattachment. + repo.updateRef("HEAD").link("refs/heads/other"); + + assertEquals( + RefUpdate.Result.LOCK_FAILURE, refUpdate.link("refs/heads/master")); + } + + @Test + public void nonRacingChange() throws Exception { + tr.branch("master").update(tr.branch("master").commit().create()); + tr.branch("other").update(tr.branch("other").commit().create()); + repo.updateRef("HEAD").link("refs/heads/master"); + + // Try to update HEAD. + RefUpdate refUpdate = repo.updateRef("HEAD"); + + // Proceed a master. This should not affect changing HEAD. + tr.branch("master").update(tr.branch("master").commit().create()); + + assertEquals(RefUpdate.Result.FORCED, refUpdate.link("refs/heads/other")); } private String blobAsString(AnyObjectId treeish, String path) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -86,7 +86,6 @@ final ObjectId f = i.toObjectId(); assertNotNull(f); assertEquals(ObjectId.fromString(s), f); - assertEquals(f.hashCode(), i.hashCode()); } @Test @@ -101,7 +100,6 @@ final ObjectId f = i.toObjectId(); assertNotNull(f); assertEquals(ObjectId.fromString(s), f); - assertEquals(f.hashCode(), i.hashCode()); } @Test @@ -215,7 +213,7 @@ } @Test - public void testEquals_Short() { + public void testEquals_Short8() { final String s = "7b6e8067"; final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s); final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s); @@ -223,6 +221,18 @@ assertTrue(a.hashCode() == b.hashCode()); assertEquals(b, a); assertEquals(a, b); + } + + @Test + public void testEquals_Short4() { + final String s = "7b6e"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s); + final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s); + assertNotSame(a, b); + assertTrue(a.hashCode() != 0); + assertTrue(a.hashCode() == b.hashCode()); + assertEquals(b, a); + assertEquals(a, b); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,7 +60,7 @@ @Override public void setUp() throws Exception { super.setUp(); - util = new TestRepository(db); + util = new TestRepository<>(db); StoredConfig config = util.getRepository().getConfig(); config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, "master", ConfigConstants.CONFIG_KEY_REMOTE, "origin"); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,12 @@ package org.eclipse.jgit.lib; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.jgit.util.FileUtils.pathToString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -56,25 +62,43 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.text.MessageFormat; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.merge.MergeConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; /** * Test reading of git config */ +@SuppressWarnings("boxing") public class ConfigTest { + // A non-ASCII whitespace character: U+2002 EN QUAD. + private static final char WS = '\u2002'; + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); @After public void tearDown() { @@ -119,7 +143,7 @@ @Test public void test005_PutGetStringList() { Config c = new Config(); - final LinkedList values = new LinkedList(); + final LinkedList values = new LinkedList<>(); values.add("value1"); values.add("value2"); c.setStringList("my", null, "somename", values); @@ -645,28 +669,6 @@ assertTrue("Subsection should contain \"B\"", names.contains("B")); } - @Test - public void testQuotingForSubSectionNames() { - String resultPattern = "[testsection \"{0}\"]\n\ttestname = testvalue\n"; - String result; - - Config config = new Config(); - config.setString("testsection", "testsubsection", "testname", - "testvalue"); - - result = MessageFormat.format(resultPattern, "testsubsection"); - assertEquals(result, config.toText()); - config.clear(); - - config.setString("testsection", "#quotable", "testname", "testvalue"); - result = MessageFormat.format(resultPattern, "#quotable"); - assertEquals(result, config.toText()); - config.clear(); - - config.setString("testsection", "with\"quote", "testname", "testvalue"); - result = MessageFormat.format(resultPattern, "with\\\"quote"); - assertEquals(result, config.toText()); - } @Test public void testNoFinalNewline() throws ConfigInvalidException { @@ -739,6 +741,169 @@ c.getStringList("a", null, "x")); } + @Test + public void testReadMultipleValuesForName() throws ConfigInvalidException { + Config c = parse("[foo]\nbar=false\nbar=true\n"); + assertTrue(c.getBoolean("foo", "bar", false)); + } + + @Test + public void testIncludeInvalidName() throws ConfigInvalidException { + expectedEx.expect(ConfigInvalidException.class); + expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); + parse("[include]\nbar\n"); + } + + @Test + public void testIncludeNoValue() throws ConfigInvalidException { + expectedEx.expect(ConfigInvalidException.class); + expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); + parse("[include]\npath\n"); + } + + @Test + public void testIncludeEmptyValue() throws ConfigInvalidException { + expectedEx.expect(ConfigInvalidException.class); + expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); + parse("[include]\npath=\n"); + } + + @Test + public void testIncludeValuePathNotFound() throws ConfigInvalidException { + // we do not expect an exception, included path not found are ignored + String notFound = "/not/found"; + Config parsed = parse("[include]\npath=" + notFound + "\n"); + assertEquals(1, parsed.getSections().size()); + assertEquals(notFound, parsed.getString("include", null, "path")); + } + + @Test + public void testIncludeValuePathWithTilde() throws ConfigInvalidException { + // we do not expect an exception, included path not supported are + // ignored + String notSupported = "~/someFile"; + Config parsed = parse("[include]\npath=" + notSupported + "\n"); + assertEquals(1, parsed.getSections().size()); + assertEquals(notSupported, parsed.getString("include", null, "path")); + } + + @Test + public void testIncludeValuePathRelative() throws ConfigInvalidException { + // we do not expect an exception, included path not supported are + // ignored + String notSupported = "someRelativeFile"; + Config parsed = parse("[include]\npath=" + notSupported + "\n"); + assertEquals(1, parsed.getSections().size()); + assertEquals(notSupported, parsed.getString("include", null, "path")); + } + + @Test + public void testIncludeTooManyRecursions() throws IOException { + File config = tmp.newFile("config"); + String include = "[include]\npath=" + pathToString(config) + "\n"; + Files.write(config.toPath(), include.getBytes()); + FileBasedConfig fbConfig = new FileBasedConfig(null, config, + FS.DETECTED); + try { + fbConfig.load(); + fail(); + } catch (ConfigInvalidException cie) { + for (Throwable t = cie; t != null; t = t.getCause()) { + if (t.getMessage() + .equals(JGitText.get().tooManyIncludeRecursions)) { + return; + } + } + fail("Expected to find expected exception message: " + + JGitText.get().tooManyIncludeRecursions); + } + } + + @Test + public void testIncludeIsNoop() throws IOException, ConfigInvalidException { + File config = tmp.newFile("config"); + + String fooBar = "[foo]\nbar=true\n"; + Files.write(config.toPath(), fooBar.getBytes()); + + Config parsed = parse("[include]\npath=" + pathToString(config) + "\n"); + assertFalse(parsed.getBoolean("foo", "bar", false)); + } + + @Test + public void testIncludeCaseInsensitiveSection() + throws IOException, ConfigInvalidException { + File included = tmp.newFile("included"); + String content = "[foo]\nbar=true\n"; + Files.write(included.toPath(), content.getBytes()); + + File config = tmp.newFile("config"); + content = "[Include]\npath=" + pathToString(included) + "\n"; + Files.write(config.toPath(), content.getBytes()); + + FileBasedConfig fbConfig = new FileBasedConfig(null, config, + FS.DETECTED); + fbConfig.load(); + assertTrue(fbConfig.getBoolean("foo", "bar", false)); + } + + @Test + public void testIncludeCaseInsensitiveKey() + throws IOException, ConfigInvalidException { + File included = tmp.newFile("included"); + String content = "[foo]\nbar=true\n"; + Files.write(included.toPath(), content.getBytes()); + + File config = tmp.newFile("config"); + content = "[include]\nPath=" + pathToString(included) + "\n"; + Files.write(config.toPath(), content.getBytes()); + + FileBasedConfig fbConfig = new FileBasedConfig(null, config, + FS.DETECTED); + fbConfig.load(); + assertTrue(fbConfig.getBoolean("foo", "bar", false)); + } + + @Test + public void testIncludeExceptionContainsLine() { + try { + parse("[include]\npath=\n"); + fail("Expected ConfigInvalidException"); + } catch (ConfigInvalidException e) { + assertTrue( + "Expected to find the problem line in the exception message", + e.getMessage().contains("include.path")); + } + } + + @Test + public void testIncludeExceptionContainsFile() throws IOException { + File included = tmp.newFile("included"); + String includedPath = pathToString(included); + String content = "[include]\npath=\n"; + Files.write(included.toPath(), content.getBytes()); + + File config = tmp.newFile("config"); + String include = "[include]\npath=" + includedPath + "\n"; + Files.write(config.toPath(), include.getBytes()); + FileBasedConfig fbConfig = new FileBasedConfig(null, config, + FS.DETECTED); + try { + fbConfig.load(); + fail("Expected ConfigInvalidException"); + } catch (ConfigInvalidException e) { + // Check that there is some exception in the chain that contains + // includedPath + for (Throwable t = e; t != null; t = t.getCause()) { + if (t.getMessage().contains(includedPath)) { + return; + } + } + fail("Expected to find the path in the exception message: " + + includedPath); + } + } + private static void assertReadLong(long exp) throws ConfigInvalidException { assertReadLong(exp, String.valueOf(exp)); } @@ -760,4 +925,296 @@ c.fromText(content); return c; } + + @Test + public void testTimeUnit() throws ConfigInvalidException { + assertEquals(0, parseTime("0", MILLISECONDS)); + assertEquals(2, parseTime("2ms", MILLISECONDS)); + assertEquals(200, parseTime("200 milliseconds", MILLISECONDS)); + + assertEquals(0, parseTime("0s", SECONDS)); + assertEquals(2, parseTime("2s", SECONDS)); + assertEquals(231, parseTime("231sec", SECONDS)); + assertEquals(1, parseTime("1second", SECONDS)); + assertEquals(300, parseTime("300 seconds", SECONDS)); + + assertEquals(2, parseTime("2m", MINUTES)); + assertEquals(2, parseTime("2min", MINUTES)); + assertEquals(1, parseTime("1 minute", MINUTES)); + assertEquals(10, parseTime("10 minutes", MINUTES)); + + assertEquals(5, parseTime("5h", HOURS)); + assertEquals(5, parseTime("5hr", HOURS)); + assertEquals(1, parseTime("1hour", HOURS)); + assertEquals(48, parseTime("48hours", HOURS)); + + assertEquals(5, parseTime("5 h", HOURS)); + assertEquals(5, parseTime("5 hr", HOURS)); + assertEquals(1, parseTime("1 hour", HOURS)); + assertEquals(48, parseTime("48 hours", HOURS)); + assertEquals(48, parseTime("48 \t \r hours", HOURS)); + + assertEquals(4, parseTime("4d", DAYS)); + assertEquals(1, parseTime("1day", DAYS)); + assertEquals(14, parseTime("14days", DAYS)); + + assertEquals(7, parseTime("1w", DAYS)); + assertEquals(7, parseTime("1week", DAYS)); + assertEquals(14, parseTime("2w", DAYS)); + assertEquals(14, parseTime("2weeks", DAYS)); + + assertEquals(30, parseTime("1mon", DAYS)); + assertEquals(30, parseTime("1month", DAYS)); + assertEquals(60, parseTime("2mon", DAYS)); + assertEquals(60, parseTime("2months", DAYS)); + + assertEquals(365, parseTime("1y", DAYS)); + assertEquals(365, parseTime("1year", DAYS)); + assertEquals(365 * 2, parseTime("2years", DAYS)); + } + + private long parseTime(String value, TimeUnit unit) + throws ConfigInvalidException { + Config c = parse("[a]\na=" + value + "\n"); + return c.getTimeUnit("a", null, "a", 0, unit); + } + + @Test + public void testTimeUnitDefaultValue() throws ConfigInvalidException { + // value not present + assertEquals(20, parse("[a]\na=0\n").getTimeUnit("a", null, "b", 20, + MILLISECONDS)); + // value is empty + assertEquals(20, parse("[a]\na=\" \"\n").getTimeUnit("a", null, "a", 20, + MILLISECONDS)); + + // value is not numeric + assertEquals(20, parse("[a]\na=test\n").getTimeUnit("a", null, "a", 20, + MILLISECONDS)); + } + + @Test + public void testTimeUnitInvalid() throws ConfigInvalidException { + expectedEx.expect(IllegalArgumentException.class); + expectedEx + .expectMessage("Invalid time unit value: a.a=1 monttthhh"); + parseTime("1 monttthhh", DAYS); + } + + @Test + public void testTimeUnitInvalidWithSection() throws ConfigInvalidException { + Config c = parse("[a \"b\"]\na=1 monttthhh\n"); + expectedEx.expect(IllegalArgumentException.class); + expectedEx.expectMessage("Invalid time unit value: a.b.a=1 monttthhh"); + c.getTimeUnit("a", "b", "a", 0, DAYS); + } + + @Test + public void testTimeUnitNegative() throws ConfigInvalidException { + expectedEx.expect(IllegalArgumentException.class); + parseTime("-1", MILLISECONDS); + } + + @Test + public void testEscapeSpacesOnly() throws ConfigInvalidException { + // Empty string is read back as null, so this doesn't round-trip. + assertEquals("", Config.escapeValue("")); + + assertValueRoundTrip(" ", "\" \""); + assertValueRoundTrip(" ", "\" \""); + } + + @Test + public void testEscapeLeadingSpace() throws ConfigInvalidException { + assertValueRoundTrip("x", "x"); + assertValueRoundTrip(" x", "\" x\""); + assertValueRoundTrip(" x", "\" x\""); + } + + @Test + public void testEscapeTrailingSpace() throws ConfigInvalidException { + assertValueRoundTrip("x", "x"); + assertValueRoundTrip("x ","\"x \""); + assertValueRoundTrip("x ","\"x \""); + } + + @Test + public void testEscapeLeadingAndTrailingSpace() + throws ConfigInvalidException { + assertValueRoundTrip(" x ", "\" x \""); + assertValueRoundTrip(" x ", "\" x \""); + assertValueRoundTrip(" x ", "\" x \""); + assertValueRoundTrip(" x ", "\" x \""); + } + + @Test + public void testNoEscapeInternalSpaces() throws ConfigInvalidException { + assertValueRoundTrip("x y"); + assertValueRoundTrip("x y"); + assertValueRoundTrip("x y"); + assertValueRoundTrip("x y z"); + assertValueRoundTrip("x " + WS + " y"); + } + + @Test + public void testNoEscapeSpecialCharacters() throws ConfigInvalidException { + assertValueRoundTrip("x\\y", "x\\\\y"); + assertValueRoundTrip("x\"y", "x\\\"y"); + assertValueRoundTrip("x\ny", "x\\ny"); + assertValueRoundTrip("x\ty", "x\\ty"); + assertValueRoundTrip("x\by", "x\\by"); + } + + @Test + public void testParseLiteralBackspace() throws ConfigInvalidException { + // This is round-tripped with an escape sequence by JGit, but C git writes + // it out as a literal backslash. + assertEquals("x\by", parseEscapedValue("x\by")); + } + + @Test + public void testEscapeCommentCharacters() throws ConfigInvalidException { + assertValueRoundTrip("x#y", "\"x#y\""); + assertValueRoundTrip("x;y", "\"x;y\""); + } + + @Test + public void testEscapeValueInvalidCharacters() { + assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y")); + } + + @Test + public void testEscapeSubsectionInvalidCharacters() { + assertIllegalArgumentException(() -> Config.escapeSubsection("x\ny")); + assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y")); + } + + @Test + public void testParseMultipleQuotedRegions() throws ConfigInvalidException { + assertEquals("b a z; \n", parseEscapedValue("b\" a\"\" z; \\n\"")); + } + + @Test + public void testParseComments() throws ConfigInvalidException { + assertEquals("baz", parseEscapedValue("baz; comment")); + assertEquals("baz", parseEscapedValue("baz# comment")); + assertEquals("baz", parseEscapedValue("baz ; comment")); + assertEquals("baz", parseEscapedValue("baz # comment")); + + assertEquals("baz", parseEscapedValue("baz ; comment")); + assertEquals("baz", parseEscapedValue("baz # comment")); + assertEquals("baz", parseEscapedValue("baz " + WS + " ; comment")); + assertEquals("baz", parseEscapedValue("baz " + WS + " # comment")); + + assertEquals("baz ", parseEscapedValue("\"baz \"; comment")); + assertEquals("baz ", parseEscapedValue("\"baz \"# comment")); + assertEquals("baz ", parseEscapedValue("\"baz \" ; comment")); + assertEquals("baz ", parseEscapedValue("\"baz \" # comment")); + } + + @Test + public void testEscapeSubsection() throws ConfigInvalidException { + assertSubsectionRoundTrip("", "\"\""); + assertSubsectionRoundTrip("x", "\"x\""); + assertSubsectionRoundTrip(" x", "\" x\""); + assertSubsectionRoundTrip("x ", "\"x \""); + assertSubsectionRoundTrip(" x ", "\" x \""); + assertSubsectionRoundTrip("x y", "\"x y\""); + assertSubsectionRoundTrip("x y", "\"x y\""); + assertSubsectionRoundTrip("x\\y", "\"x\\\\y\""); + assertSubsectionRoundTrip("x\"y", "\"x\\\"y\""); + + // Unlike for values, \b and \t are not escaped. + assertSubsectionRoundTrip("x\by", "\"x\by\""); + assertSubsectionRoundTrip("x\ty", "\"x\ty\""); + } + + @Test + public void testParseInvalidValues() { + assertInvalidValue(JGitText.get().newlineInQuotesNotAllowed, "x\"\n\"y"); + assertInvalidValue(JGitText.get().endOfFileInEscape, "x\\"); + assertInvalidValue( + MessageFormat.format(JGitText.get().badEscape, 'q'), "x\\q"); + } + + @Test + public void testParseInvalidSubsections() { + assertInvalidSubsection( + JGitText.get().newlineInQuotesNotAllowed, "\"x\ny\""); + } + + @Test + public void testDropBackslashFromInvalidEscapeSequenceInSubsectionName() + throws ConfigInvalidException { + assertEquals("x0", parseEscapedSubsection("\"x\\0\"")); + assertEquals("xq", parseEscapedSubsection("\"x\\q\"")); + // Unlike for values, \b, \n, and \t are not valid escape sequences. + assertEquals("xb", parseEscapedSubsection("\"x\\b\"")); + assertEquals("xn", parseEscapedSubsection("\"x\\n\"")); + assertEquals("xt", parseEscapedSubsection("\"x\\t\"")); + } + + private static void assertValueRoundTrip(String value) + throws ConfigInvalidException { + assertValueRoundTrip(value, value); + } + + private static void assertValueRoundTrip(String value, String expectedEscaped) + throws ConfigInvalidException { + String escaped = Config.escapeValue(value); + assertEquals("escape failed;", expectedEscaped, escaped); + assertEquals("parse failed;", value, parseEscapedValue(escaped)); + } + + private static String parseEscapedValue(String escapedValue) + throws ConfigInvalidException { + String text = "[foo]\nbar=" + escapedValue; + Config c = parse(text); + return c.getString("foo", null, "bar"); + } + + private static void assertInvalidValue(String expectedMessage, + String escapedValue) { + try { + parseEscapedValue(escapedValue); + fail("expected ConfigInvalidException"); + } catch (ConfigInvalidException e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + private static void assertSubsectionRoundTrip(String subsection, + String expectedEscaped) throws ConfigInvalidException { + String escaped = Config.escapeSubsection(subsection); + assertEquals("escape failed;", expectedEscaped, escaped); + assertEquals("parse failed;", subsection, parseEscapedSubsection(escaped)); + } + + private static String parseEscapedSubsection(String escapedSubsection) + throws ConfigInvalidException { + String text = "[foo " + escapedSubsection + "]\nbar = value"; + Config c = parse(text); + Set subsections = c.getSubsections("foo"); + assertEquals("only one section", 1, subsections.size()); + return subsections.iterator().next(); + } + + private static void assertIllegalArgumentException(Runnable r) { + try { + r.run(); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + private static void assertInvalidSubsection(String expectedMessage, + String escapedSubsection) { + try { + parseEscapedSubsection(escapedSubsection); + fail("expected ConfigInvalidException"); + } catch (ConfigInvalidException e) { + assertEquals(expectedMessage, e.getMessage()); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ import static org.junit.Assert.fail; import java.io.UnsupportedEncodingException; + import org.junit.Test; public class ConstantsEncodingTest { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -338,68 +338,71 @@ */ private void testMaliciousPath(boolean good, boolean secondCheckout, String... path) throws GitAPIException, IOException { - Git git = new Git(db); - ObjectInserter newObjectInserter; - newObjectInserter = git.getRepository().newObjectInserter(); - ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB, - "data".getBytes()); - newObjectInserter = git.getRepository().newObjectInserter(); - FileMode mode = FileMode.REGULAR_FILE; - ObjectId insertId = blobId; - for (int i = path.length - 1; i >= 0; --i) { - TreeFormatter treeFormatter = new TreeFormatter(); - treeFormatter.append("goodpath", mode, insertId); - insertId = newObjectInserter.insert(treeFormatter); - mode = FileMode.TREE; - } - newObjectInserter = git.getRepository().newObjectInserter(); - CommitBuilder commitBuilder = new CommitBuilder(); - commitBuilder.setAuthor(author); - commitBuilder.setCommitter(committer); - commitBuilder.setMessage("foo#1"); - commitBuilder.setTreeId(insertId); - ObjectId firstCommitId = newObjectInserter.insert(commitBuilder); + try (Git git = new Git(db); + RevWalk revWalk = new RevWalk(git.getRepository())) { + ObjectInserter newObjectInserter; + newObjectInserter = git.getRepository().newObjectInserter(); + ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB, + "data".getBytes()); + newObjectInserter = git.getRepository().newObjectInserter(); + FileMode mode = FileMode.REGULAR_FILE; + ObjectId insertId = blobId; + for (int i = path.length - 1; i >= 0; --i) { + TreeFormatter treeFormatter = new TreeFormatter(); + treeFormatter.append("goodpath", mode, insertId); + insertId = newObjectInserter.insert(treeFormatter); + mode = FileMode.TREE; + } + newObjectInserter = git.getRepository().newObjectInserter(); + CommitBuilder commitBuilder = new CommitBuilder(); + commitBuilder.setAuthor(author); + commitBuilder.setCommitter(committer); + commitBuilder.setMessage("foo#1"); + commitBuilder.setTreeId(insertId); + ObjectId firstCommitId = newObjectInserter.insert(commitBuilder); - newObjectInserter = git.getRepository().newObjectInserter(); - mode = FileMode.REGULAR_FILE; - insertId = blobId; - for (int i = path.length - 1; i >= 0; --i) { - TreeFormatter treeFormatter = new TreeFormatter(); - treeFormatter.append(path[i], mode, insertId); - insertId = newObjectInserter.insert(treeFormatter); - mode = FileMode.TREE; - } + newObjectInserter = git.getRepository().newObjectInserter(); + mode = FileMode.REGULAR_FILE; + insertId = blobId; + for (int i = path.length - 1; i >= 0; --i) { + TreeFormatter treeFormatter = new TreeFormatter(); + treeFormatter.append(path[i].getBytes(), 0, + path[i].getBytes().length, + mode, insertId, true); + insertId = newObjectInserter.insert(treeFormatter); + mode = FileMode.TREE; + } - // Create another commit - commitBuilder = new CommitBuilder(); - commitBuilder.setAuthor(author); - commitBuilder.setCommitter(committer); - commitBuilder.setMessage("foo#2"); - commitBuilder.setTreeId(insertId); - commitBuilder.setParentId(firstCommitId); - ObjectId commitId = newObjectInserter.insert(commitBuilder); + // Create another commit + commitBuilder = new CommitBuilder(); + commitBuilder.setAuthor(author); + commitBuilder.setCommitter(committer); + commitBuilder.setMessage("foo#2"); + commitBuilder.setTreeId(insertId); + commitBuilder.setParentId(firstCommitId); + ObjectId commitId = newObjectInserter.insert(commitBuilder); - RevWalk revWalk = new RevWalk(git.getRepository()); - if (!secondCheckout) - git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId)) - .setName("refs/heads/master").setCreateBranch(true).call(); - try { - if (secondCheckout) { - git.checkout().setStartPoint(revWalk.parseCommit(commitId)) - .setName("refs/heads/master").setCreateBranch(true) - .call(); - } else { - git.branchCreate().setName("refs/heads/next") - .setStartPoint(commitId.name()).call(); - git.checkout().setName("refs/heads/next") - .call(); + if (!secondCheckout) + git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId)) + .setName("refs/heads/master").setCreateBranch(true).call(); + try { + if (secondCheckout) { + git.checkout().setStartPoint(revWalk.parseCommit(commitId)) + .setName("refs/heads/master").setCreateBranch(true) + .call(); + } else { + git.branchCreate().setName("refs/heads/next") + .setStartPoint(commitId.name()).call(); + git.checkout().setName("refs/heads/next") + .call(); + } + if (!good) + fail("Checkout of Tree " + Arrays.asList(path) + " should fail"); + } catch (InvalidPathException e) { + if (good) + throw e; + assertTrue(e.getMessage().startsWith("Invalid path")); } - if (!good) - fail("Checkout of Tree " + Arrays.asList(path) + " should fail"); - } catch (InvalidPathException e) { - if (good) - throw e; - assertTrue(e.getMessage().startsWith("Invalid path")); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -65,12 +65,15 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.events.ChangeRecorder; +import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; @@ -78,6 +81,8 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; import org.junit.Test; public class DirCacheCheckoutTest extends RepositoryTestCase { @@ -111,7 +116,7 @@ return dco.getRemoved(); } - private Map getUpdated() { + private Map getUpdated() { return dco.getUpdated(); } @@ -127,7 +132,7 @@ if ((args.length % 2) > 0) throw new IllegalArgumentException("needs to be pairs"); - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); for (int i = 0; i < args.length; i += 2) { map.put(args[i], args[i + 1]); } @@ -138,59 +143,83 @@ @Test public void testResetHard() throws IOException, NoFilepatternException, GitAPIException { - Git git = new Git(db); - writeTrashFile("f", "f()"); - writeTrashFile("D/g", "g()"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("inital").call(); - assertIndex(mkmap("f", "f()", "D/g", "g()")); - - git.branchCreate().setName("topic").call(); - - writeTrashFile("f", "f()\nmaster"); - writeTrashFile("D/g", "g()\ng2()"); - writeTrashFile("E/h", "h()"); - git.add().addFilepattern(".").call(); - RevCommit master = git.commit().setMessage("master-1").call(); - assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); - - checkoutBranch("refs/heads/topic"); - assertIndex(mkmap("f", "f()", "D/g", "g()")); - - writeTrashFile("f", "f()\nside"); - assertTrue(new File(db.getWorkTree(), "D/g").delete()); - writeTrashFile("G/i", "i()"); - git.add().addFilepattern(".").call(); - git.add().addFilepattern(".").setUpdate(true).call(); - RevCommit topic = git.commit().setMessage("topic-1").call(); - assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); - - writeTrashFile("untracked", "untracked"); - - resetHard(master); - assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); - resetHard(topic); - assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); - assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked", - "untracked")); - - assertEquals(MergeStatus.CONFLICTING, git.merge().include(master) - .call().getMergeStatus()); - assertEquals( - "[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]", - indexState(0)); - - resetHard(master); - assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); - assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", - "h()", "untracked", "untracked")); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + writeTrashFile("f", "f()"); + writeTrashFile("D/g", "g()"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("inital").call(); + assertIndex(mkmap("f", "f()", "D/g", "g()")); + recorder.assertNoEvent(); + git.branchCreate().setName("topic").call(); + recorder.assertNoEvent(); + + writeTrashFile("f", "f()\nmaster"); + writeTrashFile("D/g", "g()\ng2()"); + writeTrashFile("E/h", "h()"); + git.add().addFilepattern(".").call(); + RevCommit master = git.commit().setMessage("master-1").call(); + assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); + recorder.assertNoEvent(); + + checkoutBranch("refs/heads/topic"); + assertIndex(mkmap("f", "f()", "D/g", "g()")); + recorder.assertEvent(new String[] { "f", "D/g" }, + new String[] { "E/h" }); + + writeTrashFile("f", "f()\nside"); + assertTrue(new File(db.getWorkTree(), "D/g").delete()); + writeTrashFile("G/i", "i()"); + git.add().addFilepattern(".").call(); + git.add().addFilepattern(".").setUpdate(true).call(); + RevCommit topic = git.commit().setMessage("topic-1").call(); + assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); + recorder.assertNoEvent(); + + writeTrashFile("untracked", "untracked"); + + resetHard(master); + assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); + recorder.assertEvent(new String[] { "f", "D/g", "E/h" }, + new String[] { "G", "G/i" }); + + resetHard(topic); + assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); + assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked", + "untracked")); + recorder.assertEvent(new String[] { "f", "G/i" }, + new String[] { "D", "D/g", "E", "E/h" }); + + assertEquals(MergeStatus.CONFLICTING, git.merge().include(master) + .call().getMergeStatus()); + assertEquals( + "[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]", + indexState(0)); + recorder.assertEvent(new String[] { "f", "D/g", "E/h" }, + ChangeRecorder.EMPTY); + + resetHard(master); + assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); + assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", + "h()", "untracked", "untracked")); + recorder.assertEvent(new String[] { "f", "D/g" }, + new String[] { "G", "G/i" }); + + } finally { + if (handle != null) { + handle.remove(); + } + } } /** * Reset hard from unclean condition. *

        - * WorkDir: Empty
        - * Index: f/g
        + * WorkDir: Empty
        + * Index: f/g
        * Merge: x * * @throws Exception @@ -198,21 +227,32 @@ @Test public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile() throws Exception { - Git git = new Git(db); - writeTrashFile("x", "x"); - git.add().addFilepattern("x").call(); - RevCommit id1 = git.commit().setMessage("c1").call(); - - writeTrashFile("f/g", "f/g"); - git.rm().addFilepattern("x").call(); - git.add().addFilepattern("f/g").call(); - git.commit().setMessage("c2").call(); - deleteTrashFile("f/g"); - deleteTrashFile("f"); - - // The actual test - git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call(); - assertIndex(mkmap("x", "x")); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + writeTrashFile("x", "x"); + git.add().addFilepattern("x").call(); + RevCommit id1 = git.commit().setMessage("c1").call(); + + writeTrashFile("f/g", "f/g"); + git.rm().addFilepattern("x").call(); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" }); + git.add().addFilepattern("f/g").call(); + git.commit().setMessage("c2").call(); + deleteTrashFile("f/g"); + deleteTrashFile("f"); + + // The actual test + git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call(); + assertIndex(mkmap("x", "x")); + recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY); + } finally { + if (handle != null) { + handle.remove(); + } + } } /** @@ -222,14 +262,23 @@ */ @Test public void testInitialCheckout() throws Exception { - Git git = new Git(db); - - TestRepository db_t = new TestRepository(db); - BranchBuilder master = db_t.branch("master"); - master.commit().add("f", "1").message("m0").create(); - assertFalse(new File(db.getWorkTree(), "f").exists()); - git.checkout().setName("master").call(); - assertTrue(new File(db.getWorkTree(), "f").exists()); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + TestRepository db_t = new TestRepository<>(db); + BranchBuilder master = db_t.branch("master"); + master.commit().add("f", "1").message("m0").create(); + assertFalse(new File(db.getWorkTree(), "f").exists()); + git.checkout().setName("master").call(); + assertTrue(new File(db.getWorkTree(), "f").exists()); + recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY); + } finally { + if (handle != null) { + handle.remove(); + } + } } private DirCacheCheckout resetHard(RevCommit commit) @@ -266,8 +315,6 @@ @Test public void testRules1thru3_NoIndexEntry() throws IOException { ObjectId head = buildTree(mk("foo")); - TreeWalk tw = TreeWalk.forPath(db, "foo", head); - ObjectId objectId = tw.getObjectId(0); ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE, new byte[0]); @@ -277,10 +324,9 @@ prescanTwoTrees(merge, head); - assertEquals(objectId, getUpdated().get("foo")); + assertTrue(getUpdated().containsKey("foo")); merge = buildTree(mkmap("foo", "a")); - tw = TreeWalk.forPath(db, "foo", merge); prescanTwoTrees(head, merge); @@ -375,7 +421,7 @@ // rules 4 and 5 HashMap idxMap; - idxMap = new HashMap(); + idxMap = new HashMap<>(); idxMap.put("foo", "foo"); setupCase(null, null, idxMap); go(); @@ -385,7 +431,7 @@ assertTrue(getConflicts().isEmpty()); // rules 6 and 7 - idxMap = new HashMap(); + idxMap = new HashMap<>(); idxMap.put("foo", "foo"); setupCase(null, idxMap, idxMap); go(); @@ -394,7 +440,7 @@ // rules 8 and 9 HashMap mergeMap; - mergeMap = new HashMap(); + mergeMap = new HashMap<>(); mergeMap.put("foo", "merge"); setupCase(null, mergeMap, idxMap); @@ -406,7 +452,7 @@ // rule 10 - HashMap headMap = new HashMap(); + HashMap headMap = new HashMap<>(); headMap.put("foo", "foo"); setupCase(headMap, null, idxMap); go(); @@ -796,6 +842,7 @@ fail("didn't get the expected exception"); } catch (CheckoutConflictException e) { assertConflict("foo"); + assertEquals("foo", e.getConflictingFiles()[0]); assertWorkDir(mkmap("foo", "bar", "other", "other")); assertIndex(mk("other")); } @@ -883,6 +930,7 @@ assertWorkDir(mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g", "e/g3")); assertConflict("e/g"); + assertEquals("e/g", e.getConflictingFiles()[0]); } } @@ -923,6 +971,383 @@ } @Test + public void testCheckoutChangeLinkToEmptyDir() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with empty directory + FileUtils.delete(link); + FileUtils.mkdir(link); + assertTrue("Link must be a directory now", link.isDirectory()); + + // modify file + writeTrashFile(fname, "b"); + assertWorkDir(mkmap(fname, "b", linkName, "/")); + recorder.assertNoEvent(); + + // revert both paths to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname) + .addPath(linkName).call(); + + assertWorkDir(mkmap(fname, "a", linkName, "a")); + recorder.assertEvent(new String[] { fname, linkName }, + ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeLinkToEmptyDirs() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + FileUtils.mkdirs(new File(link, "dummyDir")); + assertTrue("Link must be a directory now", link.isDirectory()); + + assertFalse("Must not delete non empty directory", link.delete()); + + // modify file + writeTrashFile(fname, "b"); + assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/")); + recorder.assertNoEvent(); + + // revert both paths to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname) + .addPath(linkName).call(); + + assertWorkDir(mkmap(fname, "a", linkName, "a")); + recorder.assertEvent(new String[] { fname, linkName }, + ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + String fname = "file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(linkName) + .call(); + + // expect only the one added to the index + assertWorkDir(mkmap(linkName, "a", fname, "a")); + recorder.assertEvent(new String[] { linkName }, + ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry() + throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + String fname = "file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + + // create and add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + git.add().addFilepattern(linkName + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(linkName) + .call(); + + // original file and link + assertWorkDir(mkmap(linkName, "a", fname, "a")); + recorder.assertEvent(new String[] { linkName }, + ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeFileToEmptyDir() throws Exception { + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + // replace file with empty directory + FileUtils.delete(file); + FileUtils.mkdir(file); + assertTrue("File must be a directory now", file.isDirectory()); + assertWorkDir(mkmap(fname, "/")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + assertWorkDir(mkmap(fname, "a")); + recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeFileToEmptyDirs() throws Exception { + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + FileUtils.mkdirs(new File(file, "dummyDir")); + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + assertWorkDir(mkmap(fname + "/dummyDir", "/")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + assertWorkDir(mkmap(fname, "a")); + recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeFileToNonEmptyDirs() throws Exception { + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + assertWorkDir(mkmap(fname, "a")); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir1", "file1", "c"); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname + "/dir1/file1", "c", + fname + "/dir2/file2", "d")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + + // expect only the one added to the index + assertWorkDir(mkmap(fname, "a")); + recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry() + throws Exception { + String fname = "was_file"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + assertWorkDir(mkmap(fname, "a")); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + + // create and add a file in the new directory to the index + writeTrashFile(fname + "/dir", "file1", "c"); + git.add().addFilepattern(fname + "/dir/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", + "d")); + recorder.assertNoEvent(); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + assertWorkDir(mkmap(fname, "a")); + recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY); + Status st = git.status().call(); + assertTrue(st.isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test public void testCheckoutOutChangesAutoCRLFfalse() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); checkout(); @@ -983,35 +1408,174 @@ } @Test + public void testDontOverwriteEmptyFolder() throws IOException { + setupCase(mk("foo"), mk("foo"), mk("foo")); + FileUtils.mkdir(new File(db.getWorkTree(), "d")); + checkout(); + assertWorkDir(mkmap("foo", "foo", "d", "/")); + } + + @Test public void testOverwriteUntrackedIgnoredFile() throws IOException, GitAPIException { String fname="file.txt"; - Git git = Git.wrap(db); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("create file").call(); + + // Create branch + git.branchCreate().setName("side").call(); + + // Modify file + writeTrashFile(fname, "b"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("modify file").call(); + recorder.assertNoEvent(); + + // Switch branches + git.checkout().setName("side").call(); + recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY); + git.rm().addFilepattern(fname).call(); + recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname }); + writeTrashFile(".gitignore", fname); + git.add().addFilepattern(".gitignore").call(); + git.commit().setMessage("delete and ignore file").call(); + + writeTrashFile(fname, "Something different"); + recorder.assertNoEvent(); + git.checkout().setName("master").call(); + assertWorkDir(mkmap(fname, "b")); + recorder.assertEvent(new String[] { fname }, + new String[] { ".gitignore" }); + assertTrue(git.status().call().isClean()); + } finally { + if (handle != null) { + handle.remove(); + } + } + } - // Add a file - writeTrashFile(fname, "a"); - git.add().addFilepattern(fname).call(); - git.commit().setMessage("create file").call(); - - // Create branch - git.branchCreate().setName("side").call(); - - // Modify file - writeTrashFile(fname, "b"); - git.add().addFilepattern(fname).call(); - git.commit().setMessage("modify file").call(); - - // Switch branches - git.checkout().setName("side").call(); - git.rm().addFilepattern(fname).call(); - writeTrashFile(".gitignore", fname); - git.add().addFilepattern(".gitignore").call(); - git.commit().setMessage("delete and ignore file").call(); - - writeTrashFile(fname, "Something different"); - git.checkout().setName("master").call(); - assertWorkDir(mkmap(fname, "b")); - assertTrue(git.status().call().isClean()); + @Test + public void testOverwriteUntrackedFileModeChange() + throws IOException, GitAPIException { + String fname = "file.txt"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("create file").call(); + assertWorkDir(mkmap(fname, "a")); + + // Create branch + git.branchCreate().setName("side").call(); + + // Switch branches + git.checkout().setName("side").call(); + recorder.assertNoEvent(); + + // replace file with directory containing files + FileUtils.delete(file); + + // create and add a file in the new directory to the index + writeTrashFile(fname + "/dir1", "file1", "c"); + git.add().addFilepattern(fname + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname + "/dir1/file1", "c", + fname + "/dir2/file2", "d")); + + try { + git.checkout().setName("master").call(); + fail("did not throw exception"); + } catch (Exception e) { + // 2 extra files are still there + assertWorkDir(mkmap(fname + "/dir1/file1", "c", + fname + "/dir2/file2", "d")); + } + recorder.assertNoEvent(); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test + public void testOverwriteUntrackedLinkModeChange() + throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + String fname = "file.txt"; + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // Create branch + git.branchCreate().setName("side").call(); + + // Switch branches + git.checkout().setName("side").call(); + recorder.assertNoEvent(); + + // replace link with directory containing files + FileUtils.delete(link); + + // create and add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + git.add().addFilepattern(linkName + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("Link must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + + try { + git.checkout().setName("master").call(); + fail("did not throw exception"); + } catch (Exception e) { + // 2 extra files are still there + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + } + recorder.assertNoEvent(); + } finally { + if (handle != null) { + handle.remove(); + } + } } @Test @@ -1019,36 +1583,47 @@ if (!FS.DETECTED.supportsExecute()) return; - Git git = Git.wrap(db); - - // Add non-executable file - File file = writeTrashFile("file.txt", "a"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit1").call(); - assertFalse(db.getFS().canExecute(file)); - - // Create branch - git.branchCreate().setName("b1").call(); - - // Make file executable - db.getFS().setExecute(file, true); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit2").call(); - - // Verify executable and working directory is clean - Status status = git.status().call(); - assertTrue(status.getModified().isEmpty()); - assertTrue(status.getChanged().isEmpty()); - assertTrue(db.getFS().canExecute(file)); - - // Switch branches - git.checkout().setName("b1").call(); - - // Verify not executable and working directory is clean - status = git.status().call(); - assertTrue(status.getModified().isEmpty()); - assertTrue(status.getChanged().isEmpty()); - assertFalse(db.getFS().canExecute(file)); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add non-executable file + File file = writeTrashFile("file.txt", "a"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit1").call(); + assertFalse(db.getFS().canExecute(file)); + + // Create branch + git.branchCreate().setName("b1").call(); + + // Make file executable + db.getFS().setExecute(file, true); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit2").call(); + recorder.assertNoEvent(); + + // Verify executable and working directory is clean + Status status = git.status().call(); + assertTrue(status.getModified().isEmpty()); + assertTrue(status.getChanged().isEmpty()); + assertTrue(db.getFS().canExecute(file)); + + // Switch branches + git.checkout().setName("b1").call(); + + // Verify not executable and working directory is clean + status = git.status().call(); + assertTrue(status.getModified().isEmpty()); + assertTrue(status.getChanged().isEmpty()); + assertFalse(db.getFS().canExecute(file)); + recorder.assertEvent(new String[] { "file.txt" }, + ChangeRecorder.EMPTY); + } finally { + if (handle != null) { + handle.remove(); + } + } } @Test @@ -1056,41 +1631,50 @@ if (!FS.DETECTED.supportsExecute()) return; - Git git = Git.wrap(db); - - // Add non-executable file - File file = writeTrashFile("file.txt", "a"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit1").call(); - assertFalse(db.getFS().canExecute(file)); - - // Create branch - git.branchCreate().setName("b1").call(); - - // Make file executable - db.getFS().setExecute(file, true); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit2").call(); - - // Verify executable and working directory is clean - Status status = git.status().call(); - assertTrue(status.getModified().isEmpty()); - assertTrue(status.getChanged().isEmpty()); - assertTrue(db.getFS().canExecute(file)); - - writeTrashFile("file.txt", "b"); - - // Switch branches - CheckoutCommand checkout = git.checkout().setName("b1"); - try { - checkout.call(); - fail("Checkout exception not thrown"); - } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) { - CheckoutResult result = checkout.getResult(); - assertNotNull(result); - assertNotNull(result.getConflictList()); - assertEquals(1, result.getConflictList().size()); - assertTrue(result.getConflictList().contains("file.txt")); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add non-executable file + File file = writeTrashFile("file.txt", "a"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit1").call(); + assertFalse(db.getFS().canExecute(file)); + + // Create branch + git.branchCreate().setName("b1").call(); + + // Make file executable + db.getFS().setExecute(file, true); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit2").call(); + + // Verify executable and working directory is clean + Status status = git.status().call(); + assertTrue(status.getModified().isEmpty()); + assertTrue(status.getChanged().isEmpty()); + assertTrue(db.getFS().canExecute(file)); + + writeTrashFile("file.txt", "b"); + + // Switch branches + CheckoutCommand checkout = git.checkout().setName("b1"); + try { + checkout.call(); + fail("Checkout exception not thrown"); + } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) { + CheckoutResult result = checkout.getResult(); + assertNotNull(result); + assertNotNull(result.getConflictList()); + assertEquals(1, result.getConflictList().size()); + assertTrue(result.getConflictList().contains("file.txt")); + } + recorder.assertNoEvent(); + } finally { + if (handle != null) { + handle.remove(); + } } } @@ -1100,40 +1684,52 @@ if (!FS.DETECTED.supportsExecute()) return; - Git git = Git.wrap(db); - - // Add non-executable file - File file = writeTrashFile("file.txt", "a"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit1").call(); - assertFalse(db.getFS().canExecute(file)); - - // Create branch - git.branchCreate().setName("b1").call(); - - // Create second commit and don't touch file - writeTrashFile("file2.txt", ""); - git.add().addFilepattern("file2.txt").call(); - git.commit().setMessage("commit2").call(); - - // stage a mode change - writeTrashFile("file.txt", "a"); - db.getFS().setExecute(file, true); - git.add().addFilepattern("file.txt").call(); - - // dirty the file - writeTrashFile("file.txt", "b"); - - assertEquals( - "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]", - indexState(CONTENT)); - assertWorkDir(mkmap("file.txt", "b", "file2.txt", "")); - - // Switch branches and check that the dirty file survived in worktree - // and index - git.checkout().setName("b1").call(); - assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT)); - assertWorkDir(mkmap("file.txt", "b")); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add non-executable file + File file = writeTrashFile("file.txt", "a"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit1").call(); + assertFalse(db.getFS().canExecute(file)); + + // Create branch + git.branchCreate().setName("b1").call(); + + // Create second commit and don't touch file + writeTrashFile("file2.txt", ""); + git.add().addFilepattern("file2.txt").call(); + git.commit().setMessage("commit2").call(); + + // stage a mode change + writeTrashFile("file.txt", "a"); + db.getFS().setExecute(file, true); + git.add().addFilepattern("file.txt").call(); + + // dirty the file + writeTrashFile("file.txt", "b"); + + assertEquals( + "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]", + indexState(CONTENT)); + assertWorkDir(mkmap("file.txt", "b", "file2.txt", "")); + recorder.assertNoEvent(); + + // Switch branches and check that the dirty file survived in + // worktree and index + git.checkout().setName("b1").call(); + assertEquals("[file.txt, mode:100755, content:a]", + indexState(CONTENT)); + assertWorkDir(mkmap("file.txt", "b")); + recorder.assertEvent(ChangeRecorder.EMPTY, + new String[] { "file2.txt" }); + } finally { + if (handle != null) { + handle.remove(); + } + } } @Test @@ -1142,40 +1738,53 @@ if (!FS.DETECTED.supportsExecute()) return; - Git git = Git.wrap(db); - - // Add non-executable file - File file = writeTrashFile("file.txt", "a"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit1").call(); - assertFalse(db.getFS().canExecute(file)); - - // Create branch - git.branchCreate().setName("b1").call(); - - // Create second commit with executable file - file = writeTrashFile("file.txt", "b"); - db.getFS().setExecute(file, true); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("commit2").call(); - - // stage the same content as in the branch we want to switch to - writeTrashFile("file.txt", "a"); - db.getFS().setExecute(file, false); - git.add().addFilepattern("file.txt").call(); - - // dirty the file - writeTrashFile("file.txt", "c"); - db.getFS().setExecute(file, true); - - assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); - assertWorkDir(mkmap("file.txt", "c")); - - // Switch branches and check that the dirty file survived in worktree - // and index - git.checkout().setName("b1").call(); - assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); - assertWorkDir(mkmap("file.txt", "c")); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add non-executable file + File file = writeTrashFile("file.txt", "a"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit1").call(); + assertFalse(db.getFS().canExecute(file)); + + // Create branch + git.branchCreate().setName("b1").call(); + + // Create second commit with executable file + file = writeTrashFile("file.txt", "b"); + db.getFS().setExecute(file, true); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("commit2").call(); + + // stage the same content as in the branch we want to switch to + writeTrashFile("file.txt", "a"); + db.getFS().setExecute(file, false); + git.add().addFilepattern("file.txt").call(); + + // dirty the file + writeTrashFile("file.txt", "c"); + db.getFS().setExecute(file, true); + + assertEquals("[file.txt, mode:100644, content:a]", + indexState(CONTENT)); + assertWorkDir(mkmap("file.txt", "c")); + recorder.assertNoEvent(); + + // Switch branches and check that the dirty file survived in + // worktree + // and index + git.checkout().setName("b1").call(); + assertEquals("[file.txt, mode:100644, content:a]", + indexState(CONTENT)); + assertWorkDir(mkmap("file.txt", "c")); + recorder.assertNoEvent(); + } finally { + if (handle != null) { + handle.remove(); + } + } } @Test @@ -1183,66 +1792,165 @@ if (!FS.DETECTED.supportsExecute()) return; - Git git = Git.wrap(db); + ChangeRecorder recorder = new ChangeRecorder(); + ListenerHandle handle = null; + try (Git git = new Git(db)) { + handle = db.getListenerList() + .addWorkingTreeModifiedListener(recorder); + // Add first file + File file1 = writeTrashFile("file1.txt", "a"); + git.add().addFilepattern("file1.txt").call(); + git.commit().setMessage("commit1").call(); + assertFalse(db.getFS().canExecute(file1)); + + // Add second file + File file2 = writeTrashFile("file2.txt", "b"); + git.add().addFilepattern("file2.txt").call(); + git.commit().setMessage("commit2").call(); + assertFalse(db.getFS().canExecute(file2)); + recorder.assertNoEvent(); + + // Create branch from first commit + assertNotNull(git.checkout().setCreateBranch(true).setName("b1") + .setStartPoint(Constants.HEAD + "~1").call()); + recorder.assertEvent(ChangeRecorder.EMPTY, + new String[] { "file2.txt" }); + + // Change content and file mode in working directory and index + file1 = writeTrashFile("file1.txt", "c"); + db.getFS().setExecute(file1, true); + git.add().addFilepattern("file1.txt").call(); + + // Switch back to 'master' + assertNotNull(git.checkout().setName(Constants.MASTER).call()); + recorder.assertEvent(new String[] { "file2.txt" }, + ChangeRecorder.EMPTY); + } finally { + if (handle != null) { + handle.remove(); + } + } + } + + @Test(expected = CheckoutConflictException.class) + public void testFolderFileConflict() throws Exception { + RevCommit headCommit = commitFile("f/a", "initial content", "master"); + RevCommit checkoutCommit = commitFile("f/a", "side content", "side"); + FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE); + writeTrashFile("f", "file instead of folder"); + new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), + checkoutCommit.getTree()).checkout(); + } + + @Test + public void testMultipleContentConflicts() throws Exception { + commitFile("a", "initial content", "master"); + RevCommit headCommit = commitFile("b", "initial content", "master"); + commitFile("a", "side content", "side"); + RevCommit checkoutCommit = commitFile("b", "side content", "side"); + writeTrashFile("a", "changed content"); + writeTrashFile("b", "changed content"); + + try { + new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), + checkoutCommit.getTree()).checkout(); + fail(); + } catch (CheckoutConflictException expected) { + assertEquals(2, expected.getConflictingFiles().length); + assertTrue(Arrays.asList(expected.getConflictingFiles()) + .contains("a")); + assertTrue(Arrays.asList(expected.getConflictingFiles()) + .contains("b")); + assertEquals("changed content", read("a")); + assertEquals("changed content", read("b")); + } + } + + @Test + public void testFolderFileAndContentConflicts() throws Exception { + RevCommit headCommit = commitFile("f/a", "initial content", "master"); + commitFile("b", "side content", "side"); + RevCommit checkoutCommit = commitFile("f/a", "side content", "side"); + FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE); + writeTrashFile("f", "file instead of a folder"); + writeTrashFile("b", "changed content"); - // Add first file - File file1 = writeTrashFile("file1.txt", "a"); - git.add().addFilepattern("file1.txt").call(); - git.commit().setMessage("commit1").call(); - assertFalse(db.getFS().canExecute(file1)); - - // Add second file - File file2 = writeTrashFile("file2.txt", "b"); - git.add().addFilepattern("file2.txt").call(); - git.commit().setMessage("commit2").call(); - assertFalse(db.getFS().canExecute(file2)); - - // Create branch from first commit - assertNotNull(git.checkout().setCreateBranch(true).setName("b1") - .setStartPoint(Constants.HEAD + "~1").call()); - - // Change content and file mode in working directory and index - file1 = writeTrashFile("file1.txt", "c"); - db.getFS().setExecute(file1, true); - git.add().addFilepattern("file1.txt").call(); + try { + new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), + checkoutCommit.getTree()).checkout(); + fail(); + } catch (CheckoutConflictException expected) { + assertEquals(2, expected.getConflictingFiles().length); + assertTrue(Arrays.asList(expected.getConflictingFiles()) + .contains("b")); + assertTrue(Arrays.asList(expected.getConflictingFiles()) + .contains("f")); + assertEquals("file instead of a folder", read("f")); + assertEquals("changed content", read("b")); + } + } - // Switch back to 'master' - assertNotNull(git.checkout().setName(Constants.MASTER).call()); + @Test + public void testLongFilename() throws Exception { + char[] bytes = new char[253]; + Arrays.fill(bytes, 'f'); + String longFileName = new String(bytes); + // 1 + doit(mkmap(longFileName, "a"), mkmap(longFileName, "b"), + mkmap(longFileName, "a")); + writeTrashFile(longFileName, "a"); + checkout(); + assertNoConflicts(); + assertUpdated(longFileName); } - public void assertWorkDir(HashMap i) throws CorruptObjectException, + public void assertWorkDir(Map i) + throws CorruptObjectException, IOException { - TreeWalk walk = new TreeWalk(db); - walk.setRecursive(true); - walk.addTree(new FileTreeIterator(db)); - String expectedValue; - String path; - int nrFiles = 0; - FileTreeIterator ft; - while (walk.next()) { - ft = walk.getTree(0, FileTreeIterator.class); - path = ft.getEntryPathString(); - expectedValue = i.get(path); - assertNotNull("found unexpected file for path " + path - + " in workdir", expectedValue); - File file = new File(db.getWorkTree(), path); - assertTrue(file.exists()); - if (file.isFile()) { - FileInputStream is = new FileInputStream(file); - byte[] buffer = new byte[(int) file.length()]; - int offset = 0; - int numRead = 0; - while (offset < buffer.length - && (numRead = is.read(buffer, offset, buffer.length - - offset)) >= 0) { - offset += numRead; + try (TreeWalk walk = new TreeWalk(db)) { + walk.setRecursive(false); + walk.addTree(new FileTreeIterator(db)); + String expectedValue; + String path; + int nrFiles = 0; + FileTreeIterator ft; + while (walk.next()) { + ft = walk.getTree(0, FileTreeIterator.class); + path = ft.getEntryPathString(); + expectedValue = i.get(path); + File file = new File(db.getWorkTree(), path); + assertTrue(file.exists()); + if (file.isFile()) { + assertNotNull("found unexpected file for path " + path + + " in workdir", expectedValue); + try (FileInputStream is = new FileInputStream(file)) { + byte[] buffer = new byte[(int) file.length()]; + int offset = 0; + int numRead = 0; + while (offset < buffer.length + && (numRead = is.read(buffer, offset, + buffer.length - offset)) >= 0) { + offset += numRead; + } + assertArrayEquals( + "unexpected content for path " + path + + " in workDir. ", + buffer, i.get(path).getBytes()); + } + nrFiles++; + } else if (file.isDirectory()) { + String[] files = file.list(); + if (files != null && files.length == 0) { + assertEquals("found unexpected empty folder for path " + + path + " in workDir. ", "/", i.get(path)); + nrFiles++; + } + } + if (walk.isSubtree()) { + walk.enterSubtree(); } - is.close(); - assertArrayEquals("unexpected content for path " + path - + " in workDir. ", buffer, i.get(path).getBytes()); - nrFiles++; } + assertEquals("WorkDir has not the right size.", i.size(), nrFiles); } - assertEquals("WorkDir has not the right size.", i.size(), nrFiles); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,13 @@ package org.eclipse.jgit.lib; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.util.Set; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; @@ -87,10 +89,9 @@ .call(); submodule_db = (FileRepository) Git.wrap(db).submoduleAdd() - .setPath("submodule") + .setPath("modules/submodule") .setURI(submoduleStandalone.getDirectory().toURI().toString()) .call(); - submoduleStandalone.close(); submodule_trash = submodule_db.getWorkTree(); addRepoToClose(submodule_db); writeTrashFile("fileInRoot", "root"); @@ -119,6 +120,31 @@ assertTrue(indexDiff.diff()); } + private void assertDiff(IndexDiff indexDiff, IgnoreSubmoduleMode mode, + IgnoreSubmoduleMode... expectedEmptyModes) throws IOException { + boolean diffResult = indexDiff.diff(); + Set submodulePaths = indexDiff + .getPathsWithIndexMode(FileMode.GITLINK); + boolean emptyExpected = false; + for (IgnoreSubmoduleMode empty : expectedEmptyModes) { + if (mode.equals(empty)) { + emptyExpected = true; + break; + } + } + if (emptyExpected) { + assertFalse("diff should be false with mode=" + mode, + diffResult); + assertEquals("should have no paths with FileMode.GITLINK", 0, + submodulePaths.size()); + } else { + assertTrue("diff should be true with mode=" + mode, + diffResult); + assertTrue("submodule path should have FileMode.GITLINK", + submodulePaths.contains("modules/submodule")); + } + } + @Theory public void testDirtySubmoduleWorktree(IgnoreSubmoduleMode mode) throws IOException { @@ -126,13 +152,8 @@ IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); indexDiff.setIgnoreSubmoduleMode(mode); - if (mode.equals(IgnoreSubmoduleMode.ALL) - || mode.equals(IgnoreSubmoduleMode.DIRTY)) - assertFalse("diff should be false with mode=" + mode, - indexDiff.diff()); - else - assertTrue("diff should be true with mode=" + mode, - indexDiff.diff()); + assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL, + IgnoreSubmoduleMode.DIRTY); } @Theory @@ -146,12 +167,7 @@ IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); indexDiff.setIgnoreSubmoduleMode(mode); - if (mode.equals(IgnoreSubmoduleMode.ALL)) - assertFalse("diff should be false with mode=" + mode, - indexDiff.diff()); - else - assertTrue("diff should be true with mode=" + mode, - indexDiff.diff()); + assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL); } @Theory @@ -164,13 +180,8 @@ IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); indexDiff.setIgnoreSubmoduleMode(mode); - if (mode.equals(IgnoreSubmoduleMode.ALL) - || mode.equals(IgnoreSubmoduleMode.DIRTY)) - assertFalse("diff should be false with mode=" + mode, - indexDiff.diff()); - else - assertTrue("diff should be true with mode=" + mode, - indexDiff.diff()); + assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL, + IgnoreSubmoduleMode.DIRTY); } @Theory @@ -184,13 +195,8 @@ IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); indexDiff.setIgnoreSubmoduleMode(mode); - if (mode.equals(IgnoreSubmoduleMode.ALL) - || mode.equals(IgnoreSubmoduleMode.DIRTY)) - assertFalse("diff should be false with mode=" + mode, - indexDiff.diff()); - else - assertTrue("diff should be true with mode=" + mode, - indexDiff.diff()); + assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL, + IgnoreSubmoduleMode.DIRTY); } @Theory @@ -201,13 +207,7 @@ IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); indexDiff.setIgnoreSubmoduleMode(mode); - if (mode.equals(IgnoreSubmoduleMode.ALL) - || mode.equals(IgnoreSubmoduleMode.DIRTY) - || mode.equals(IgnoreSubmoduleMode.UNTRACKED)) - assertFalse("diff should be false with mode=" + mode, - indexDiff.diff()); - else - assertTrue("diff should be true with mode=" + mode, - indexDiff.diff()); + assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL, + IgnoreSubmoduleMode.DIRTY, IgnoreSubmoduleMode.UNTRACKED); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -77,7 +77,6 @@ import org.eclipse.jgit.util.IO; import org.junit.Test; -@SuppressWarnings("deprecation") public class IndexDiffTest extends RepositoryTestCase { static PathEdit add(final Repository db, final File workdir, @@ -87,6 +86,7 @@ final ObjectId id = inserter.insert(Constants.OBJ_BLOB, IO.readFully(f)); return new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setLength(f.length()); @@ -99,8 +99,7 @@ public void testAdded() throws IOException { writeTrashFile("file1", "file1"); writeTrashFile("dir/subfile", "dir/subfile"); - Tree tree = new Tree(db); - tree.setId(insertTree(tree)); + ObjectId tree = insertTree(new TreeFormatter()); DirCache index = db.lockDirCache(); DirCacheEditor editor = index.editor(); @@ -108,7 +107,7 @@ editor.add(add(db, trash, "dir/subfile")); editor.commit(); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, tree, iterator); diff.diff(); assertEquals(2, diff.getAdded().size()); assertTrue(diff.getAdded().contains("file1")); @@ -124,18 +123,16 @@ writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); - Tree tree = new Tree(db); - tree.addFile("file2"); - tree.addFile("dir/file3"); - assertEquals(2, tree.memberCount()); - tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); + tree.append("dir", FileMode.TREE, insertTree(dir)); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getRemoved().size()); assertTrue(diff.getRemoved().contains("file2")); @@ -152,21 +149,22 @@ writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); - Git git = new Git(db); - git.add().addFilepattern("file2").addFilepattern("dir/file3").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("file2").addFilepattern("dir/file3").call(); + } writeTrashFile("dir/file3", "changed"); - Tree tree = new Tree(db); - tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - assertEquals(2, tree.memberCount()); - - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("dir", FileMode.TREE, insertTree(dir)); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + ObjectId treeId = insertTree(tree); + FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getChanged().size()); assertTrue(diff.getChanged().contains("file2")); @@ -181,38 +179,38 @@ @Test public void testConflicting() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - // create side branch with two modifications - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - writeTrashFile("a", "1\na(side)\n3\n"); - writeTrashFile("b", "1\nb\n3\n(side)"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - // update a on master to generate conflict - checkoutBranch("refs/heads/master"); - writeTrashFile("a", "1\na(main)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); - - // merge side with master - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + // create side branch with two modifications + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + writeTrashFile("a", "1\na(side)\n3\n"); + writeTrashFile("b", "1\nb\n3\n(side)"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + // update a on master to generate conflict + checkoutBranch("refs/heads/master"); + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + // merge side with master + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + } FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); diff.diff(); assertEquals("[b]", - new TreeSet(diff.getChanged()).toString()); + new TreeSet<>(diff.getChanged()).toString()); assertEquals("[]", diff.getAdded().toString()); assertEquals("[]", diff.getRemoved().toString()); assertEquals("[]", diff.getMissing().toString()); @@ -225,35 +223,35 @@ @Test public void testConflictingDeletedAndModified() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - writeTrashFile("b", "1\nb\n3\n"); - git.add().addFilepattern("a").addFilepattern("b").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - // create side branch and delete "a" - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - git.rm().addFilepattern("a").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - // update a on master to generate conflict - checkoutBranch("refs/heads/master"); - writeTrashFile("a", "1\na(main)\n3\n"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("main").call(); - - // merge side with master - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + writeTrashFile("b", "1\nb\n3\n"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + // create side branch and delete "a" + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + git.rm().addFilepattern("a").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + // update a on master to generate conflict + checkoutBranch("refs/heads/master"); + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + // merge side with master + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + } FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); diff.diff(); - assertEquals("[]", new TreeSet(diff.getChanged()).toString()); + assertEquals("[]", new TreeSet<>(diff.getChanged()).toString()); assertEquals("[]", diff.getAdded().toString()); assertEquals("[]", diff.getRemoved().toString()); assertEquals("[]", diff.getMissing().toString()); @@ -266,34 +264,34 @@ @Test public void testConflictingFromMultipleCreations() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "1\na\n3\n"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial").call(); - - createBranch(initialCommit, "refs/heads/side"); - checkoutBranch("refs/heads/side"); - - writeTrashFile("b", "1\nb(side)\n3\n"); - git.add().addFilepattern("b").call(); - RevCommit secondCommit = git.commit().setMessage("side").call(); - - checkoutBranch("refs/heads/master"); - - writeTrashFile("b", "1\nb(main)\n3\n"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("main").call(); - - MergeResult result = git.merge().include(secondCommit.getId()) - .setStrategy(MergeStrategy.RESOLVE).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("b", "1\nb(side)\n3\n"); + git.add().addFilepattern("b").call(); + RevCommit secondCommit = git.commit().setMessage("side").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("b", "1\nb(main)\n3\n"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("main").call(); + + MergeResult result = git.merge().include(secondCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + } FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); diff.diff(); - assertEquals("[]", new TreeSet(diff.getChanged()).toString()); + assertEquals("[]", new TreeSet<>(diff.getChanged()).toString()); assertEquals("[]", diff.getAdded().toString()); assertEquals("[]", diff.getRemoved().toString()); assertEquals("[]", diff.getMissing().toString()); @@ -308,23 +306,23 @@ writeTrashFile("a.c", "a.c"); writeTrashFile("a=c", "a=c"); writeTrashFile("a=d", "a=d"); - Git git = new Git(db); - git.add().addFilepattern("a.b").call(); - git.add().addFilepattern("a.c").call(); - git.add().addFilepattern("a=c").call(); - git.add().addFilepattern("a=d").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("a.b").call(); + git.add().addFilepattern("a.c").call(); + git.add().addFilepattern("a=c").call(); + git.add().addFilepattern("a=d").call(); + } - Tree tree = new Tree(db); + TreeFormatter tree = new TreeFormatter(); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - - tree.setId(insertTree(tree)); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -343,7 +341,6 @@ */ @Test public void testUnchangedComplex() throws IOException, GitAPIException { - Git git = new Git(db); writeTrashFile("a.b", "a.b"); writeTrashFile("a.c", "a.c"); writeTrashFile("a/b.b/b", "a/b.b/b"); @@ -351,29 +348,34 @@ writeTrashFile("a/c", "a/c"); writeTrashFile("a=c", "a=c"); writeTrashFile("a=d", "a=d"); - git.add().addFilepattern("a.b").addFilepattern("a.c") - .addFilepattern("a/b.b/b").addFilepattern("a/b") - .addFilepattern("a/c").addFilepattern("a=c") - .addFilepattern("a=d").call(); + try (Git git = new Git(db)) { + git.add().addFilepattern("a.b").addFilepattern("a.c") + .addFilepattern("a/b.b/b").addFilepattern("a/b") + .addFilepattern("a/c").addFilepattern("a=c") + .addFilepattern("a=d").call(); + } + - Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); - tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415")); - tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - - Tree tree3 = (Tree) tree.findTreeMember("a/b.b"); - tree3.setId(insertTree(tree3)); - Tree tree2 = (Tree) tree.findTreeMember("a"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + TreeFormatter bb = new TreeFormatter(); + bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + + TreeFormatter a = new TreeFormatter(); + a.append("b", FileMode.REGULAR_FILE, ObjectId + .fromString("db89c972fc57862eae378f45b74aca228037d415")); + a.append("b.b", FileMode.TREE, insertTree(bb)); + a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a", FileMode.TREE, insertTree(a)); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -383,9 +385,9 @@ assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } - private ObjectId insertTree(Tree tree) throws IOException { + private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); + ObjectId id = oi.insert(tree); oi.flush(); return id; } @@ -399,11 +401,12 @@ */ @Test public void testRemovedUntracked() throws Exception{ - Git git = new Git(db); String path = "file"; - writeTrashFile(path, "content"); - git.add().addFilepattern(path).call(); - git.commit().setMessage("commit").call(); + try (Git git = new Git(db)) { + writeTrashFile(path, "content"); + git.add().addFilepattern(path).call(); + git.commit().setMessage("commit").call(); + } removeFromIndex(path); FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); @@ -419,51 +422,51 @@ */ @Test public void testUntrackedFolders() throws Exception { - Git git = new Git(db); - - IndexDiff diff = new IndexDiff(db, Constants.HEAD, - new FileTreeIterator(db)); - diff.diff(); - assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); - - writeTrashFile("readme", ""); - writeTrashFile("src/com/A.java", ""); - writeTrashFile("src/com/B.java", ""); - writeTrashFile("src/org/A.java", ""); - writeTrashFile("src/org/B.java", ""); - writeTrashFile("target/com/A.java", ""); - writeTrashFile("target/com/B.java", ""); - writeTrashFile("target/org/A.java", ""); - writeTrashFile("target/org/B.java", ""); - - git.add().addFilepattern("src").addFilepattern("readme").call(); - git.commit().setMessage("initial").call(); - - diff = new IndexDiff(db, Constants.HEAD, - new FileTreeIterator(db)); - diff.diff(); - assertEquals(new HashSet(Arrays.asList("target")), - diff.getUntrackedFolders()); - - writeTrashFile("src/tst/A.java", ""); - writeTrashFile("src/tst/B.java", ""); - - diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); - diff.diff(); - assertEquals(new HashSet(Arrays.asList("target", "src/tst")), - diff.getUntrackedFolders()); - - git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org") - .call(); - git.commit().setMessage("second").call(); - writeTrashFile("src/org/C.java", ""); - - diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); - diff.diff(); - assertEquals( - new HashSet(Arrays.asList("src/org", "src/tst", - "target")), - diff.getUntrackedFolders()); + try (Git git = new Git(db)) { + IndexDiff diff = new IndexDiff(db, Constants.HEAD, + new FileTreeIterator(db)); + diff.diff(); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + + writeTrashFile("readme", ""); + writeTrashFile("src/com/A.java", ""); + writeTrashFile("src/com/B.java", ""); + writeTrashFile("src/org/A.java", ""); + writeTrashFile("src/org/B.java", ""); + writeTrashFile("target/com/A.java", ""); + writeTrashFile("target/com/B.java", ""); + writeTrashFile("target/org/A.java", ""); + writeTrashFile("target/org/B.java", ""); + + git.add().addFilepattern("src").addFilepattern("readme").call(); + git.commit().setMessage("initial").call(); + + diff = new IndexDiff(db, Constants.HEAD, + new FileTreeIterator(db)); + diff.diff(); + assertEquals(new HashSet<>(Arrays.asList("target")), + diff.getUntrackedFolders()); + + writeTrashFile("src/tst/A.java", ""); + writeTrashFile("src/tst/B.java", ""); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals(new HashSet<>(Arrays.asList("target", "src/tst")), + diff.getUntrackedFolders()); + + git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org") + .call(); + git.commit().setMessage("second").call(); + writeTrashFile("src/org/C.java", ""); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals( + new HashSet<>(Arrays.asList("src/org", "src/tst", + "target")), + diff.getUntrackedFolders()); + } } /** @@ -473,85 +476,86 @@ */ @Test public void testUntrackedNotIgnoredFolders() throws Exception { - Git git = new Git(db); - - IndexDiff diff = new IndexDiff(db, Constants.HEAD, - new FileTreeIterator(db)); - diff.diff(); - assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); - - writeTrashFile("readme", ""); - writeTrashFile("sr/com/X.java", ""); - writeTrashFile("src/com/A.java", ""); - writeTrashFile("src/org/B.java", ""); - writeTrashFile("srcs/org/Y.java", ""); - writeTrashFile("target/com/A.java", ""); - writeTrashFile("target/org/B.java", ""); - writeTrashFile(".gitignore", "/target\n/sr"); - - git.add().addFilepattern("readme").addFilepattern(".gitignore") - .addFilepattern("srcs/").call(); - git.commit().setMessage("initial").call(); - - diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); - diff.diff(); - assertEquals(new HashSet(Arrays.asList("src")), - diff.getUntrackedFolders()); - - git.add().addFilepattern("src").call(); - writeTrashFile("sr/com/X1.java", ""); - writeTrashFile("src/tst/A.java", ""); - writeTrashFile("src/tst/B.java", ""); - writeTrashFile("srcs/com/Y1.java", ""); - deleteTrashFile(".gitignore"); - - diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); - diff.diff(); - assertEquals( - new HashSet(Arrays.asList("srcs/com", "sr", "src/tst", - "target")), - diff.getUntrackedFolders()); + try (Git git = new Git(db)) { + IndexDiff diff = new IndexDiff(db, Constants.HEAD, + new FileTreeIterator(db)); + diff.diff(); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + + writeTrashFile("readme", ""); + writeTrashFile("sr/com/X.java", ""); + writeTrashFile("src/com/A.java", ""); + writeTrashFile("src/org/B.java", ""); + writeTrashFile("srcs/org/Y.java", ""); + writeTrashFile("target/com/A.java", ""); + writeTrashFile("target/org/B.java", ""); + writeTrashFile(".gitignore", "/target\n/sr"); + + git.add().addFilepattern("readme").addFilepattern(".gitignore") + .addFilepattern("srcs/").call(); + git.commit().setMessage("initial").call(); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals(new HashSet<>(Arrays.asList("src")), + diff.getUntrackedFolders()); + + git.add().addFilepattern("src").call(); + writeTrashFile("sr/com/X1.java", ""); + writeTrashFile("src/tst/A.java", ""); + writeTrashFile("src/tst/B.java", ""); + writeTrashFile("srcs/com/Y1.java", ""); + deleteTrashFile(".gitignore"); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals( + new HashSet<>(Arrays.asList("srcs/com", "sr", "src/tst", + "target")), + diff.getUntrackedFolders()); + } } @Test public void testAssumeUnchanged() throws Exception { - Git git = new Git(db); - String path = "file"; - writeTrashFile(path, "content"); - git.add().addFilepattern(path).call(); - String path2 = "file2"; - writeTrashFile(path2, "content"); - String path3 = "file3"; - writeTrashFile(path3, "some content"); - git.add().addFilepattern(path2).addFilepattern(path3).call(); - git.commit().setMessage("commit").call(); - assumeUnchanged(path2); - assumeUnchanged(path3); - writeTrashFile(path, "more content"); - deleteTrashFile(path3); - - FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - assertEquals(2, diff.getAssumeUnchanged().size()); - assertEquals(1, diff.getModified().size()); - assertEquals(0, diff.getChanged().size()); - assertTrue(diff.getAssumeUnchanged().contains("file2")); - assertTrue(diff.getAssumeUnchanged().contains("file3")); - assertTrue(diff.getModified().contains("file")); - - git.add().addFilepattern(".").call(); - - iterator = new FileTreeIterator(db); - diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - assertEquals(2, diff.getAssumeUnchanged().size()); - assertEquals(0, diff.getModified().size()); - assertEquals(1, diff.getChanged().size()); - assertTrue(diff.getAssumeUnchanged().contains("file2")); - assertTrue(diff.getAssumeUnchanged().contains("file3")); - assertTrue(diff.getChanged().contains("file")); - assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + try (Git git = new Git(db)) { + String path = "file"; + writeTrashFile(path, "content"); + git.add().addFilepattern(path).call(); + String path2 = "file2"; + writeTrashFile(path2, "content"); + String path3 = "file3"; + writeTrashFile(path3, "some content"); + git.add().addFilepattern(path2).addFilepattern(path3).call(); + git.commit().setMessage("commit").call(); + assumeUnchanged(path2); + assumeUnchanged(path3); + writeTrashFile(path, "more content"); + deleteTrashFile(path3); + + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + assertEquals(2, diff.getAssumeUnchanged().size()); + assertEquals(1, diff.getModified().size()); + assertEquals(0, diff.getChanged().size()); + assertTrue(diff.getAssumeUnchanged().contains("file2")); + assertTrue(diff.getAssumeUnchanged().contains("file3")); + assertTrue(diff.getModified().contains("file")); + + git.add().addFilepattern(".").call(); + + iterator = new FileTreeIterator(db); + diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + assertEquals(2, diff.getAssumeUnchanged().size()); + assertEquals(0, diff.getModified().size()); + assertEquals(1, diff.getChanged().size()); + assertTrue(diff.getAssumeUnchanged().contains("file2")); + assertTrue(diff.getAssumeUnchanged().contains("file3")); + assertTrue(diff.getChanged().contains("file")); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + } } @Test @@ -577,147 +581,148 @@ @Test public void testStageState_mergeAndReset_bug() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "content"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - - // create branch and add a new file - final String branchName = Constants.R_HEADS + "branch"; - createBranch(initialCommit, branchName); - checkoutBranch(branchName); - writeTrashFile("b", "second file content - branch"); - git.add().addFilepattern("b").call(); - RevCommit branchCommit = git.commit().setMessage("branch commit") - .call(); - - // checkout master and add the same new file - checkoutBranch(Constants.R_HEADS + Constants.MASTER); - writeTrashFile("b", "second file content - master"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("master commit").call(); - - // try and merge - MergeResult result = git.merge().include(branchCommit).call(); - assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); - - FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - - assertTrue(diff.getChanged().isEmpty()); - assertTrue(diff.getAdded().isEmpty()); - assertTrue(diff.getRemoved().isEmpty()); - assertTrue(diff.getMissing().isEmpty()); - assertTrue(diff.getModified().isEmpty()); - assertEquals(1, diff.getConflicting().size()); - assertTrue(diff.getConflicting().contains("b")); - assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() - .get("b")); - assertTrue(diff.getUntrackedFolders().isEmpty()); - - // reset file b to its master state without altering the index - writeTrashFile("b", "second file content - master"); - - // we should have the same result - iterator = new FileTreeIterator(db); - diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - - assertTrue(diff.getChanged().isEmpty()); - assertTrue(diff.getAdded().isEmpty()); - assertTrue(diff.getRemoved().isEmpty()); - assertTrue(diff.getMissing().isEmpty()); - assertTrue(diff.getModified().isEmpty()); - assertEquals(1, diff.getConflicting().size()); - assertTrue(diff.getConflicting().contains("b")); - assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() - .get("b")); - assertTrue(diff.getUntrackedFolders().isEmpty()); + try (Git git = new Git(db)) { + writeTrashFile("a", "content"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + + // create branch and add a new file + final String branchName = Constants.R_HEADS + "branch"; + createBranch(initialCommit, branchName); + checkoutBranch(branchName); + writeTrashFile("b", "second file content - branch"); + git.add().addFilepattern("b").call(); + RevCommit branchCommit = git.commit().setMessage("branch commit") + .call(); + + // checkout master and add the same new file + checkoutBranch(Constants.R_HEADS + Constants.MASTER); + writeTrashFile("b", "second file content - master"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("master commit").call(); + + // try and merge + MergeResult result = git.merge().include(branchCommit).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + + assertTrue(diff.getChanged().isEmpty()); + assertTrue(diff.getAdded().isEmpty()); + assertTrue(diff.getRemoved().isEmpty()); + assertTrue(diff.getMissing().isEmpty()); + assertTrue(diff.getModified().isEmpty()); + assertEquals(1, diff.getConflicting().size()); + assertTrue(diff.getConflicting().contains("b")); + assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() + .get("b")); + assertTrue(diff.getUntrackedFolders().isEmpty()); + + // reset file b to its master state without altering the index + writeTrashFile("b", "second file content - master"); + + // we should have the same result + iterator = new FileTreeIterator(db); + diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + + assertTrue(diff.getChanged().isEmpty()); + assertTrue(diff.getAdded().isEmpty()); + assertTrue(diff.getRemoved().isEmpty()); + assertTrue(diff.getMissing().isEmpty()); + assertTrue(diff.getModified().isEmpty()); + assertEquals(1, diff.getConflicting().size()); + assertTrue(diff.getConflicting().contains("b")); + assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() + .get("b")); + assertTrue(diff.getUntrackedFolders().isEmpty()); + } } @Test public void testStageState_simulated_bug() throws Exception { - Git git = new Git(db); - - writeTrashFile("a", "content"); - git.add().addFilepattern("a").call(); - RevCommit initialCommit = git.commit().setMessage("initial commit") - .call(); - - // create branch and add a new file - final String branchName = Constants.R_HEADS + "branch"; - createBranch(initialCommit, branchName); - checkoutBranch(branchName); - writeTrashFile("b", "second file content - branch"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("branch commit") - .call(); - - // checkout master and add the same new file - checkoutBranch(Constants.R_HEADS + Constants.MASTER); - writeTrashFile("b", "second file content - master"); - git.add().addFilepattern("b").call(); - git.commit().setMessage("master commit").call(); - - // Simulate a failed merge of branch into master - DirCacheBuilder builder = db.lockDirCache().builder(); - DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0, - "content"); - builder.add(entry); - entry = createEntry("b", FileMode.REGULAR_FILE, 2, - "second file content - master"); - builder.add(entry); - entry = createEntry("b", FileMode.REGULAR_FILE, 3, - "second file content - branch"); - builder.add(entry); - builder.commit(); - - FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - - assertTrue(diff.getChanged().isEmpty()); - assertTrue(diff.getAdded().isEmpty()); - assertTrue(diff.getRemoved().isEmpty()); - assertTrue(diff.getMissing().isEmpty()); - assertTrue(diff.getModified().isEmpty()); - assertEquals(1, diff.getConflicting().size()); - assertTrue(diff.getConflicting().contains("b")); - assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() - .get("b")); - assertTrue(diff.getUntrackedFolders().isEmpty()); + try (Git git = new Git(db)) { + writeTrashFile("a", "content"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial commit") + .call(); + + // create branch and add a new file + final String branchName = Constants.R_HEADS + "branch"; + createBranch(initialCommit, branchName); + checkoutBranch(branchName); + writeTrashFile("b", "second file content - branch"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("branch commit") + .call(); + + // checkout master and add the same new file + checkoutBranch(Constants.R_HEADS + Constants.MASTER); + writeTrashFile("b", "second file content - master"); + git.add().addFilepattern("b").call(); + git.commit().setMessage("master commit").call(); + + // Simulate a failed merge of branch into master + DirCacheBuilder builder = db.lockDirCache().builder(); + DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0, + "content"); + builder.add(entry); + entry = createEntry("b", FileMode.REGULAR_FILE, 2, + "second file content - master"); + builder.add(entry); + entry = createEntry("b", FileMode.REGULAR_FILE, 3, + "second file content - branch"); + builder.add(entry); + builder.commit(); + + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + + assertTrue(diff.getChanged().isEmpty()); + assertTrue(diff.getAdded().isEmpty()); + assertTrue(diff.getRemoved().isEmpty()); + assertTrue(diff.getMissing().isEmpty()); + assertTrue(diff.getModified().isEmpty()); + assertEquals(1, diff.getConflicting().size()); + assertTrue(diff.getConflicting().contains("b")); + assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates() + .get("b")); + assertTrue(diff.getUntrackedFolders().isEmpty()); + } } @Test public void testAutoCRLFInput() throws Exception { - Git git = new Git(db); - FileBasedConfig config = db.getConfig(); - - // Make sure core.autocrlf is false before adding - config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE); - config.save(); - - // File is already in repository with CRLF - writeTrashFile("crlf.txt", "this\r\ncontains\r\ncrlf\r\n"); - git.add().addFilepattern("crlf.txt").call(); - git.commit().setMessage("Add crlf.txt").call(); - - // Now set core.autocrlf to input - config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.INPUT); - config.save(); + try (Git git = new Git(db)) { + FileBasedConfig config = db.getConfig(); - FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); - diff.diff(); - - assertTrue( - "Expected no modified files, but there were: " - + diff.getModified(), diff.getModified().isEmpty()); + // Make sure core.autocrlf is false before adding + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE); + config.save(); + + // File is already in repository with CRLF + writeTrashFile("crlf.txt", "this\r\ncontains\r\ncrlf\r\n"); + git.add().addFilepattern("crlf.txt").call(); + git.commit().setMessage("Add crlf.txt").call(); + + // Now set core.autocrlf to input + config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.INPUT); + config.save(); + + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); + diff.diff(); + + assertTrue( + "Expected no modified files, but there were: " + + diff.getModified(), diff.getModified().isEmpty()); + } } private void verifyStageState(StageState expected, int... stages) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,80 +49,81 @@ @Test public void testLastModifiedTimes() throws Exception { - Git git = new Git(db); - String path = "file"; - writeTrashFile(path, "content"); - String path2 = "file2"; - writeTrashFile(path2, "content2"); - - git.add().addFilepattern(path).call(); - git.add().addFilepattern(path2).call(); - git.commit().setMessage("commit").call(); - - DirCache dc = db.readDirCache(); - DirCacheEntry entry = dc.getEntry(path); - DirCacheEntry entry2 = dc.getEntry(path); - - assertTrue("last modified shall not be zero!", - entry.getLastModified() != 0); - - assertTrue("last modified shall not be zero!", - entry2.getLastModified() != 0); - - writeTrashFile(path, "new content"); - git.add().addFilepattern(path).call(); - git.commit().setMessage("commit2").call(); - - dc = db.readDirCache(); - entry = dc.getEntry(path); - entry2 = dc.getEntry(path); - - assertTrue("last modified shall not be zero!", - entry.getLastModified() != 0); - - assertTrue("last modified shall not be zero!", - entry2.getLastModified() != 0); + try (Git git = new Git(db)) { + String path = "file"; + writeTrashFile(path, "content"); + String path2 = "file2"; + writeTrashFile(path2, "content2"); + + git.add().addFilepattern(path).call(); + git.add().addFilepattern(path2).call(); + git.commit().setMessage("commit").call(); + + DirCache dc = db.readDirCache(); + DirCacheEntry entry = dc.getEntry(path); + DirCacheEntry entry2 = dc.getEntry(path); + + assertTrue("last modified shall not be zero!", + entry.getLastModified() != 0); + + assertTrue("last modified shall not be zero!", + entry2.getLastModified() != 0); + + writeTrashFile(path, "new content"); + git.add().addFilepattern(path).call(); + git.commit().setMessage("commit2").call(); + + dc = db.readDirCache(); + entry = dc.getEntry(path); + entry2 = dc.getEntry(path); + + assertTrue("last modified shall not be zero!", + entry.getLastModified() != 0); + + assertTrue("last modified shall not be zero!", + entry2.getLastModified() != 0); + } } @Test public void testModify() throws Exception { - Git git = new Git(db); - String path = "file"; - writeTrashFile(path, "content"); - - git.add().addFilepattern(path).call(); - git.commit().setMessage("commit").call(); + try (Git git = new Git(db)) { + String path = "file"; + writeTrashFile(path, "content"); - DirCache dc = db.readDirCache(); - DirCacheEntry entry = dc.getEntry(path); + git.add().addFilepattern(path).call(); + git.commit().setMessage("commit").call(); - long masterLastMod = entry.getLastModified(); + DirCache dc = db.readDirCache(); + DirCacheEntry entry = dc.getEntry(path); - git.checkout().setCreateBranch(true).setName("side").call(); + long masterLastMod = entry.getLastModified(); - Thread.sleep(10); - String path2 = "file2"; - writeTrashFile(path2, "side content"); - git.add().addFilepattern(path2).call(); - git.commit().setMessage("commit").call(); + git.checkout().setCreateBranch(true).setName("side").call(); - dc = db.readDirCache(); - entry = dc.getEntry(path); + Thread.sleep(10); + String path2 = "file2"; + writeTrashFile(path2, "side content"); + git.add().addFilepattern(path2).call(); + git.commit().setMessage("commit").call(); - long sideLastMode = entry.getLastModified(); + dc = db.readDirCache(); + entry = dc.getEntry(path); - Thread.sleep(2000); + long sideLastMode = entry.getLastModified(); - writeTrashFile(path, "uncommitted content"); - git.checkout().setName("master").call(); + Thread.sleep(2000); - dc = db.readDirCache(); - entry = dc.getEntry(path); + writeTrashFile(path, "uncommitted content"); + git.checkout().setName("master").call(); - assertTrue("shall have equal mod time!", masterLastMod == sideLastMode); - assertTrue("shall not equal master timestamp!", - entry.getLastModified() == masterLastMod); + dc = db.readDirCache(); + entry = dc.getEntry(path); + assertTrue("shall have equal mod time!", masterLastMod == sideLastMode); + assertTrue("shall not equal master timestamp!", + entry.getLastModified() == masterLastMod); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -68,13 +68,11 @@ assertEquals(db.readMergeHeads().size(), 2); assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId()); assertEquals(db.readMergeHeads().get(1), ObjectId.fromString(sampleId)); + // same test again, this time with lower-level io - FileOutputStream fos = new FileOutputStream(new File(db.getDirectory(), - "MERGE_HEAD")); - try { + try (FileOutputStream fos = new FileOutputStream( + new File(db.getDirectory(), "MERGE_HEAD"));) { fos.write("0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n".getBytes(Constants.CHARACTER_ENCODING)); - } finally { - fos.close(); } assertEquals(db.readMergeHeads().size(), 2); assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId()); @@ -82,12 +80,9 @@ db.writeMergeHeads(Collections. emptyList()); assertEquals(read(new File(db.getDirectory(), "MERGE_HEAD")), ""); assertEquals(db.readMergeHeads(), null); - fos = new FileOutputStream(new File(db.getDirectory(), - "MERGE_HEAD")); - try { + try (FileOutputStream fos = new FileOutputStream( + new File(db.getDirectory(), "MERGE_HEAD"))) { fos.write(sampleId.getBytes(Constants.CHARACTER_ENCODING)); - } finally { - fos.close(); } assertEquals(db.readMergeHeads().size(), 1); assertEquals(db.readMergeHeads().get(0), ObjectId.fromString(sampleId)); @@ -103,12 +98,9 @@ db.writeMergeCommitMsg(null); assertEquals(db.readMergeCommitMsg(), null); assertFalse(new File(db.getDirectory(), "MERGE_MSG").exists()); - FileOutputStream fos = new FileOutputStream(new File(db.getDirectory(), - Constants.MERGE_MSG)); - try { + try (FileOutputStream fos = new FileOutputStream( + new File(db.getDirectory(), Constants.MERGE_MSG))) { fos.write(mergeMsg.getBytes(Constants.CHARACTER_ENCODING)); - } finally { - fos.close(); } assertEquals(db.readMergeCommitMsg(), mergeMsg); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,21 +45,81 @@ package org.eclipse.jgit.lib; import static java.lang.Integer.valueOf; -import static java.lang.Long.valueOf; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.junit.JGitTestUtil.concat; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BAD; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.Constants.encodeASCII; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; +import static org.eclipse.jgit.util.RawParseUtils.decode; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; -import java.io.UnsupportedEncodingException; import java.text.MessageFormat; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; public class ObjectCheckerTest { + private static final ObjectChecker SECRET_KEY_CHECKER = new ObjectChecker() { + @Override + public void checkBlob(byte[] raw) throws CorruptObjectException { + String in = decode(raw); + if (in.contains("secret_key")) { + throw new CorruptObjectException("don't add a secret key"); + } + } + }; + + private static final ObjectChecker SECRET_KEY_BLOB_CHECKER = new ObjectChecker() { + @Override + public BlobObjectChecker newBlobObjectChecker() { + return new BlobObjectChecker() { + private boolean containSecretKey; + + @Override + public void update(byte[] in, int offset, int len) { + String str = decode(in, offset, offset + len); + if (str.contains("secret_key")) { + containSecretKey = true; + } + } + + @Override + public void endBlob(AnyObjectId id) + throws CorruptObjectException { + if (containSecretKey) { + throw new CorruptObjectException( + "don't add a secret key"); + } + } + }; + } + }; + private ObjectChecker checker; + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Before public void setUp() throws Exception { checker = new ObjectChecker(); @@ -67,15 +127,10 @@ @Test public void testInvalidType() { - try { - checker.check(Constants.OBJ_BAD, new byte[0]); - fail("Did not throw CorruptObjectException"); - } catch (CorruptObjectException e) { - final String m = e.getMessage(); - assertEquals(MessageFormat.format( - JGitText.get().corruptObjectInvalidType2, - valueOf(Constants.OBJ_BAD)), m); - } + String msg = MessageFormat.format( + JGitText.get().corruptObjectInvalidType2, + valueOf(OBJ_BAD)); + assertCorrupt(msg, OBJ_BAD, new byte[0]); } @Test @@ -84,13 +139,39 @@ checker.checkBlob(new byte[0]); checker.checkBlob(new byte[1]); - checker.check(Constants.OBJ_BLOB, new byte[0]); - checker.check(Constants.OBJ_BLOB, new byte[1]); + checker.check(OBJ_BLOB, new byte[0]); + checker.check(OBJ_BLOB, new byte[1]); + } + + @Test + public void testCheckBlobNotCorrupt() throws CorruptObjectException { + SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"public_key\"")); + } + + @Test + public void testCheckBlobCorrupt() throws CorruptObjectException { + thrown.expect(CorruptObjectException.class); + SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"secret_key\"")); + } + + @Test + public void testCheckBlobWithBlobObjectCheckerNotCorrupt() + throws CorruptObjectException { + SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB, + encodeASCII("key = \"public_key\"")); + } + + @Test + public void testCheckBlobWithBlobObjectCheckerCorrupt() + throws CorruptObjectException { + thrown.expect(CorruptObjectException.class); + SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB, + encodeASCII("key = \"secret_key\"")); } @Test public void testValidCommitNoParent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -99,14 +180,14 @@ b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommitBlankAuthor() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -115,9 +196,9 @@ b.append("author <> 0 +0000\n"); b.append("committer <> 0 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test @@ -127,15 +208,13 @@ b.append("author b 0 +0000\n"); b.append("committer <> 0 +0000\n"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad date", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test @@ -145,20 +224,18 @@ b.append("author <> 0 +0000\n"); b.append("committer b 0 +0000\n"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad date", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test public void testValidCommit1Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -171,14 +248,14 @@ b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommit2Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -195,14 +272,14 @@ b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommit128Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -217,15 +294,15 @@ b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommitNormalTime() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - final String when = "1222757360 -0730"; + StringBuilder b = new StringBuilder(); + String when = "1222757360 -0730"; b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -234,843 +311,645 @@ b.append("author A. U. Thor " + when + "\n"); b.append("committer A. U. Thor " + when + "\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testInvalidCommitNoTree1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("trie "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("z\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9b"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid tree", OBJ_COMMIT, data); } @Test public void testInvalidCommitInvalidTree4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("z\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent5() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertCorrupt("no author", OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoAuthor() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoAuthor() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoCommitter1() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoCommitter1() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no committer", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoCommitter2() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoCommitter2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no committer", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor1() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor1() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("missing email", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor3() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor3() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("missing email", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor4() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor4() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad date", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor5() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor5() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a \n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad date", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor6() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor6() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a z"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad date", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor7() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor7() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a 1 z"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad time zone", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidCommitter() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidCommitter() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a 1 +0000\n"); b.append("committer a <"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad email", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test public void testValidTag() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag test-tag\n"); b.append("tagger A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTag(data); - checker.check(Constants.OBJ_TAG, data); + checker.check(OBJ_TAG, data); } @Test public void testInvalidTagNoObject1() { - final StringBuilder b = new StringBuilder(); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, new byte[0]); } @Test public void testInvalidTagNoObject2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, b); } @Test public void testInvalidTagNoObject3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("obejct "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, b); } @Test public void testInvalidTagNoObject4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("zz9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoObject5() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append(" \n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoObject6() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoType1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type\tcommit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("tpye commit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag\tfoo\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tga foo\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testValidTagHasNoTaggerHeader() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); - - checker.checkTag(Constants.encodeASCII(b.toString())); + checker.checkTag(encodeASCII(b.toString())); } @Test public void testInvalidTagInvalidTaggerHeader1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); b.append("tagger \n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid tagger", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("missing email", OBJ_TAG, data); checker.setAllowInvalidPersonIdent(true); checker.checkTag(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_TAG, data); } @Test - public void testInvalidTagInvalidTaggerHeader3() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidTagInvalidTaggerHeader3() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); b.append("tagger a < 1 +000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid tagger", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("bad email", OBJ_TAG, data); + assertSkipListAccepts(OBJ_TAG, data); } @Test public void testValidEmptyTree() throws CorruptObjectException { checker.checkTree(new byte[0]); - checker.check(Constants.OBJ_TREE, new byte[0]); + checker.check(OBJ_TREE, new byte[0]); } @Test public void testValidTree1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 regular-file"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree2() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100755 executable"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree3() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 tree"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree4() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "120000 symlink"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree5() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "160000 git link"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree6() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 .a"); - final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(encodeASCII(b.toString())); + } + + @Test + public void testValidTreeWithGitmodules() throws CorruptObjectException { + ObjectId treeId = ObjectId + .fromString("0123012301230123012301230123012301230123"); + StringBuilder b = new StringBuilder(); + ObjectId blobId = entry(b, "100644 .gitmodules"); + + byte[] data = encodeASCII(b.toString()); + checker.checkTree(treeId, data); + assertEquals(1, checker.getGitsubmodules().size()); + assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId()); + assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId()); + } + + /* + * Windows case insensitivity and long file name handling + * means that .gitmodules has many synonyms. + * + * Examples inspired by git.git's t/t0060-path-utils.sh, by + * Johannes Schindelin and Congyi Wu. + */ + @Test + public void testNTFSGitmodules() throws CorruptObjectException { + for (String gitmodules : new String[] { + ".GITMODULES", + ".gitmodules", + ".Gitmodules", + ".gitmoduleS", + "gitmod~1", + "GITMOD~1", + "gitmod~4", + "GI7EBA~1", + "gi7eba~9", + "GI7EB~10", + "GI7E~123", + "~1000000", + "~9999999" + }) { + checker = new ObjectChecker(); // Reset the ObjectChecker state. + checker.setSafeForWindows(true); + ObjectId treeId = ObjectId + .fromString("0123012301230123012301230123012301230123"); + StringBuilder b = new StringBuilder(); + ObjectId blobId = entry(b, "100644 " + gitmodules); + + byte[] data = encodeASCII(b.toString()); + checker.checkTree(treeId, data); + assertEquals(1, checker.getGitsubmodules().size()); + assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId()); + assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId()); + } + } + + @Test + public void testNotGitmodules() throws CorruptObjectException { + for (String notGitmodules : new String[] { + ".gitmodu", + ".gitmodules oh never mind", + }) { + checker = new ObjectChecker(); // Reset the ObjectChecker state. + checker.setSafeForWindows(true); + ObjectId treeId = ObjectId + .fromString("0123012301230123012301230123012301230123"); + StringBuilder b = new StringBuilder(); + entry(b, "100644 " + notGitmodules); + + byte[] data = encodeASCII(b.toString()); + checker.checkTree(treeId, data); + assertEquals(0, checker.getGitsubmodules().size()); + } + } + + /* + * TODO HFS: match ".gitmodules" case-insensitively, after stripping out + * certain zero-length Unicode code points that HFS+ strips out + */ + + @Test + public void testValidTreeWithGitmodulesUppercase() + throws CorruptObjectException { + ObjectId treeId = ObjectId + .fromString("0123012301230123012301230123012301230123"); + StringBuilder b = new StringBuilder(); + ObjectId blobId = entry(b, "100644 .GITMODULES"); + + byte[] data = encodeASCII(b.toString()); + checker.setSafeForWindows(true); + checker.checkTree(treeId, data); + assertEquals(1, checker.getGitsubmodules().size()); + assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId()); + assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId()); + } + + @Test + public void testTreeWithInvalidGitmodules() throws CorruptObjectException { + ObjectId treeId = ObjectId + .fromString("0123012301230123012301230123012301230123"); + StringBuilder b = new StringBuilder(); + entry(b, "100644 .gitmodulez"); + + byte[] data = encodeASCII(b.toString()); + checker.checkTree(treeId, data); + checker.setSafeForWindows(true); + assertEquals(0, checker.getGitsubmodules().size()); + } + + @Test + public void testNullSha1InTreeEntry() throws CorruptObjectException { + byte[] data = concat( + encodeASCII("100644 A"), new byte[] { '\0' }, + new byte[OBJECT_ID_LENGTH]); + assertCorrupt("entry points to null SHA-1", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(NULL_SHA1, true); checker.checkTree(data); } @@ -1084,357 +963,313 @@ @Test public void testValidTreeSorting1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 fooaaa"); entry(b, "100755 foobar"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting2() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100755 fooaaa"); entry(b, "100644 foobar"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting3() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting4() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "40000 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting5() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a.c"); entry(b, "40000 a"); entry(b, "100644 a0c"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting6() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 apple"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting7() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 an orang"); entry(b, "40000 an orange"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting8() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a0c"); entry(b, "100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testAcceptTreeModeWithZero() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "040000 a"); + byte[] data = encodeASCII(b.toString()); checker.setAllowLeadingZeroFileMode(true); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(data); + + checker.setAllowLeadingZeroFileMode(false); + assertSkipListAccepts(OBJ_TREE, data); + + checker.setIgnore(ZERO_PADDED_FILEMODE, true); + checker.checkTree(data); } @Test public void testInvalidTreeModeStartsWithZero1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "0 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeStartsWithZero2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "0100644 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeStartsWithZero3() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "040000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeNotOctal1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "8 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode character", e.getMessage()); - } + assertCorrupt("invalid mode character", OBJ_TREE, b); } @Test public void testInvalidTreeModeNotOctal2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "Z a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode character", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid mode character", OBJ_TREE, data); + assertSkipListRejects("invalid mode character", OBJ_TREE, data); } @Test public void testInvalidTreeModeNotSupportedMode1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "1 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode 1", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid mode 1", OBJ_TREE, data); + assertSkipListRejects("invalid mode 1", OBJ_TREE, data); } @Test public void testInvalidTreeModeNotSupportedMode2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "170000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode " + 0170000, e.getMessage()); - } + assertCorrupt("invalid mode " + 0170000, OBJ_TREE, b); } @Test public void testInvalidTreeModeMissingName() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in mode", e.getMessage()); - } + assertCorrupt("truncated in mode", OBJ_TREE, b); } @Test - public void testInvalidTreeNameContainsSlash() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeNameContainsSlash() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a/b"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("name contains '/'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("name contains '/'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(FULL_PATHNAME, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsEmpty() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeNameIsEmpty() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 "); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("zero length name", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("zero length name", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(EMPTY_NAME, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDot() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeNameIsDot() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 ."); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotDot() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeNameIsDotDot() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 .."); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '..'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '..'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTDOT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsGit() { + public void testInvalidTreeNameIsGit() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsMixedCaseGit() { + public void testInvalidTreeNameIsMixedCaseGit() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .GiT"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.GiT'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.GiT'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsMacHFSGit() { + public void testInvalidTreeNameIsMacHFSGit() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gi\u200Ct"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '.gi\u200Ct' contains ignorable Unicode characters", - e.getMessage()); - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '.gi\u200Ct' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsMacHFSGit2() { + public void testInvalidTreeNameIsMacHFSGit2() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 \u206B.git"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '\u206B.git' contains ignorable Unicode characters", - e.getMessage()); - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '\u206B.git' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsMacHFSGit3() { + public void testInvalidTreeNameIsMacHFSGit3() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\uFEFF"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '.git\uFEFF' contains ignorable Unicode characters", - e.getMessage()); - } - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); - private static byte[] concat(byte[] b1, byte[] b2) { - byte[] data = new byte[b1.length + b2.length]; - System.arraycopy(b1, 0, data, 0, b1.length); - System.arraycopy(b2, 0, data, b1.length, b2.length); - return data; + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '.git\uFEFF' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } + + @Test - public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() { - byte[] data = concat(Constants.encode("100644 .git"), + public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() + throws CorruptObjectException { + byte[] data = concat(encode("100644 .git"), new byte[] { (byte) 0xef }); StringBuilder b = new StringBuilder(); entry(b, ""); - data = concat(data, Constants.encode(b.toString())); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name contains byte sequence '0xef' which is not a valid UTF-8 character", - e.getMessage()); - } + data = concat(data, encode(b.toString())); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name contains byte sequence '0xef' which is not a valid UTF-8 character", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() { - byte[] data = concat(Constants.encode("100644 .git"), new byte[] { + public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() + throws CorruptObjectException { + byte[] data = concat(encode("100644 .git"), + new byte[] { (byte) 0xe2, (byte) 0xab }); StringBuilder b = new StringBuilder(); entry(b, ""); - data = concat(data, Constants.encode(b.toString())); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character", - e.getMessage()); - } + data = concat(data, encode(b.toString())); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test @@ -1442,7 +1277,7 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\u200Cx"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.setSafeForMacOS(true); checker.checkTree(data); } @@ -1452,7 +1287,7 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .kit\u200C"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.setSafeForMacOS(true); checker.checkTree(data); } @@ -1462,21 +1297,19 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\u200C"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotGitDot() { + public void testInvalidTreeNameIsDotGitDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git."); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git.'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git.'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1484,20 +1317,19 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git.."); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(encodeASCII(b.toString())); } @Test - public void testInvalidTreeNameIsDotGitSpace() { + public void testInvalidTreeNameIsDotGitSpace() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1505,7 +1337,7 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1514,7 +1346,7 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoo bar"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1523,7 +1355,7 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar."); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1532,266 +1364,251 @@ throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar.."); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotGitDotSpace() { + public void testInvalidTreeNameIsDotGitDotSpace() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git. "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git. '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git. '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotGitSpaceDot() { + public void testInvalidTreeNameIsDotGitSpaceDot() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git . "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git . '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git . '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsGITTilde1() { + public void testInvalidTreeNameIsGITTilde1() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GIT~1"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name 'GIT~1'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsGiTTilde1() { + public void testInvalidTreeNameIsGiTTilde1() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GiT~1"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name 'GiT~1'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test public void testValidTreeNameIsGitTilde11() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GIT~11"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @Test public void testInvalidTreeTruncatedInName() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in name", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("truncated in name", OBJ_TREE, data); + assertSkipListRejects("truncated in name", OBJ_TREE, data); } @Test public void testInvalidTreeTruncatedInObjectId() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644 b\0\1\2"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in object id", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("truncated in object id", OBJ_TREE, data); + assertSkipListRejects("truncated in object id", OBJ_TREE, data); } @Test - public void testInvalidTreeBadSorting1() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting1() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 foobar"); entry(b, "100644 fooaaa"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); + + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + + ObjectId id = idFor(OBJ_TREE, data); try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); + checker.check(id, OBJ_TREE, data); + fail("Did not throw CorruptObjectException"); } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); + assertSame(TREE_NOT_SORTED, e.getErrorType()); + assertEquals("treeNotSorted: object " + id.name() + + ": incorrectly sorted", e.getMessage()); } + + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test - public void testInvalidTreeBadSorting2() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 a.c"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test - public void testInvalidTreeBadSorting3() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting3() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a0c"); entry(b, "40000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test - public void testInvalidTreeDuplicateNames1() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames1_File() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); + } + + @Test + public void testInvalidTreeDuplicateNames1_Tree() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "40000 a"); + entry(b, "40000 a"); + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test - public void testInvalidTreeDuplicateNames2() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100755 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test - public void testInvalidTreeDuplicateNames3() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames3() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "40000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test - public void testInvalidTreeDuplicateNames4() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames4() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a.c"); entry(b, "100644 a.d"); entry(b, "100644 a.e"); entry(b, "40000 a"); entry(b, "100644 zoo"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames5() - throws UnsupportedEncodingException { + throws CorruptObjectException { StringBuilder b = new StringBuilder(); - entry(b, "100644 a"); entry(b, "100644 A"); - byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForWindows(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + entry(b, "100644 a"); + byte[] data = b.toString().getBytes(UTF_8); + checker.setSafeForWindows(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames6() - throws UnsupportedEncodingException { + throws CorruptObjectException { StringBuilder b = new StringBuilder(); - entry(b, "100644 a"); entry(b, "100644 A"); - byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + entry(b, "100644 a"); + byte[] data = b.toString().getBytes(UTF_8); + checker.setSafeForMacOS(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames7() - throws UnsupportedEncodingException { - try { - Class.forName("java.text.Normalizer"); - } catch (ClassNotFoundException e) { - // Ignore this test on Java 5 platform. - return; - } - + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 \u0065\u0301"); entry(b, "100644 \u00e9"); - byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = b.toString().getBytes(UTF_8); + checker.setSafeForMacOS(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames8() - throws UnsupportedEncodingException, CorruptObjectException { + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 A"); checker.setSafeForMacOS(true); - checker.checkTree(b.toString().getBytes("UTF-8")); + checker.checkTree(b.toString().getBytes(UTF_8)); } @Test public void testRejectNulInPathSegment() { try { - checker.checkPathSegment(Constants.encodeASCII("a\u0000b"), 0, 3); + checker.checkPathSegment(encodeASCII("a\u0000b"), 0, 3); fail("incorrectly accepted NUL in middle of name"); } catch (CorruptObjectException e) { assertEquals("name contains byte 0x00", e.getMessage()); @@ -1893,13 +1710,74 @@ private void checkOneName(String name) throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 " + name); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(encodeASCII(b.toString())); } - private static void entry(final StringBuilder b, final String modeName) { + /* + * Returns the id generated for the entry + */ + private static ObjectId entry(StringBuilder b, String modeName) { + byte[] id = new byte[OBJECT_ID_LENGTH]; + b.append(modeName); b.append('\0'); - for (int i = 0; i < Constants.OBJECT_ID_LENGTH; i++) + for (int i = 0; i < OBJECT_ID_LENGTH; i++) { b.append((char) i); + id[i] = (byte) i; + } + + return ObjectId.fromRaw(id); + } + + private void assertCorrupt(String msg, int type, StringBuilder b) { + assertCorrupt(msg, type, encodeASCII(b.toString())); + } + + private void assertCorrupt(String msg, int type, byte[] data) { + try { + checker.check(type, data); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + assertEquals(msg, e.getMessage()); + } + } + + private void assertSkipListAccepts(int type, byte[] data) + throws CorruptObjectException { + ObjectId id = idFor(type, data); + checker.setSkipList(set(id)); + checker.check(id, type, data); + checker.setSkipList(null); + } + + private void assertSkipListRejects(String msg, int type, byte[] data) { + ObjectId id = idFor(type, data); + checker.setSkipList(set(id)); + try { + checker.check(id, type, data); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + assertEquals(msg, e.getMessage()); + } + checker.setSkipList(null); + } + + private static ObjectIdSet set(final ObjectId... ids) { + return new ObjectIdSet() { + @Override + public boolean contains(AnyObjectId objectId) { + for (ObjectId id : ids) { + if (id.equals(objectId)) { + return true; + } + } + return false; + } + }; + } + + @SuppressWarnings("resource") + private static ObjectId idFor(int type, byte[] raw) { + return new ObjectInserter.Formatter().idFor(type, raw); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,7 +73,7 @@ @Test public void testEmptyMap() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); assertTrue(m.isEmpty()); assertEquals(0, m.size()); @@ -86,7 +86,7 @@ @Test public void testAddGetAndContains() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); m.add(id_2); m.add(id_3); @@ -108,7 +108,7 @@ @Test public void testClear() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); assertSame(id_1, m.get(id_1)); @@ -126,7 +126,7 @@ @Test public void testAddIfAbsent() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); assertSame(id_1, m.addIfAbsent(new SubId(id_1))); @@ -145,7 +145,7 @@ @Test public void testAddGrowsWithObjects() { int n = 16384; - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); for (int i = 32; i < n; i++) m.add(new SubId(id(i))); @@ -159,7 +159,7 @@ @Test public void testAddIfAbsentGrowsWithObjects() { int n = 16384; - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); for (int i = 32; i < n; i++) m.addIfAbsent(new SubId(id(i))); @@ -172,7 +172,7 @@ @Test public void testIterator() { - ObjectIdOwnerMap m = new ObjectIdOwnerMap(); + ObjectIdOwnerMap m = new ObjectIdOwnerMap<>(); m.add(id_1); m.add(id_2); m.add(id_3); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSerializerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSerializerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSerializerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSerializerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018, David Pursehouse + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.Test; + +public class ObjectIdSerializerTest { + @Test + public void serialize() throws Exception { + ObjectId original = new ObjectId(1, 2, 3, 4, 5); + ObjectId deserialized = writeAndReadBackFromTempFile(original); + assertEquals(original, deserialized); + } + + @Test + public void serializeZeroId() throws Exception { + ObjectId original = ObjectId.zeroId(); + ObjectId deserialized = writeAndReadBackFromTempFile(original); + assertEquals(original, deserialized); + } + + @Test + public void serializeNull() throws Exception { + ObjectId deserialized = writeAndReadBackFromTempFile(null); + assertNull(deserialized); + } + + private ObjectId writeAndReadBackFromTempFile(ObjectId objectId) + throws Exception { + File file = File.createTempFile("ObjectIdSerializerTest_", ""); + try (OutputStream out = new FileOutputStream(file)) { + if (objectId == null) { + ObjectIdSerializer.write(out, objectId); + } else { + ObjectIdSerializer.writeWithoutMarker(out, objectId); + } + } + try (InputStream in = new FileInputStream(file)) { + if (objectId == null) { + return ObjectIdSerializer.read(in); + } else { + return ObjectIdSerializer.readWithoutMarker(in); + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,7 +73,7 @@ @Test public void testEmptyMap() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); assertTrue(m.isEmpty()); assertEquals(0, m.size()); @@ -86,7 +86,7 @@ @Test public void testAddGetAndContains() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); m.add(id_2); m.add(id_3); @@ -108,7 +108,7 @@ @Test public void testClear() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); assertSame(id_1, m.get(id_1)); @@ -126,7 +126,7 @@ @Test public void testAddIfAbsent() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); assertSame(id_1, m.addIfAbsent(new SubId(id_1))); @@ -144,7 +144,7 @@ @Test public void testAddGrowsWithObjects() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); for (int i = 32; i < 8000; i++) m.add(new SubId(id(i))); @@ -157,7 +157,7 @@ @Test public void testAddIfAbsentGrowsWithObjects() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); for (int i = 32; i < 8000; i++) m.addIfAbsent(new SubId(id(i))); @@ -170,7 +170,7 @@ @Test public void testIterator() { - ObjectIdSubclassMap m = new ObjectIdSubclassMap(); + ObjectIdSubclassMap m = new ObjectIdSubclassMap<>(); m.add(id_1); m.add(id_2); m.add(id_3); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -49,8 +49,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.eclipse.jgit.errors.InvalidObjectIdException; +import java.util.Locale; +import org.eclipse.jgit.errors.InvalidObjectIdException; import org.junit.Test; public class ObjectIdTest { @@ -123,7 +124,7 @@ public void test011_toString() { final String x = "0123456789ABCDEFabcdef1234567890abcdefAB"; final ObjectId oid = ObjectId.fromString(x); - assertEquals(x.toLowerCase(), oid.name()); + assertEquals(x.toLowerCase(Locale.ROOT), oid.name()); } @Test(expected = InvalidObjectIdException.class) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,127 +42,85 @@ */ package org.eclipse.jgit.lib; -import static java.lang.Long.valueOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.TreeSet; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.treewalk.FileTreeIterator; -import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl; -import org.eclipse.jgit.treewalk.NameConflictTreeWalk; -import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; +import org.junit.Test; public class RacyGitTests extends RepositoryTestCase { - public void testIterator() throws IllegalStateException, IOException, - InterruptedException { - TreeSet modTimes = new TreeSet(); - File lastFile = null; - for (int i = 0; i < 10; i++) { - lastFile = new File(db.getWorkTree(), "0." + i); - FileUtils.createNewFile(lastFile); - if (i == 5) - fsTick(lastFile); - } - modTimes.add(valueOf(fsTick(lastFile))); - for (int i = 0; i < 10; i++) { - lastFile = new File(db.getWorkTree(), "1." + i); - FileUtils.createNewFile(lastFile); - } - modTimes.add(valueOf(fsTick(lastFile))); - for (int i = 0; i < 10; i++) { - lastFile = new File(db.getWorkTree(), "2." + i); - FileUtils.createNewFile(lastFile); - if (i % 4 == 0) - fsTick(lastFile); - } - FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl( - db, modTimes); - NameConflictTreeWalk tw = new NameConflictTreeWalk(db); - tw.addTree(fileIt); - tw.setRecursive(true); - FileTreeIterator t; - long t0 = 0; - for (int i = 0; i < 10; i++) { - assertTrue(tw.next()); - t = tw.getTree(0, FileTreeIterator.class); - if (i == 0) - t0 = t.getEntryLastModified(); - else - assertEquals(t0, t.getEntryLastModified()); - } - long t1 = 0; - for (int i = 0; i < 10; i++) { - assertTrue(tw.next()); - t = tw.getTree(0, FileTreeIterator.class); - if (i == 0) { - t1 = t.getEntryLastModified(); - assertTrue(t1 > t0); - } else - assertEquals(t1, t.getEntryLastModified()); - } - long t2 = 0; - for (int i = 0; i < 10; i++) { - assertTrue(tw.next()); - t = tw.getTree(0, FileTreeIterator.class); - if (i == 0) { - t2 = t.getEntryLastModified(); - assertTrue(t2 > t1); - } else - assertEquals(t2, t.getEntryLastModified()); - } - } - public void testRacyGitDetection() throws IOException, - IllegalStateException, InterruptedException { - TreeSet modTimes = new TreeSet(); - File lastFile; + @Test + public void testRacyGitDetection() throws Exception { + // Reset to force creation of index file + try (Git git = new Git(db)) { + git.reset().call(); + } // wait to ensure that modtimes of the file doesn't match last index // file modtime - modTimes.add(valueOf(fsTick(db.getIndexFile()))); + fsTick(db.getIndexFile()); // create two files - addToWorkDir("a", "a"); - lastFile = addToWorkDir("b", "b"); + File a = writeToWorkDir("a", "a"); + File b = writeToWorkDir("b", "b"); + assertTrue(a.setLastModified(b.lastModified())); + assertTrue(b.setLastModified(b.lastModified())); // wait to ensure that file-modTimes and therefore index entry modTime // doesn't match the modtime of index-file after next persistance - modTimes.add(valueOf(fsTick(lastFile))); + fsTick(b); // now add both files to the index. No racy git expected - resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes)); + resetIndex(new FileTreeIterator(db)); assertEquals( - "[a, mode:100644, time:t0, length:1, content:a]" + - "[b, mode:100644, time:t0, length:1, content:b]", + "[a, mode:100644, time:t0, length:1, content:a]" + + "[b, mode:100644, time:t0, length:1, content:b]", indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT)); - // Remember the last modTime of index file. All modifications times of - // further modification are translated to this value so it looks that - // files have been modified in the same time slot as the index file - modTimes.add(Long.valueOf(db.getIndexFile().lastModified())); - - // modify one file - addToWorkDir("a", "a2"); - // now update the index the index. 'a' has to be racily clean -- because - // it's modification time is exactly the same as the previous index file - // mod time. - resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes)); + // wait to ensure the file 'a' is updated at t1. + fsTick(db.getIndexFile()); - db.readDirCache(); - // although racily clean a should not be reported as being dirty + // Create a racy git situation. This is a situation that the index is + // updated and then a file is modified within the same tick of the + // filesystem timestamp resolution. By changing the index file + // artificially, we create a fake racy situation. + File updatedA = writeToWorkDir("a", "a2"); + long newLastModified = updatedA.lastModified() + 100; + assertTrue(updatedA.setLastModified(newLastModified)); + resetIndex(new FileTreeIterator(db)); + assertTrue(db.getIndexFile().setLastModified(newLastModified)); + + DirCache dc = db.readDirCache(); + // check index state: although racily clean a should not be reported as + // being dirty since we forcefully reset the index to match the working + // tree assertEquals( - "[a, mode:100644, time:t1, smudged, length:0, content:a2]" + - "[b, mode:100644, time:t0, length:1, content:b]", - indexState(SMUDGE|MOD_TIME|LENGTH|CONTENT)); + "[a, mode:100644, time:t1, smudged, length:0, content:a2]" + + "[b, mode:100644, time:t0, length:1, content:b]", + indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT)); + + // compare state of files in working tree with index to check that + // FileTreeIterator.isModified() works as expected + FileTreeIterator f = new FileTreeIterator(db.getWorkTree(), db.getFS(), + db.getConfig().get(WorkingTreeOptions.KEY)); + assertTrue(f.findFile("a")); + try (ObjectReader reader = db.newObjectReader()) { + assertFalse(f.isModified(dc.getEntry("a"), false, reader)); + } } - private File addToWorkDir(String path, String content) throws IOException { + private File writeToWorkDir(String path, String content) throws IOException { File f = new File(db.getWorkTree(), path); FileOutputStream fos = new FileOutputStream(f); try { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -63,7 +63,7 @@ @Override public Map getRefs(String prefix) throws IOException { if (ALL.equals(prefix)) { - Map existing = new HashMap(); + Map existing = new HashMap<>(); existing.put("refs/heads/a/b", null /* not used */); existing.put("refs/heads/q", null /* not used */); return existing; @@ -141,8 +141,8 @@ private void assertConflictingNames(String proposed, String... conflicts) throws IOException { - Set expected = new HashSet(Arrays.asList(conflicts)); + Set expected = new HashSet<>(Arrays.asList(conflicts)); assertEquals(expected, - new HashSet(refDatabase.getConflictingNames(proposed))); + new HashSet<>(refDatabase.getConflictingNames(proposed))); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -70,8 +70,7 @@ // do one commit and check that reflog size is 0: no reflogs should be // written - commit("A Commit\n", new PersonIdent(author, commitTime, tz), - new PersonIdent(committer, commitTime, tz)); + commit("A Commit\n", commitTime, tz); commitTime += 60 * 1000; assertTrue( "Reflog for HEAD still contain no entry", @@ -83,8 +82,7 @@ assertTrue(cfg.get(CoreConfig.KEY).isLogAllRefUpdates()); // do one commit and check that reflog size is increased to 1 - commit("A Commit\n", new PersonIdent(author, commitTime, tz), - new PersonIdent(committer, commitTime, tz)); + commit("A Commit\n", commitTime, tz); commitTime += 60 * 1000; assertTrue( "Reflog for HEAD should contain one entry", @@ -96,18 +94,17 @@ assertFalse(cfg.get(CoreConfig.KEY).isLogAllRefUpdates()); // do one commit and check that reflog size is 2 - commit("A Commit\n", new PersonIdent(author, commitTime, tz), - new PersonIdent(committer, commitTime, tz)); + commit("A Commit\n", commitTime, tz); assertTrue( "Reflog for HEAD should contain two entries", db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 2); } - private void commit(String commitMsg, PersonIdent author, - PersonIdent committer) throws IOException { + private void commit(String commitMsg, long commitTime, int tz) + throws IOException { final CommitBuilder commit = new CommitBuilder(); - commit.setAuthor(author); - commit.setCommitter(committer); + commit.setAuthor(new PersonIdent(author, commitTime, tz)); + commit.setCommitter(new PersonIdent(committer, commitTime, tz)); commit.setMessage(commitMsg); ObjectId id; try (ObjectInserter inserter = db.newObjectInserter()) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,117 +60,123 @@ @Test public void resolveMasterCommits() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - RevCommit c2 = git.commit().setMessage("edit file").call(); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + RevCommit c2 = git.commit().setMessage("edit file").call(); - assertEquals(c2, db.resolve("master@{0}")); - assertEquals(c1, db.resolve("master@{1}")); + assertEquals(c2, db.resolve("master@{0}")); + assertEquals(c1, db.resolve("master@{1}")); + } } @Test public void resolveUnnamedCurrentBranchCommits() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - RevCommit c2 = git.commit().setMessage("edit file").call(); - - assertEquals(c2, db.resolve("master@{0}")); - assertEquals(c1, db.resolve("master@{1}")); - - git.checkout().setCreateBranch(true).setName("newbranch") - .setStartPoint(c1).call(); - - // same as current branch, e.g. master - assertEquals(c1, db.resolve("@{0}")); - try { + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + RevCommit c2 = git.commit().setMessage("edit file").call(); + + assertEquals(c2, db.resolve("master@{0}")); + assertEquals(c1, db.resolve("master@{1}")); + + git.checkout().setCreateBranch(true).setName("newbranch") + .setStartPoint(c1).call(); + + // same as current branch, e.g. master + assertEquals(c1, db.resolve("@{0}")); + try { + assertEquals(c1, db.resolve("@{1}")); + fail(); // Looking at wrong ref, e.g HEAD + } catch (RevisionSyntaxException e) { + assertNotNull(e); + } + + // detached head, read HEAD reflog + git.checkout().setName(c2.getName()).call(); + assertEquals(c2, db.resolve("@{0}")); assertEquals(c1, db.resolve("@{1}")); - fail(); // Looking at wrong ref, e.g HEAD - } catch (RevisionSyntaxException e) { - assertNotNull(e); + assertEquals(c2, db.resolve("@{2}")); } - - // detached head, read HEAD reflog - git.checkout().setName(c2.getName()).call(); - assertEquals(c2, db.resolve("@{0}")); - assertEquals(c1, db.resolve("@{1}")); - assertEquals(c2, db.resolve("@{2}")); } @Test public void resolveReflogParent() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("edit file").call(); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("edit file").call(); - assertEquals(c1, db.resolve("master@{0}~1")); + assertEquals(c1, db.resolve("master@{0}~1")); + } } @Test public void resolveNonExistingBranch() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - assertNull(db.resolve("notabranch@{7}")); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + assertNull(db.resolve("notabranch@{7}")); + } } @Test public void resolvePreviousBranch() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - writeTrashFile("file.txt", "content2"); - git.add().addFilepattern("file.txt").call(); - RevCommit c2 = git.commit().setMessage("edit file").call(); - - git.checkout().setCreateBranch(true).setName("newbranch") - .setStartPoint(c1).call(); - - git.checkout().setName(c1.getName()).call(); - - git.checkout().setName("master").call(); - - assertEquals(c1.getName(), db.simplify("@{-1}")); - assertEquals("newbranch", db.simplify("@{-2}")); - assertEquals("master", db.simplify("@{-3}")); - - // chained expression - try { - // Cannot refer to reflog of detached head - db.resolve("@{-1}@{0}"); - fail(); - } catch (RevisionSyntaxException e) { - // good - } - assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName()); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + RevCommit c2 = git.commit().setMessage("edit file").call(); + + git.checkout().setCreateBranch(true).setName("newbranch") + .setStartPoint(c1).call(); + + git.checkout().setName(c1.getName()).call(); + + git.checkout().setName("master").call(); + + assertEquals(c1.getName(), db.simplify("@{-1}")); + assertEquals("newbranch", db.simplify("@{-2}")); + assertEquals("master", db.simplify("@{-3}")); + + // chained expression + try { + // Cannot refer to reflog of detached head + db.resolve("@{-1}@{0}"); + fail(); + } catch (RevisionSyntaxException e) { + // good + } + assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName()); - assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName()); + assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName()); + } } @Test public void resolveDate() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - try { - db.resolve("master@{yesterday}"); - fail("Exception not thrown"); - } catch (RevisionSyntaxException e) { - assertNotNull(e); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + try { + db.resolve("master@{yesterday}"); + fail("Exception not thrown"); + } catch (RevisionSyntaxException e) { + assertNotNull(e); + } } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -99,7 +99,7 @@ "ab/c", "dummy", true); config.save(); assertEquals("[ab/c, origin]", - new TreeSet(db.getRemoteNames()).toString()); + new TreeSet<>(db.getRemoteNames()).toString()); // one-level deep remote branch assertEquals("master", @@ -163,7 +163,7 @@ @Test public void testReadSymRefToPacked() throws IOException { writeSymref("HEAD", "refs/heads/b"); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals(Ref.Storage.LOOSE, ref.getStorage()); assertTrue("is symref", ref.isSymbolic()); ref = ref.getTarget(); @@ -181,7 +181,7 @@ assertEquals(Result.FORCED, update); // internal writeSymref("HEAD", "refs/heads/master"); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals(Ref.Storage.LOOSE, ref.getStorage()); ref = ref.getTarget(); assertEquals("refs/heads/master", ref.getName()); @@ -194,13 +194,13 @@ updateRef.setNewObjectId(db.resolve("refs/heads/master")); Result update = updateRef.update(); assertEquals(Result.NEW, update); - Ref ref = db.getRef("ref/heads/new"); + Ref ref = db.exactRef("ref/heads/new"); assertEquals(Storage.LOOSE, ref.getStorage()); } @Test public void testGetShortRef() throws IOException { - Ref ref = db.getRef("master"); + Ref ref = db.exactRef("refs/heads/master"); assertEquals("refs/heads/master", ref.getName()); assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); } @@ -222,7 +222,7 @@ assertNull(db.getRefDatabase().exactRef("refs/foo/bar")); - Ref ref = db.getRef("refs/foo/bar"); + Ref ref = db.findRef("refs/foo/bar"); assertEquals("refs/heads/refs/foo/bar", ref.getName()); assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); } @@ -237,7 +237,7 @@ assertEquals("refs/foo/bar", exactRef.getName()); assertEquals(masterId, exactRef.getObjectId()); - Ref ref = db.getRef("refs/foo/bar"); + Ref ref = db.findRef("refs/foo/bar"); assertEquals("refs/foo/bar", ref.getName()); assertEquals(masterId, ref.getObjectId()); } @@ -251,7 +251,7 @@ @Test public void testReadLoosePackedRef() throws IOException, InterruptedException { - Ref ref = db.getRef("refs/heads/master"); + Ref ref = db.exactRef("refs/heads/master"); assertEquals(Storage.PACKED, ref.getStorage()); FileOutputStream os = new FileOutputStream(new File(db.getDirectory(), "refs/heads/master")); @@ -259,7 +259,7 @@ os.write('\n'); os.close(); - ref = db.getRef("refs/heads/master"); + ref = db.exactRef("refs/heads/master"); assertEquals(Storage.LOOSE, ref.getStorage()); } @@ -271,7 +271,7 @@ */ @Test public void testReadSimplePackedRefSameRepo() throws IOException { - Ref ref = db.getRef("refs/heads/master"); + Ref ref = db.exactRef("refs/heads/master"); ObjectId pid = db.resolve("refs/heads/master^"); assertEquals(Storage.PACKED, ref.getStorage()); RefUpdate updateRef = db.updateRef("refs/heads/master"); @@ -280,19 +280,19 @@ Result update = updateRef.update(); assertEquals(Result.FORCED, update); - ref = db.getRef("refs/heads/master"); + ref = db.exactRef("refs/heads/master"); assertEquals(Storage.LOOSE, ref.getStorage()); } @Test public void testResolvedNamesBranch() throws IOException { - Ref ref = db.getRef("a"); + Ref ref = db.findRef("a"); assertEquals("refs/heads/a", ref.getName()); } @Test public void testResolvedSymRef() throws IOException { - Ref ref = db.getRef(Constants.HEAD); + Ref ref = db.exactRef(Constants.HEAD); assertEquals(Constants.HEAD, ref.getName()); assertTrue("is symbolic ref", ref.isSymbolic()); assertSame(Ref.Storage.LOOSE, ref.getStorage()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 Ericsson + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.eclipse.jgit.lib.RepositoryCacheConfig.AUTO_CLEANUP_DELAY; +import static org.eclipse.jgit.lib.RepositoryCacheConfig.NO_CLEANUP; +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.junit.Before; +import org.junit.Test; + +public class RepositoryCacheConfigTest { + + private RepositoryCacheConfig config; + + @Before + public void setUp() { + config = new RepositoryCacheConfig(); + } + + @Test + public void testDefaultValues() { + assertEquals(TimeUnit.HOURS.toMillis(1), config.getExpireAfter()); + assertEquals(config.getExpireAfter() / 10, config.getCleanupDelay()); + } + + @Test + public void testCleanupDelay() { + config.setCleanupDelay(TimeUnit.HOURS.toMillis(1)); + assertEquals(TimeUnit.HOURS.toMillis(1), config.getCleanupDelay()); + } + + @Test + public void testAutoCleanupDelay() { + config.setExpireAfter(TimeUnit.MINUTES.toMillis(20)); + config.setCleanupDelay(AUTO_CLEANUP_DELAY); + assertEquals(TimeUnit.MINUTES.toMillis(20), config.getExpireAfter()); + assertEquals(config.getExpireAfter() / 10, config.getCleanupDelay()); + } + + @Test + public void testAutoCleanupDelayShouldBeMax10minutes() { + config.setExpireAfter(TimeUnit.HOURS.toMillis(10)); + assertEquals(TimeUnit.HOURS.toMillis(10), config.getExpireAfter()); + assertEquals(TimeUnit.MINUTES.toMillis(10), config.getCleanupDelay()); + } + + @Test + public void testDisabledCleanupDelay() { + config.setCleanupDelay(NO_CLEANUP); + assertEquals(NO_CLEANUP, config.getCleanupDelay()); + } + + @Test + public void testFromConfig() throws ConfigInvalidException { + Config otherConfig = new Config(); + otherConfig.fromText("[core]\nrepositoryCacheExpireAfter=1000\n" + + "repositoryCacheCleanupDelay=500"); + config.fromConfig(otherConfig); + assertEquals(1000, config.getExpireAfter()); + assertEquals(500, config.getCleanupDelay()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -109,7 +109,7 @@ @Test public void testFileKeyOpenNew() throws IOException { - final Repository n = createBareRepository(); + final Repository n = createRepository(true, false); final File gitdir = n.getDirectory(); n.close(); recursiveDelete(gitdir); @@ -173,4 +173,127 @@ assertEquals(0, RepositoryCache.getRegisteredKeys().size()); } + @Test + public void testRepositoryUsageCount() throws Exception { + FileKey loc = FileKey.exact(db.getDirectory(), db.getFS()); + Repository d2 = RepositoryCache.open(loc); + assertEquals(1, d2.useCnt.get()); + RepositoryCache.open(FileKey.exact(loc.getFile(), db.getFS())); + assertEquals(2, d2.useCnt.get()); + d2.close(); + assertEquals(1, d2.useCnt.get()); + d2.close(); + assertEquals(0, d2.useCnt.get()); + } + + @Test + public void testRepositoryUsageCountWithRegisteredRepository() + throws IOException { + Repository repo = createRepository(false, false); + assertEquals(1, repo.useCnt.get()); + RepositoryCache.register(repo); + assertEquals(1, repo.useCnt.get()); + repo.close(); + assertEquals(0, repo.useCnt.get()); + } + + @Test + public void testRepositoryNotUnregisteringWhenClosing() throws Exception { + FileKey loc = FileKey.exact(db.getDirectory(), db.getFS()); + Repository d2 = RepositoryCache.open(loc); + assertEquals(1, d2.useCnt.get()); + assertThat(RepositoryCache.getRegisteredKeys(), + hasItem(FileKey.exact(db.getDirectory(), db.getFS()))); + assertEquals(1, RepositoryCache.getRegisteredKeys().size()); + d2.close(); + assertEquals(0, d2.useCnt.get()); + assertEquals(1, RepositoryCache.getRegisteredKeys().size()); + assertTrue(RepositoryCache.isCached(d2)); + } + + @Test + public void testRepositoryUnregisteringWhenExpiredAndUsageCountNegative() + throws Exception { + Repository repoA = createBareRepository(); + RepositoryCache.register(repoA); + + assertEquals(1, RepositoryCache.getRegisteredKeys().size()); + assertTrue(RepositoryCache.isCached(repoA)); + + // close the repo twice to make usage count negative + repoA.close(); + repoA.close(); + // fake that repoA was closed more than 1 hour ago (default expiration + // time) + repoA.closedAt.set(System.currentTimeMillis() - 65 * 60 * 1000); + + RepositoryCache.clearExpired(); + + assertEquals(0, RepositoryCache.getRegisteredKeys().size()); + } + + @Test + public void testRepositoryUnregisteringWhenExpired() throws Exception { + Repository repoA = createRepository(true, false); + Repository repoB = createRepository(true, false); + Repository repoC = createBareRepository(); + RepositoryCache.register(repoA); + RepositoryCache.register(repoB); + RepositoryCache.register(repoC); + + assertEquals(3, RepositoryCache.getRegisteredKeys().size()); + assertTrue(RepositoryCache.isCached(repoA)); + assertTrue(RepositoryCache.isCached(repoB)); + assertTrue(RepositoryCache.isCached(repoC)); + + // fake that repoA was closed more than 1 hour ago (default expiration + // time) + repoA.close(); + repoA.closedAt.set(System.currentTimeMillis() - 65 * 60 * 1000); + // close repoB but this one will not be expired + repoB.close(); + + assertEquals(3, RepositoryCache.getRegisteredKeys().size()); + assertTrue(RepositoryCache.isCached(repoA)); + assertTrue(RepositoryCache.isCached(repoB)); + assertTrue(RepositoryCache.isCached(repoC)); + + RepositoryCache.clearExpired(); + + assertEquals(2, RepositoryCache.getRegisteredKeys().size()); + assertFalse(RepositoryCache.isCached(repoA)); + assertTrue(RepositoryCache.isCached(repoB)); + assertTrue(RepositoryCache.isCached(repoC)); + } + + @Test + public void testReconfigure() throws InterruptedException, IOException { + Repository repo = createRepository(false, false); + RepositoryCache.register(repo); + assertTrue(RepositoryCache.isCached(repo)); + repo.close(); + assertTrue(RepositoryCache.isCached(repo)); + + // Actually, we would only need to validate that + // WorkQueue.getExecutor().scheduleWithFixedDelay is called with proper + // values but since we do not have a mock library, we test + // reconfiguration from a black box perspective. I.e. reconfigure + // expireAfter and cleanupDelay to 1 ms and wait until the Repository + // is evicted to prove that reconfiguration worked. + RepositoryCacheConfig config = new RepositoryCacheConfig(); + config.setExpireAfter(1); + config.setCleanupDelay(1); + config.install(); + + // Instead of using a fixed waiting time, start with small and increase: + // sleep 1, 2, 4, 8, 16, ..., 1024 ms + // This wait will time out after 2048 ms + for (int i = 0; i <= 10; i++) { + Thread.sleep(1 << i); + if (!RepositoryCache.isCached(repo)) { + return; + } + } + fail("Repository should have been evicted from cache"); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -267,35 +267,37 @@ @Test public void resolveExprSimple() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - git.commit().setMessage("create file").call(); - assertEquals("master", db.simplify("master")); - assertEquals("refs/heads/master", db.simplify("refs/heads/master")); - assertEquals("HEAD", db.simplify("HEAD")); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + assertEquals("master", db.simplify("master")); + assertEquals("refs/heads/master", db.simplify("refs/heads/master")); + assertEquals("HEAD", db.simplify("HEAD")); + } } @Test public void resolveUpstream() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit c1 = git.commit().setMessage("create file").call(); - writeTrashFile("file2.txt", "content"); - RefUpdate updateRemoteRef = db.updateRef("refs/remotes/origin/main"); - updateRemoteRef.setNewObjectId(c1); - updateRemoteRef.update(); - db.getConfig().setString("branch", "master", "remote", "origin"); - db.getConfig() - .setString("branch", "master", "merge", "refs/heads/main"); - db.getConfig().setString("remote", "origin", "url", - "git://example.com/here"); - db.getConfig().setString("remote", "origin", "fetch", - "+refs/heads/*:refs/remotes/origin/*"); - git.add().addFilepattern("file2.txt").call(); - git.commit().setMessage("create file").call(); - assertEquals("refs/remotes/origin/main", db.simplify("@{upstream}")); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit c1 = git.commit().setMessage("create file").call(); + writeTrashFile("file2.txt", "content"); + RefUpdate updateRemoteRef = db.updateRef("refs/remotes/origin/main"); + updateRemoteRef.setNewObjectId(c1); + updateRemoteRef.update(); + db.getConfig().setString("branch", "master", "remote", "origin"); + db.getConfig() + .setString("branch", "master", "merge", "refs/heads/main"); + db.getConfig().setString("remote", "origin", "url", + "git://example.com/here"); + db.getConfig().setString("remote", "origin", "fetch", + "+refs/heads/*:refs/remotes/origin/*"); + git.add().addFilepattern("file2.txt").call(); + git.commit().setMessage("create file").call(); + assertEquals("refs/remotes/origin/main", db.simplify("@{upstream}")); + } } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017, David Pursehouse + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.junit.Test; + +public class SubmoduleConfigTest { + @Test + public void fetchRecurseMatch() throws Exception { + assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("yes")); + assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("YES")); + assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("true")); + assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("TRUE")); + + assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("on-demand")); + assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ON-DEMAND")); + assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("on_demand")); + assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ON_DEMAND")); + + assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("no")); + assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("NO")); + assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("false")); + assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("FALSE")); + } + + @Test + public void fetchRecurseNoMatch() throws Exception { + assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue("Y")); + assertFalse(FetchRecurseSubmodulesMode.NO.matchConfigValue("N")); + assertFalse(FetchRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ONDEMAND")); + assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue("")); + assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue(null)); + } + + @Test + public void fetchRecurseToConfigValue() { + assertEquals("on-demand", + FetchRecurseSubmodulesMode.ON_DEMAND.toConfigValue()); + assertEquals("true", FetchRecurseSubmodulesMode.YES.toConfigValue()); + assertEquals("false", FetchRecurseSubmodulesMode.NO.toConfigValue()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,11 +75,13 @@ p.toExternalString()); } + @SuppressWarnings("unused") @Test(expected = IllegalArgumentException.class) public void nullForNameShouldThrowIllegalArgumentException() { new PersonIdent(null, "author@example.com"); } + @SuppressWarnings("unused") @Test(expected = IllegalArgumentException.class) public void nullForEmailShouldThrowIllegalArgumentException() { new PersonIdent("A U Thor", null); @@ -87,12 +89,54 @@ @Test public void testToExternalStringTrimsNameAndEmail() throws Exception { - PersonIdent personIdent = new PersonIdent(" A U Thor ", - " author@example.com "); + PersonIdent personIdent = new PersonIdent(" \u0010A U Thor ", + " author@example.com \u0009"); - String externalString = personIdent.toExternalString(); + assertEquals(" \u0010A U Thor ", personIdent.getName()); + assertEquals(" author@example.com \u0009", personIdent.getEmailAddress()); + String externalString = personIdent.toExternalString(); assertTrue(externalString.startsWith("A U Thor ")); } + @Test + public void testToExternalStringTrimsAllWhitespace() { + String ws = " \u0001 \n "; + PersonIdent personIdent = new PersonIdent(ws, ws); + assertEquals(ws, personIdent.getName()); + assertEquals(ws, personIdent.getEmailAddress()); + + String externalString = personIdent.toExternalString(); + assertTrue(externalString.startsWith(" <>")); + } + + @Test + public void testToExternalStringTrimsOtherBadCharacters() { + String name = " Foo\r\n "; + String email = " Baz>\n\u1234")); + } + + @Test + public void testEmptyNameAndEmail() { + PersonIdent personIdent = new PersonIdent("", ""); + assertEquals("", personIdent.getName()); + assertEquals("", personIdent.getEmailAddress()); + + String externalString = personIdent.toExternalString(); + assertTrue(externalString.startsWith(" <>")); + } + + @Test + public void testAppendSanitized() { + StringBuilder r = new StringBuilder(); + PersonIdent.appendSanitized(r, " Baz>\n\u1234 - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; -import org.junit.Test; - -@SuppressWarnings("deprecation") -public class T0002_TreeTest extends SampleDataRepositoryTestCase { - private static final ObjectId SOME_FAKE_ID = ObjectId.fromString( - "0123456789abcdef0123456789abcdef01234567"); - - private static int compareNamesUsingSpecialCompare(String a, String b) - throws UnsupportedEncodingException { - char lasta = '\0'; - byte[] abytes; - if (a.length() > 0 && a.charAt(a.length()-1) == '/') { - lasta = '/'; - a = a.substring(0, a.length() - 1); - } - abytes = a.getBytes("ISO-8859-1"); - char lastb = '\0'; - byte[] bbytes; - if (b.length() > 0 && b.charAt(b.length()-1) == '/') { - lastb = '/'; - b = b.substring(0, b.length() - 1); - } - bbytes = b.getBytes("ISO-8859-1"); - return Tree.compareNames(abytes, bbytes, lasta, lastb); - } - - @Test - public void test000_sort_01() throws UnsupportedEncodingException { - assertEquals(0, compareNamesUsingSpecialCompare("a","a")); - } - - @Test - public void test000_sort_02() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a","b")); - assertEquals(1, compareNamesUsingSpecialCompare("b","a")); - } - - @Test - public void test000_sort_03() throws UnsupportedEncodingException { - assertEquals(1, compareNamesUsingSpecialCompare("a:","a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a:")); - assertEquals(1, compareNamesUsingSpecialCompare("a:","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:")); - } - - @Test - public void test000_sort_04() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a")); - } - - @Test - public void test000_sort_05() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a.")); - - } - - @Test - public void test001_createEmpty() throws IOException { - final Tree t = new Tree(db); - assertTrue("isLoaded", t.isLoaded()); - assertTrue("isModified", t.isModified()); - assertTrue("no parent", t.getParent() == null); - assertTrue("isRoot", t.isRoot()); - assertTrue("no name", t.getName() == null); - assertTrue("no nameUTF8", t.getNameUTF8() == null); - assertTrue("has entries array", t.members() != null); - assertEquals("entries is empty", 0, t.members().length); - assertEquals("full name is empty", "", t.getFullName()); - assertTrue("no id", t.getId() == null); - assertTrue("database is r", t.getRepository() == db); - assertTrue("no foo child", t.findTreeMember("foo") == null); - assertTrue("no foo child", t.findBlobMember("foo") == null); - } - - @Test - public void test002_addFile() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("have file", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("is modified", t.isModified()); - assertTrue("has no id", t.getId() == null); - assertTrue("found bob", t.findBlobMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertNotNull("members array not null", i); - assertTrue("iterator is not empty", i != null && i.length > 0); - assertTrue("iterator returns file", i != null && i[0] == f); - assertTrue("iterator is empty", i != null && i.length == 1); - } - - @Test - public void test004_addTree() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final Tree f = t.addTree(n); - assertNotNull("have tree", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("parent matches", f.getParent() == t); - assertTrue("repository matches", f.getRepository() == db); - assertTrue("isLoaded", f.isLoaded()); - assertFalse("has items", f.members().length > 0); - assertFalse("is root", f.isRoot()); - assertTrue("parent is modified", t.isModified()); - assertTrue("parent has no id", t.getId() == null); - assertTrue("found bob child", t.findTreeMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertTrue("iterator is not empty", i.length > 0); - assertTrue("iterator returns file", i[0] == f); - assertEquals("iterator is empty", 1, i.length); - } - - @Test - public void test005_addRecursiveFile() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry f = t.addFile("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test005_addRecursiveTree() throws IOException { - final Tree t = new Tree(db); - final Tree f = t.addTree("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test006_addDeepTree() throws IOException { - final Tree t = new Tree(db); - - final Tree e = t.addTree("e"); - assertNotNull("have e", e); - assertTrue("e.parent == t", e.getParent() == t); - final Tree f = t.addTree("f"); - assertNotNull("have f", f); - assertTrue("f.parent == t", f.getParent() == t); - final Tree g = f.addTree("g"); - assertNotNull("have g", g); - assertTrue("g.parent == f", g.getParent() == f); - final Tree h = g.addTree("h"); - assertNotNull("have h", h); - assertTrue("h.parent = g", h.getParent() == g); - - h.setId(SOME_FAKE_ID); - assertTrue("h not modified", !h.isModified()); - g.setId(SOME_FAKE_ID); - assertTrue("g not modified", !g.isModified()); - f.setId(SOME_FAKE_ID); - assertTrue("f not modified", !f.isModified()); - e.setId(SOME_FAKE_ID); - assertTrue("e not modified", !e.isModified()); - t.setId(SOME_FAKE_ID); - assertTrue("t not modified.", !t.isModified()); - - assertEquals("full path of h ok", "f/g/h", h.getFullName()); - assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h); - assertTrue("Can't find f/z", t.findBlobMember("f/z") == null); - assertTrue("Can't find y/z", t.findBlobMember("y/z") == null); - - final FileTreeEntry i = h.addFile("i"); - assertNotNull(i); - assertEquals("full path of i ok", "f/g/h/i", i.getFullName()); - assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i); - assertTrue("h modified", h.isModified()); - assertTrue("g modified", g.isModified()); - assertTrue("f modified", f.isModified()); - assertTrue("e not modified", !e.isModified()); - assertTrue("t modified", t.isModified()); - - assertTrue("h no id", h.getId() == null); - assertTrue("g no id", g.getId() == null); - assertTrue("f no id", f.getId() == null); - assertTrue("e has id", e.getId() != null); - assertTrue("t no id", t.getId() == null); - } - - @Test - public void test007_manyFileLookup() throws IOException { - final Tree t = new Tree(db); - final List files = new ArrayList(26 * 26); - for (char level1 = 'a'; level1 <= 'z'; level1++) { - for (char level2 = 'a'; level2 <= 'z'; level2++) { - final String n = "." + level1 + level2 + "9"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("File " + n + " added.", f); - assertEquals(n, f.getName()); - files.add(f); - } - } - assertEquals(files.size(), t.memberCount()); - final TreeEntry[] ents = t.members(); - assertNotNull(ents); - assertEquals(files.size(), ents.length); - for (int k = 0; k < ents.length; k++) { - assertTrue("File " + files.get(k).getName() - + " is at " + k + ".", files.get(k) == ents[k]); - } - } - - @Test - public void test008_SubtreeInternalSorting() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-b"); - final FileTreeEntry e1 = t.addFile("a-"); - final FileTreeEntry e2 = t.addFile("a=b"); - final Tree e3 = t.addTree("a"); - final FileTreeEntry e4 = t.addFile("a="); - - final TreeEntry[] ents = t.members(); - assertSame(e1, ents[0]); - assertSame(e0, ents[1]); - assertSame(e3, ents[2]); - assertSame(e4, ents[3]); - assertSame(e2, ents[4]); - } - - @Test - public void test009_SymlinkAndGitlink() throws IOException { - final Tree symlinkTree = mapTree("symlink"); - assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt")); - final Tree gitlinkTree = mapTree("gitlink"); - assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule")); - } - - private Tree mapTree(String name) throws IOException { - ObjectId id = db.resolve(name + "^{tree}"); - return new Tree(db, id, db.open(id).getCachedBytes()); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,6 +62,7 @@ final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock); runOnThread(new Runnable() { + @Override public void run() { try { pm.start(1); @@ -128,6 +129,7 @@ final CountDownLatch doEndWorker = new CountDownLatch(1); final Thread bg = new Thread() { + @Override public void run() { assertFalse(pm.isCancelled()); @@ -175,24 +177,29 @@ int value; + @Override public void update(int completed) { value += completed; } + @Override public void start(int totalTasks) { value = totalTasks; } + @Override public void beginTask(String title, int totalWork) { taskTitle = title; value = totalWork; } + @Override public void endTask() { taskTitle = null; value = 0; } + @Override public boolean isCancelled() { return false; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.lib; import static org.eclipse.jgit.junit.Assert.assertEquals; +import static org.junit.Assert.assertEquals; import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.util.SystemReader; @@ -94,6 +95,25 @@ } } + private static void assertNormalized(final String name, + final String expected) { + SystemReader instance = SystemReader.getInstance(); + try { + setUnixSystemReader(); + String normalized = Repository.normalizeBranchName(name); + assertEquals("Normalization of " + name, expected, normalized); + assertEquals("\"" + normalized + "\"", true, + Repository.isValidRefName(Constants.R_HEADS + normalized)); + setWindowsSystemReader(); + normalized = Repository.normalizeBranchName(name); + assertEquals("Normalization of " + name, expected, normalized); + assertEquals("\"" + normalized + "\"", true, + Repository.isValidRefName(Constants.R_HEADS + normalized)); + } finally { + SystemReader.setInstance(instance); + } + } + @Test public void testEmptyString() { assertValid(false, ""); @@ -247,4 +267,59 @@ assertValid(true, "refs/heads/conx"); assertValid(true, "refs/heads/xcon"); } + + @Test + public void testNormalizeBranchName() { + assertEquals(true, Repository.normalizeBranchName(null) == ""); + assertEquals(true, Repository.normalizeBranchName("").equals("")); + assertNormalized("Bug 12345::::Hello World", "Bug_12345-Hello_World"); + assertNormalized("Bug 12345 :::: Hello World", "Bug_12345_Hello_World"); + assertNormalized("Bug 12345 :::: Hello::: World", + "Bug_12345_Hello-World"); + assertNormalized(":::Bug 12345 - Hello World", "Bug_12345_Hello_World"); + assertNormalized("---Bug 12345 - Hello World", "Bug_12345_Hello_World"); + assertNormalized("Bug 12345 ---- Hello --- World", + "Bug_12345_Hello_World"); + assertNormalized("Bug 12345 - Hello World!", "Bug_12345_Hello_World!"); + assertNormalized("Bug 12345 : Hello World!", "Bug_12345_Hello_World!"); + assertNormalized("Bug 12345 _ Hello World!", "Bug_12345_Hello_World!"); + assertNormalized("Bug 12345 - Hello World!", + "Bug_12345_Hello_World!"); + assertNormalized(" Bug 12345 - Hello World! ", + "Bug_12345_Hello_World!"); + assertNormalized(" Bug 12345 - Hello World! ", + "Bug_12345_Hello_World!"); + assertNormalized("Bug 12345 - Hello______ World!", + "Bug_12345_Hello_World!"); + assertNormalized("_Bug 12345 - Hello World!", "Bug_12345_Hello_World!"); + } + + @Test + public void testNormalizeWithSlashes() { + assertNormalized("foo/bar/baz", "foo/bar/baz"); + assertNormalized("foo/bar.lock/baz.lock", "foo/bar_lock/baz_lock"); + assertNormalized("foo/.git/.git~1/bar", "foo/git/git-1/bar"); + assertNormalized(".foo/aux/con/com3.txt/com0/prn/lpt1", + "foo/+aux/+con/+com3.txt/com0/+prn/+lpt1"); + assertNormalized("foo/../bar///.--ba...z", "foo/bar/ba.z"); + } + + @Test + public void testNormalizeWithUnicode() { + assertNormalized("f\u00f6\u00f6/.b\u00e0r/*[<>|^~/b\u00e9\\z", + "f\u00f6\u00f6/b\u00e0r/b\u00e9-z"); + assertNormalized("\u5165\u53e3 entrance;/.\u51fa\u53e3_*ex*it*/", + "\u5165\u53e3_entrance;/\u51fa\u53e3_ex-it"); + } + + @Test + public void testNormalizeAlreadyValidRefName() { + assertNormalized("refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); + assertNormalized("refs/tags/v1.0-20170223", "refs/tags/v1.0-20170223"); + } + + @Test + public void testNormalizeTrimmedUnicodeAlreadyValidRefName() { + assertNormalized(" \u00e5ngstr\u00f6m\t", "\u00e5ngstr\u00f6m"); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -132,6 +132,45 @@ } /** + * Merge two modifications with a shared delete at the end. The underlying + * diff algorithm has to provide consistent edit results to get the expected + * merge result. + * + * @throws IOException + */ + @Test + public void testTwoModificationsWithSharedDelete() throws IOException { + assertEquals(t("Cb}n}"), + merge("ab}n}n}", "ab}n}", "Cb}n}")); + } + + /** + * Merge modifications with a shared insert in the middle. The + * underlying diff algorithm has to provide consistent edit + * results to get the expected merge result. + * + * @throws IOException + */ + @Test + public void testModificationsWithMiddleInsert() throws IOException { + assertEquals(t("aBcd123123uvwxPq"), + merge("abcd123uvwxpq", "aBcd123123uvwxPq", "abcd123123uvwxpq")); + } + + /** + * Merge modifications with a shared delete in the middle. The + * underlying diff algorithm has to provide consistent edit + * results to get the expected merge result. + * + * @throws IOException + */ + @Test + public void testModificationsWithMiddleDelete() throws IOException { + assertEquals(t("Abz}z123Q"), + merge("abz}z}z123q", "Abz}z123Q", "abz}z123q")); + } + + /** * Test a conflicting region at the very start of the text. * * @throws IOException diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,9 +50,9 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Before; import org.junit.Test; @@ -84,44 +84,44 @@ @Test public void testOneBranch() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(a), master); assertEquals("Merge branch 'a'", message); } @Test public void testTwoBranches() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(a, b), master); assertEquals("Merge branches 'a' and 'b'", message); } @Test public void testThreeBranches() throws IOException { - Ref c = db.getRef("refs/heads/c"); - Ref b = db.getRef("refs/heads/b"); - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref c = db.exactRef("refs/heads/c"); + Ref b = db.exactRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(c, b, a), master); assertEquals("Merge branches 'c', 'b' and 'a'", message); } @Test public void testRemoteBranch() throws Exception { - Ref remoteA = db.getRef("refs/remotes/origin/remote-a"); - Ref master = db.getRef("refs/heads/master"); + Ref remoteA = db.exactRef("refs/remotes/origin/remote-a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(remoteA), master); assertEquals("Merge remote-tracking branch 'origin/remote-a'", message); } @Test public void testMixed() throws IOException { - Ref c = db.getRef("refs/heads/c"); - Ref remoteA = db.getRef("refs/remotes/origin/remote-a"); - Ref master = db.getRef("refs/heads/master"); + Ref c = db.exactRef("refs/heads/c"); + Ref remoteA = db.exactRef("refs/remotes/origin/remote-a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(c, remoteA), master); assertEquals("Merge branch 'c', remote-tracking branch 'origin/remote-a'", message); @@ -129,8 +129,8 @@ @Test public void testTag() throws IOException { - Ref tagA = db.getRef("refs/tags/A"); - Ref master = db.getRef("refs/heads/master"); + Ref tagA = db.exactRef("refs/tags/A"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(tagA), master); assertEquals("Merge tag 'A'", message); } @@ -141,7 +141,7 @@ .fromString("6db9c2ebf75590eef973081736730a9ea169a0c4"); Ref commit = new ObjectIdRef.Unpeeled(Storage.LOOSE, objectId.getName(), objectId); - Ref master = db.getRef("refs/heads/master"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(commit), master); assertEquals("Merge commit '6db9c2ebf75590eef973081736730a9ea169a0c4'", message); @@ -154,7 +154,7 @@ .fromString("6db9c2ebf75590eef973081736730a9ea169a0c4"); Ref remoteBranch = new ObjectIdRef.Unpeeled(Storage.LOOSE, name, objectId); - Ref master = db.getRef("refs/heads/master"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(remoteBranch), master); assertEquals("Merge branch 'test' of http://egit.eclipse.org/jgit.git", message); @@ -162,16 +162,16 @@ @Test public void testIntoOtherThanMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); String message = formatter.format(Arrays.asList(a), b); assertEquals("Merge branch 'a' into b", message); } @Test public void testIntoHeadOtherThanMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); SymbolicRef head = new SymbolicRef("HEAD", b); String message = formatter.format(Arrays.asList(a), head); assertEquals("Merge branch 'a' into b", message); @@ -179,8 +179,8 @@ @Test public void testIntoSymbolicRefHeadPointingToMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); SymbolicRef head = new SymbolicRef("HEAD", master); String message = formatter.format(Arrays.asList(a), head); assertEquals("Merge branch 'a'", message); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -110,7 +110,7 @@ @Before public void setUp() throws Exception { super.setUp(); - db_t = new TestRepository(db); + db_t = new TestRepository<>(db); } @Theory @@ -778,7 +778,7 @@ db.close(); file.delete(); db = new FileRepository(db.getDirectory()); - db_t = new TestRepository(db); + db_t = new TestRepository<>(db); break; } } @@ -817,40 +817,35 @@ void modifyWorktree(WorktreeState worktreeState, String path, String other) throws Exception { - FileOutputStream fos = null; - ObjectId bloblId; - - try { - switch (worktreeState) { - case Missing: - new File(db.getWorkTree(), path).delete(); - break; - case DifferentFromHeadAndOther: - write(new File(db.getWorkTree(), path), - Integer.toString(counter++)); - break; - case SameAsHead: - bloblId = contentId(Constants.HEAD, path); - fos = new FileOutputStream(new File(db.getWorkTree(), path)); - db.newObjectReader().open(bloblId).copyTo(fos); - break; - case SameAsOther: - bloblId = contentId(other, path); - fos = new FileOutputStream(new File(db.getWorkTree(), path)); - db.newObjectReader().open(bloblId).copyTo(fos); - break; - case Bare: - if (db.isBare()) - return; - File workTreeFile = db.getWorkTree(); - db.getConfig().setBoolean("core", null, "bare", true); - db.getDirectory().renameTo(new File(workTreeFile, "test.git")); - db = new FileRepository(new File(workTreeFile, "test.git")); - db_t = new TestRepository(db); + switch (worktreeState) { + case Missing: + new File(db.getWorkTree(), path).delete(); + break; + case DifferentFromHeadAndOther: + write(new File(db.getWorkTree(), path), + Integer.toString(counter++)); + break; + case SameAsHead: + try (FileOutputStream fos = new FileOutputStream( + new File(db.getWorkTree(), path))) { + db.newObjectReader().open(contentId(Constants.HEAD, path)) + .copyTo(fos); } - } finally { - if (fos != null) - fos.close(); + break; + case SameAsOther: + try (FileOutputStream fos = new FileOutputStream( + new File(db.getWorkTree(), path))) { + db.newObjectReader().open(contentId(other, path)).copyTo(fos); + } + break; + case Bare: + if (db.isBare()) + return; + File workTreeFile = db.getWorkTree(); + db.getConfig().setBoolean("core", null, "bare", true); + db.getDirectory().renameTo(new File(workTreeFile, "test.git")); + db = new FileRepository(new File(workTreeFile, "test.git")); + db_t = new TestRepository<>(db); } } @@ -872,32 +867,31 @@ private String contentAsString(Repository r, ObjectId treeId, String path) throws MissingObjectException, IOException { - TreeWalk tw = new TreeWalk(r); - tw.addTree(treeId); - tw.setFilter(PathFilter.create(path)); - tw.setRecursive(true); - if (!tw.next()) - return null; - AnyObjectId blobId = tw.getObjectId(0); + AnyObjectId blobId; + try (TreeWalk tw = new TreeWalk(r)) { + tw.addTree(treeId); + tw.setFilter(PathFilter.create(path)); + tw.setRecursive(true); + if (!tw.next()) { + return null; + } + blobId = tw.getObjectId(0); + } StringBuilder result = new StringBuilder(); - BufferedReader br = null; ObjectReader or = r.newObjectReader(); - try { - br = new BufferedReader(new InputStreamReader(or.open(blobId) - .openStream())); + try (BufferedReader br = new BufferedReader( + new InputStreamReader(or.open(blobId).openStream()))) { String line; boolean first = true; while ((line = br.readLine()) != null) { - if (!first) + if (!first) { result.append('\n'); + } result.append(line); first = false; } return result.toString(); - } finally { - if (br != null) - br.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,12 +42,20 @@ */ package org.eclipse.jgit.merge; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; 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.assertTrue; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Arrays; +import java.util.Map; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult; @@ -56,12 +64,31 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; +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.ObjectStream; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.junit.Assert; import org.junit.experimental.theories.DataPoint; @@ -88,35 +115,36 @@ file = new File(folder1, "file2.txt"); write(file, "folder1--file2.txt"); - Git git = new Git(db); - git.add().addFilepattern(folder1.getName()).call(); - RevCommit base = git.commit().setMessage("adding folder").call(); - - recursiveDelete(folder1); - git.rm().addFilepattern("folder1/file1.txt") - .addFilepattern("folder1/file2.txt").call(); - RevCommit other = git.commit() - .setMessage("removing folders on 'other'").call(); - - git.checkout().setName(base.name()).call(); - - file = new File(db.getWorkTree(), "unrelated.txt"); - write(file, "unrelated"); - - git.add().addFilepattern("unrelated.txt").call(); - RevCommit head = git.commit().setMessage("Adding another file").call(); - - // Untracked file to cause failing path for delete() of folder1 - // but that's ok. - file = new File(folder1, "file3.txt"); - write(file, "folder1--file3.txt"); - - ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false); - merger.setCommitNames(new String[] { "BASE", "HEAD", "other" }); - merger.setWorkingTreeIterator(new FileTreeIterator(db)); - boolean ok = merger.merge(head.getId(), other.getId()); - assertTrue(ok); - assertTrue(file.exists()); + try (Git git = new Git(db)) { + git.add().addFilepattern(folder1.getName()).call(); + RevCommit base = git.commit().setMessage("adding folder").call(); + + recursiveDelete(folder1); + git.rm().addFilepattern("folder1/file1.txt") + .addFilepattern("folder1/file2.txt").call(); + RevCommit other = git.commit() + .setMessage("removing folders on 'other'").call(); + + git.checkout().setName(base.name()).call(); + + file = new File(db.getWorkTree(), "unrelated.txt"); + write(file, "unrelated"); + + git.add().addFilepattern("unrelated.txt").call(); + RevCommit head = git.commit().setMessage("Adding another file").call(); + + // Untracked file to cause failing path for delete() of folder1 + // but that's ok. + file = new File(folder1, "file3.txt"); + write(file, "folder1--file3.txt"); + + ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false); + merger.setCommitNames(new String[] { "BASE", "HEAD", "other" }); + merger.setWorkingTreeIterator(new FileTreeIterator(db)); + boolean ok = merger.merge(head.getId(), other.getId()); + assertTrue(ok); + assertTrue(file.exists()); + } } /** @@ -370,6 +398,38 @@ mergeResult.getMergeStatus()); } + @Theory + public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy) + throws IOException, GitAPIException { + Git git = Git.wrap(db); + db.getConfig().setString("core", null, "autocrlf", "true"); + db.getConfig().save(); + writeTrashFile("crlf.txt", "a crlf file\r\n"); + git.add().addFilepattern("crlf.txt").call(); + git.commit().setMessage("base").call(); + + git.branchCreate().setName("brancha").call(); + + writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n"); + git.add().addFilepattern("crlf.txt").call(); + git.commit().setMessage("on master").call(); + + git.checkout().setName("brancha").call(); + File testFile = writeTrashFile("crlf.txt", + "a first line\r\na crlf file\r\n"); + git.add().addFilepattern("crlf.txt").call(); + git.commit().setMessage("on brancha").call(); + + MergeResult mergeResult = git.merge().setStrategy(strategy) + .include(db.resolve("master")).call(); + assertEquals(MergeResult.MergeStatus.MERGED, + mergeResult.getMergeStatus()); + checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n"); + assertEquals( + "[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]", + indexState(CONTENT)); + } + /** * Merging two equal subtrees when the index does not contain any file in * that subtree should lead to a merged state. @@ -406,7 +466,7 @@ /** * Merging two equal subtrees with an incore merger should lead to a merged - * state (The 'Gerrit' use case). + * state. * * @param strategy * @throws Exception @@ -440,6 +500,43 @@ } /** + * Merging two equal subtrees with an incore merger should lead to a merged + * state, without using a Repository (the 'Gerrit' use case). + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("d/1", "orig"); + git.add().addFilepattern("d/1").call(); + RevCommit first = git.commit().setMessage("added d/1").call(); + + writeTrashFile("d/1", "modified"); + RevCommit masterCommit = git.commit().setAll(true) + .setMessage("modified d/1 on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("d/1", "modified"); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified d/1 on side").call(); + + git.rm().addFilepattern("d/1").call(); + git.rm().addFilepattern("d").call(); + + try (ObjectInserter ins = db.newObjectInserter()) { + ThreeWayMerger resolveMerger = + (ThreeWayMerger) strategy.newMerger(ins, db.getConfig()); + boolean noProblems = resolveMerger.merge(masterCommit, sideCommit); + assertTrue(noProblems); + } + } + + /** * Merging two equal subtrees when the index and HEAD does not contain any * file in that subtree should lead to a merged state. * @@ -584,6 +681,273 @@ } } + @Theory + public void checkContentMergeNoConflict(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("file", "1\n2\n3"); + git.add().addFilepattern("file").call(); + RevCommit first = git.commit().setMessage("added file").call(); + + writeTrashFile("file", "1master\n2\n3"); + git.commit().setAll(true).setMessage("modified file on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("file", "1\n2\n3side"); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified file on side").call(); + + git.checkout().setName("master").call(); + MergeResult result = + git.merge().setStrategy(strategy).include(sideCommit).call(); + assertEquals(MergeStatus.MERGED, result.getMergeStatus()); + String expected = "1master\n2\n3side"; + assertEquals(expected, read("file")); + } + + @Theory + public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("file", "1\n2\n3"); + git.add().addFilepattern("file").call(); + RevCommit first = git.commit().setMessage("added file").call(); + + writeTrashFile("file", "1master\n2\n3"); + RevCommit masterCommit = git.commit().setAll(true) + .setMessage("modified file on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("file", "1\n2\n3side"); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified file on side").call(); + + try (ObjectInserter ins = db.newObjectInserter()) { + ResolveMerger merger = + (ResolveMerger) strategy.newMerger(ins, db.getConfig()); + boolean noProblems = merger.merge(masterCommit, sideCommit); + assertTrue(noProblems); + assertEquals("1master\n2\n3side", + readBlob(merger.getResultTreeId(), "file")); + } + } + + + /** + * Merging a change involving large binary files should short-circuit reads. + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception { + Git git = Git.wrap(db); + final int LINELEN = 72; + + // setup a merge that would work correctly if we disconsider the stray '\0' + // that the file contains near the start. + byte[] binary = new byte[LINELEN * 2000]; + for (int i = 0; i < binary.length; i++) { + binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x'); + } + binary[50] = '\0'; + + writeTrashFile("file", new String(binary, UTF_8)); + git.add().addFilepattern("file").call(); + RevCommit first = git.commit().setMessage("added file").call(); + + // Generate an edit in a single line. + int idx = LINELEN * 1200 + 1; + byte save = binary[idx]; + binary[idx] = '@'; + writeTrashFile("file", new String(binary, UTF_8)); + + binary[idx] = save; + git.add().addFilepattern("file").call(); + RevCommit masterCommit = git.commit().setAll(true) + .setMessage("modified file l 1200").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call(); + binary[LINELEN * 1500 + 1] = '!'; + writeTrashFile("file", new String(binary, UTF_8)); + git.add().addFilepattern("file").call(); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified file l 1500").call(); + + try (ObjectInserter ins = db.newObjectInserter()) { + // Check that we don't read the large blobs. + ObjectInserter forbidInserter = new ObjectInserter.Filter() { + @Override + protected ObjectInserter delegate() { + return ins; + } + + @Override + public ObjectReader newReader() { + return new BigReadForbiddenReader(super.newReader(), 8000); + } + }; + + ResolveMerger merger = + (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig()); + boolean noProblems = merger.merge(masterCommit, sideCommit); + assertFalse(noProblems); + } + } + + /** + * Throws an exception if reading beyond limit. + */ + class BigReadForbiddenStream extends ObjectStream.Filter { + int limit; + + BigReadForbiddenStream(ObjectStream orig, int limit) { + super(orig.getType(), orig.getSize(), orig); + this.limit = limit; + } + + @Override + public long skip(long n) throws IOException { + limit -= n; + if (limit < 0) { + throw new IllegalStateException(); + } + + return super.skip(n); + } + + @Override + public int read() throws IOException { + int r = super.read(); + limit--; + if (limit < 0) { + throw new IllegalStateException(); + } + return r; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int n = super.read(b, off, len); + limit -= n; + if (limit < 0) { + throw new IllegalStateException(); + } + return n; + } + } + + class BigReadForbiddenReader extends ObjectReader.Filter { + ObjectReader delegate; + int limit; + + @Override + protected ObjectReader delegate() { + return delegate; + } + + BigReadForbiddenReader(ObjectReader delegate, int limit) { + this.delegate = delegate; + this.limit = limit; + } + + @Override + public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException { + ObjectLoader orig = super.open(objectId, typeHint); + return new ObjectLoader.Filter() { + @Override + protected ObjectLoader delegate() { + return orig; + } + + @Override + public ObjectStream openStream() throws IOException { + ObjectStream os = orig.openStream(); + return new BigReadForbiddenStream(os, limit); + } + }; + } + } + + @Theory + public void checkContentMergeConflict(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("file", "1\n2\n3"); + git.add().addFilepattern("file").call(); + RevCommit first = git.commit().setMessage("added file").call(); + + writeTrashFile("file", "1master\n2\n3"); + git.commit().setAll(true).setMessage("modified file on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("file", "1side\n2\n3"); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified file on side").call(); + + git.checkout().setName("master").call(); + MergeResult result = + git.merge().setStrategy(strategy).include(sideCommit).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + String expected = "<<<<<<< HEAD\n" + + "1master\n" + + "=======\n" + + "1side\n" + + ">>>>>>> " + sideCommit.name() + "\n" + + "2\n" + + "3"; + assertEquals(expected, read("file")); + } + + @Theory + public void checkContentMergeConflict_noTree(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("file", "1\n2\n3"); + git.add().addFilepattern("file").call(); + RevCommit first = git.commit().setMessage("added file").call(); + + writeTrashFile("file", "1master\n2\n3"); + RevCommit masterCommit = git.commit().setAll(true) + .setMessage("modified file on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("file", "1side\n2\n3"); + RevCommit sideCommit = git.commit().setAll(true) + .setMessage("modified file on side").call(); + + try (ObjectInserter ins = db.newObjectInserter()) { + ResolveMerger merger = + (ResolveMerger) strategy.newMerger(ins, db.getConfig()); + boolean noProblems = merger.merge(masterCommit, sideCommit); + assertFalse(noProblems); + assertEquals(Arrays.asList("file"), merger.getUnmergedPaths()); + + MergeFormatter fmt = new MergeFormatter(); + merger.getMergeResults().get("file"); + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + fmt.formatMerge(out, merger.getMergeResults().get("file"), + "BASE", "OURS", "THEIRS", UTF_8.name()); + String expected = "<<<<<<< OURS\n" + + "1master\n" + + "=======\n" + + "1side\n" + + ">>>>>>> THEIRS\n" + + "2\n" + + "3"; + assertEquals(expected, new String(out.toByteArray(), UTF_8)); + } + } + } + /** * Merging after criss-cross merges. In this case we merge together two * commits which have two equally good common ancestors @@ -630,7 +994,7 @@ // ResolveMerge try { MergeResult mergeResult = git.merge().setStrategy(strategy) - .include(git.getRepository().getRef("refs/heads/side")) + .include(git.getRepository().exactRef("refs/heads/side")) .call(); assertEquals(MergeStrategy.RECURSIVE, strategy); assertEquals(MergeResult.MergeStatus.MERGED, @@ -693,7 +1057,7 @@ // Create initial content and remember when the last file was written. f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); - lastTs4 = f.lastModified(); + lastTs4 = FS.DETECTED.lastModified(f); // add all files, commit and check this doesn't update any working tree // files and that the index is in a new file system timer tick. Make @@ -706,8 +1070,8 @@ checkConsistentLastModified("0", "1", "2", "3", "4"); checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); assertEquals("Commit should not touch working tree file 4", lastTs4, - new File(db.getWorkTree(), "4").lastModified()); - lastTsIndex = indexFile.lastModified(); + FS.DETECTED.lastModified(new File(db.getWorkTree(), "4"))); + lastTsIndex = FS.DETECTED.lastModified(indexFile); // Do modifications on the master branch. Then add and commit. This // should touch only "0", "2 and "3" @@ -721,7 +1085,7 @@ checkConsistentLastModified("0", "1", "2", "3", "4"); checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" + lastTsIndex, "<0", "2", "3", "<.git/index"); - lastTsIndex = indexFile.lastModified(); + lastTsIndex = FS.DETECTED.lastModified(indexFile); // Checkout a side branch. This should touch only "0", "2 and "3" fsTick(indexFile); @@ -730,7 +1094,7 @@ checkConsistentLastModified("0", "1", "2", "3", "4"); checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" + lastTsIndex, "<0", "2", "3", ".git/index"); - lastTsIndex = indexFile.lastModified(); + lastTsIndex = FS.DETECTED.lastModified(indexFile); // This checkout may have populated worktree and index so fast that we // may have smudged entries now. Check that we have the right content @@ -743,13 +1107,13 @@ indexState(CONTENT)); fsTick(indexFile); f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); - lastTs4 = f.lastModified(); + lastTs4 = FS.DETECTED.lastModified(f); fsTick(f); git.add().addFilepattern(".").call(); checkConsistentLastModified("0", "1", "2", "3", "4"); checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", "4", "<.git/index"); - lastTsIndex = indexFile.lastModified(); + lastTsIndex = FS.DETECTED.lastModified(indexFile); // Do modifications on the side branch. Touch only "1", "2 and "3" fsTick(indexFile); @@ -760,7 +1124,7 @@ checkConsistentLastModified("0", "1", "2", "3", "4"); checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" + lastTsIndex, "<1", "2", "3", "<.git/index"); - lastTsIndex = indexFile.lastModified(); + lastTsIndex = FS.DETECTED.lastModified(indexFile); // merge master and side. Should only touch "0," "2" and "3" fsTick(indexFile); @@ -777,6 +1141,146 @@ indexState(CONTENT)); } + /** + * Merging two conflicting submodules when the index does not contain any + * entry for that submodule. + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkMergeConflictingSubmodulesWithoutIndex( + MergeStrategy strategy) throws Exception { + Git git = Git.wrap(db); + writeTrashFile("initial", "initial"); + git.add().addFilepattern("initial").call(); + RevCommit initial = git.commit().setMessage("initial").call(); + + writeSubmodule("one", ObjectId + .fromString("1000000000000000000000000000000000000000")); + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + RevCommit right = git.commit().setMessage("added one").call(); + + // a second commit in the submodule + + git.checkout().setStartPoint(initial).setName("left") + .setCreateBranch(true).call(); + writeSubmodule("one", ObjectId + .fromString("2000000000000000000000000000000000000000")); + + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + git.commit().setMessage("a different one").call(); + + MergeResult result = git.merge().setStrategy(strategy).include(right) + .call(); + + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + Map conflicts = result.getConflicts(); + assertEquals(1, conflicts.size()); + assertNotNull(conflicts.get("one")); + } + + /** + * Merging two non-conflicting submodules when the index does not contain + * any entry for either submodule. + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkMergeNonConflictingSubmodulesWithoutIndex( + MergeStrategy strategy) throws Exception { + Git git = Git.wrap(db); + writeTrashFile("initial", "initial"); + git.add().addFilepattern("initial").call(); + + writeSubmodule("one", ObjectId + .fromString("1000000000000000000000000000000000000000")); + + // Our initial commit should include a .gitmodules with a bunch of + // comment lines, so that + // we don't have a content merge issue when we add a new submodule at + // the top and a different + // one at the bottom. This is sort of a hack, but it should allow + // add/add submodule merges + String existing = read(Constants.DOT_GIT_MODULES); + String context = "\n# context\n# more context\n# yet more context\n"; + write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES), + existing + context + context + context); + + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + RevCommit initial = git.commit().setMessage("initial").call(); + + writeSubmodule("two", ObjectId + .fromString("1000000000000000000000000000000000000000")); + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + + RevCommit right = git.commit().setMessage("added two").call(); + + git.checkout().setStartPoint(initial).setName("left") + .setCreateBranch(true).call(); + + // we need to manually create the submodule for three for the + // .gitmodules hackery + addSubmoduleToIndex("three", ObjectId + .fromString("1000000000000000000000000000000000000000")); + new File(db.getWorkTree(), "three").mkdir(); + + existing = read(Constants.DOT_GIT_MODULES); + String three = "[submodule \"three\"]\n\tpath = three\n\turl = " + + db.getDirectory().toURI() + "\n"; + write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES), + three + existing); + + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + git.commit().setMessage("a different one").call(); + + MergeResult result = git.merge().setStrategy(strategy).include(right) + .call(); + + assertNull(result.getCheckoutConflicts()); + assertNull(result.getFailingPaths()); + for (String dir : Arrays.asList("one", "two", "three")) { + assertTrue(new File(db.getWorkTree(), dir).isDirectory()); + } + } + + private void writeSubmodule(String path, ObjectId commit) + throws IOException, ConfigInvalidException { + addSubmoduleToIndex(path, commit); + new File(db.getWorkTree(), path).mkdir(); + + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, + db.getDirectory().toURI().toString()); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig( + new File(db.getWorkTree(), Constants.DOT_GIT_MODULES), + db.getFS()); + modulesConfig.load(); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.save(); + + } + + private void addSubmoduleToIndex(String path, ObjectId commit) + throws IOException { + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new DirCacheEditor.PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(commit); + } + }); + editor.commit(); + } + // Assert that every specified index entry has the same last modification // timestamp as the associated file private void checkConsistentLastModified(String... pathes) @@ -788,7 +1292,7 @@ "IndexEntry with path " + path + " has lastmodified with is different from the worktree file", - new File(workTree, path).lastModified(), dc.getEntry(path) + FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path) .getLastModified()); } @@ -798,14 +1302,15 @@ // then this file must be younger then file i. A path "*" // represents a file with a modification time of // E.g. ("a", "b", " lastMod); @@ -814,4 +1319,15 @@ curMod >= lastMod); } } + + private String readBlob(ObjectId treeish, String path) throws Exception { + TestRepository tr = new TestRepository<>(db); + RevWalk rw = tr.getRevWalk(); + RevTree tree = rw.parseTree(treeish); + RevObject obj = tr.get(tree, path); + if (obj == null) { + return null; + } + return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -72,6 +72,16 @@ } @Test + public void testOurs_noRepo() throws IOException { + try (ObjectInserter ins = db.newObjectInserter()) { + Merger ourMerger = MergeStrategy.OURS.newMerger(ins, db.getConfig()); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); + assertTrue(merge); + assertEquals(db.resolve("a^{tree}"), ourMerger.getResultTreeId()); + } + } + + @Test public void testTheirs() throws IOException { Merger ourMerger = MergeStrategy.THEIRS.newMerger(db); boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); @@ -80,6 +90,16 @@ } @Test + public void testTheirs_noRepo() throws IOException { + try (ObjectInserter ins = db.newObjectInserter()) { + Merger ourMerger = MergeStrategy.THEIRS.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); + assertTrue(merge); + assertEquals(db.resolve("c^{tree}"), ourMerger.getResultTreeId()); + } + } + + @Test public void testTrivialTwoWay() throws IOException { Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); @@ -104,6 +124,16 @@ } @Test + public void testTrivialTwoWay_noRepo() throws IOException { + try (ObjectInserter ins = db.newObjectInserter()) { + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(ins, db.getConfig()); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a^0^0^0"), db.resolve("a^0^0^1") }); + assertTrue(merge); + assertEquals(db.resolve("a^0^0^{tree}"), ourMerger.getResultTreeId()); + } + } + + @Test public void testTrivialTwoWay_conflict() throws IOException { Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("f"), db.resolve("g") }); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -73,17 +73,18 @@ @Test public void testCommit() throws Exception { - Git git = new Git(db); - revCommit = git.commit().setMessage("squash_me").call(); + try (Git git = new Git(db)) { + revCommit = git.commit().setMessage("squash_me").call(); - Ref master = db.getRef("refs/heads/master"); - String message = msgFormatter.format(Arrays.asList(revCommit), master); - assertEquals( - "Squashed commit of the following:\n\ncommit " - + revCommit.getName() + "\nAuthor: " - + revCommit.getAuthorIdent().getName() + " <" - + revCommit.getAuthorIdent().getEmailAddress() - + ">\nDate: " + dateFormatter.formatDate(author) - + "\n\n\tsquash_me\n", message); + Ref master = db.exactRef("refs/heads/master"); + String message = msgFormatter.format(Arrays.asList(revCommit), master); + assertEquals( + "Squashed commit of the following:\n\ncommit " + + revCommit.getName() + "\nAuthor: " + + revCommit.getAuthorIdent().getName() + " <" + + revCommit.getAuthorIdent().getEmailAddress() + + ">\nDate: " + dateFormatter.formatDate(author) + + "\n\n\tsquash_me\n", message); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -123,6 +123,7 @@ this.locale = locale; } + @Override public TranslationBundle call() throws Exception { NLS.setLocale(locale); barrier.await(); // wait for the other thread to set its locale diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,7 +75,7 @@ @Before public void setUp() throws Exception { super.setUp(); - tr = new TestRepository(db); + tr = new TestRepository<>(db); reader = db.newObjectReader(); inserter = db.newObjectInserter(); merger = new DefaultNoteMerger(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -97,7 +97,7 @@ @Before public void setUp() throws Exception { super.setUp(); - tr = new TestRepository(db); + tr = new TestRepository<>(db); reader = db.newObjectReader(); inserter = db.newObjectInserter(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,7 +83,7 @@ public void setUp() throws Exception { super.setUp(); - tr = new TestRepository(db); + tr = new TestRepository<>(db); reader = db.newObjectReader(); inserter = db.newObjectInserter(); } @@ -403,10 +403,12 @@ } RevCommit n = commitNoteMap(map); - TreeWalk tw = new TreeWalk(reader); - tw.reset(n.getTree()); - while (tw.next()) - assertFalse("no fan-out subtree", tw.isSubtree()); + try (TreeWalk tw = new TreeWalk(reader)) { + tw.reset(n.getTree()); + while (tw.next()) { + assertFalse("no fan-out subtree", tw.isSubtree()); + } + } for (int i = 254; i < 256; i++) { idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i); @@ -418,13 +420,15 @@ // The 00 bucket is fully split. String path = fanout(38, idBuf.name()); - tw = TreeWalk.forPath(reader, path, n.getTree()); - assertNotNull("has " + path, tw); + try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) { + assertNotNull("has " + path, tw); + } // The other bucket is not. path = fanout(2, data1.name()); - tw = TreeWalk.forPath(reader, path, n.getTree()); - assertNotNull("has " + path, tw); + try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) { + assertNotNull("has " + path, tw); + } } @Test @@ -445,11 +449,13 @@ assertEquals("empty tree", empty, n.getTree()); } + @Test public void testIteratorEmptyMap() { Iterator it = NoteMap.newEmptyMap().iterator(); assertFalse(it.hasNext()); } + @Test public void testIteratorFlatTree() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); @@ -468,6 +474,7 @@ assertEquals(2, count(it)); } + @Test public void testIteratorFanoutTree2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); @@ -486,6 +493,7 @@ assertEquals(2, count(it)); } + @Test public void testIteratorFanoutTree2_2_36() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); @@ -504,6 +512,7 @@ assertEquals(2, count(it)); } + @Test public void testIteratorFullyFannedOut() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); @@ -522,12 +531,13 @@ assertEquals(2, count(it)); } + @Test public void testShorteningNoteRefName() throws Exception { String expectedShortName = "review"; String noteRefName = Constants.R_NOTES + expectedShortName; assertEquals(expectedShortName, NoteMap.shortenRefName(noteRefName)); String nonNoteRefName = Constants.R_HEADS + expectedShortName; - assertEquals(nonNoteRefName, NoteMap.shortenRefName(expectedShortName)); + assertEquals(nonNoteRefName, NoteMap.shortenRefName(nonNoteRefName)); } private RevCommit commitNoteMap(NoteMap map) throws IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -90,17 +90,14 @@ } private Patch parseTestPatchFile(final String patchFile) throws IOException { - final InputStream in = getClass().getResourceAsStream(patchFile); - if (in == null) { - fail("No " + patchFile + " test vector"); - return null; // Never happens - } - try { + try (InputStream in = getClass().getResourceAsStream(patchFile)) { + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } final Patch p = new Patch(); p.parse(in); return p; - } finally { - in.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.patch; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,7 +60,7 @@ public class GetTextTest { @Test public void testGetText_BothISO88591() throws IOException { - final Charset cs = Charset.forName("ISO-8859-1"); + final Charset cs = ISO_8859_1; final Patch p = parseTestPatchFile(); assertTrue(p.getErrors().isEmpty()); assertEquals(1, p.getFiles().size()); @@ -69,7 +71,7 @@ @Test public void testGetText_NoBinary() throws IOException { - final Charset cs = Charset.forName("ISO-8859-1"); + final Charset cs = ISO_8859_1; final Patch p = parseTestPatchFile(); assertTrue(p.getErrors().isEmpty()); assertEquals(1, p.getFiles().size()); @@ -80,8 +82,8 @@ @Test public void testGetText_Convert() throws IOException { - final Charset csOld = Charset.forName("ISO-8859-1"); - final Charset csNew = Charset.forName("UTF-8"); + final Charset csOld = ISO_8859_1; + final Charset csNew = UTF_8; final Patch p = parseTestPatchFile(); assertTrue(p.getErrors().isEmpty()); assertEquals(1, p.getFiles().size()); @@ -100,8 +102,8 @@ @Test public void testGetText_DiffCc() throws IOException { - final Charset csOld = Charset.forName("ISO-8859-1"); - final Charset csNew = Charset.forName("UTF-8"); + final Charset csOld = ISO_8859_1; + final Charset csNew = UTF_8; final Patch p = parseTestPatchFile(); assertTrue(p.getErrors().isEmpty()); assertEquals(1, p.getFiles().size()); @@ -121,28 +123,24 @@ private Patch parseTestPatchFile() throws IOException { final String patchFile = JGitTestUtil.getName() + ".patch"; - final InputStream in = getClass().getResourceAsStream(patchFile); - if (in == null) { - fail("No " + patchFile + " test vector"); - return null; // Never happens - } - try { + try (InputStream in = getClass().getResourceAsStream(patchFile)) { + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } final Patch p = new Patch(); p.parse(in); return p; - } finally { - in.close(); } } private String readTestPatchFile(final Charset cs) throws IOException { final String patchFile = JGitTestUtil.getName() + ".patch"; - final InputStream in = getClass().getResourceAsStream(patchFile); - if (in == null) { - fail("No " + patchFile + " test vector"); - return null; // Never happens - } - try { + try (InputStream in = getClass().getResourceAsStream(patchFile)) { + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } final InputStreamReader r = new InputStreamReader(in, cs); char[] tmp = new char[2048]; final StringBuilder s = new StringBuilder(); @@ -150,8 +148,6 @@ while ((n = r.read(tmp)) > 0) s.append(tmp, 0, n); return s.toString(); - } finally { - in.close(); } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,6 @@ package org.eclipse.jgit.patch; import static java.lang.Integer.valueOf; -import static java.lang.Long.valueOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -96,7 +96,7 @@ throws IOException { TestPlotWalk walk = new TestPlotWalk(db); walk.markStart(walk.parseCommit(start)); - PlotCommitList commitList = new PlotCommitList(); + PlotCommitList commitList = new PlotCommitList<>(); commitList.source(walk); commitList.fillTo(1000); return commitList; @@ -116,7 +116,7 @@ private static class TestPlotRenderer extends AbstractPlotRenderer { - List indentations = new LinkedList(); + List indentations = new LinkedList<>(); @Override protected int drawLabel(int x, int y, Ref ref) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -123,7 +123,7 @@ } private static Set asSet(int... numbers) { - Set result = new HashSet(); + Set result = new HashSet<>(); for (int n : numbers) result.add(Integer.valueOf(n)); return result; @@ -138,7 +138,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(c.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -159,7 +159,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(d.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -181,7 +181,7 @@ pw.markStart(pw.lookupCommit(b.getId())); pw.markStart(pw.lookupCommit(c.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -205,7 +205,7 @@ pw.markStart(pw.lookupCommit(c.getId())); pw.markStart(pw.lookupCommit(d.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -240,7 +240,7 @@ // pw.markStart(pw.lookupCommit(f.getId())); pw.markStart(pw.lookupCommit(g.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -274,7 +274,7 @@ pw.markStart(pw.lookupCommit(i.getId())); pw.markStart(pw.lookupCommit(g.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); Set childPositions = asSet(0, 1); @@ -333,7 +333,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(merge_fixed_logged_npe.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -406,7 +406,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(m3)); pw.markStart(pw.lookupCommit(s2)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -471,7 +471,7 @@ pw.markStart(pw.lookupCommit(e.getId())); pw.markStart(pw.lookupCommit(a5.getId())); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -520,7 +520,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(a4)); pw.markStart(pw.lookupCommit(b3)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -565,7 +565,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(a4)); pw.markStart(pw.lookupCommit(b3)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -615,7 +615,7 @@ pw.markStart(pw.lookupCommit(a4)); pw.markStart(pw.lookupCommit(b2)); pw.markStart(pw.lookupCommit(c)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -654,7 +654,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(a3)); pw.markStart(pw.lookupCommit(b1)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(2); // don't process a1 @@ -677,7 +677,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(a)); pw.markStart(pw.lookupCommit(b)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); @@ -696,7 +696,7 @@ PlotWalk pw = new PlotWalk(db); pw.markStart(pw.lookupCommit(a)); pw.markStart(pw.lookupCommit(b2)); - PlotCommitList pcl = new PlotCommitList(); + PlotCommitList pcl = new PlotCommitList<>(); pcl.source(pw); pcl.fillTo(Integer.MAX_VALUE); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -50,10 +50,12 @@ import org.junit.Test; public class DateRevQueueTest extends RevQueueTestCase { + @Override protected DateRevQueue create() { return new DateRevQueue(); } + @Override @Test public void testEmpty() throws Exception { super.testEmpty(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,10 +52,12 @@ import org.junit.Test; public class FIFORevQueueTest extends RevQueueTestCase { + @Override protected FIFORevQueue create() { return new FIFORevQueue(); } + @Override @Test public void testEmpty() throws Exception { super.testEmpty(); @@ -70,7 +72,7 @@ @Test public void testAddLargeBlocks() throws Exception { - final ArrayList lst = new ArrayList(); + final ArrayList lst = new ArrayList<>(); for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) { final RevCommit c = commit(); lst.add(c); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -48,6 +48,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.util.List; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,10 +53,12 @@ import org.junit.Test; public class LIFORevQueueTest extends RevQueueTestCase { + @Override protected LIFORevQueue create() { return new LIFORevQueue(); } + @Override @Test public void testEmpty() throws Exception { super.testEmpty(); @@ -71,7 +73,7 @@ @Test public void testAddLargeBlocks() throws Exception { - final ArrayList lst = new ArrayList(); + final ArrayList lst = new ArrayList<>(); for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) { final RevCommit c = commit(); lst.add(c); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -46,22 +46,20 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import java.io.IOException; +import java.util.Set; import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Sets; -import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.filter.MessageRevFilter; import org.eclipse.jgit.revwalk.filter.NotRevFilter; import org.eclipse.jgit.revwalk.filter.ObjectFilter; - -import java.io.IOException; -import java.util.Set; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; public class ObjectWalkFilterTest { private TestRepository tr; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,14 +47,12 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileTreeEntry; +import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.junit.Test; -@SuppressWarnings("deprecation") public class ObjectWalkTest extends RevWalkTestCase { protected ObjectWalk objw; @@ -220,28 +218,24 @@ .fromString("abbbfafe3129f85747aba7bfac992af77134c607"); final RevTree tree_root, tree_A, tree_AB; final RevCommit b; - { - Tree root = new Tree(db); - Tree A = root.addTree("A"); - FileTreeEntry B = root.addFile("B"); - B.setId(bId); - - Tree A_A = A.addTree("A"); - Tree A_B = A.addTree("B"); - - try (final ObjectInserter inserter = db.newObjectInserter()) { - A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format())); - A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format())); - A.setId(inserter.insert(Constants.OBJ_TREE, A.format())); - root.setId(inserter.insert(Constants.OBJ_TREE, root.format())); - inserter.flush(); - } - - tree_root = rw.parseTree(root.getId()); - tree_A = rw.parseTree(A.getId()); - tree_AB = rw.parseTree(A_A.getId()); - assertSame(tree_AB, rw.parseTree(A_B.getId())); - b = commit(rw.parseTree(root.getId())); + try (ObjectInserter inserter = db.newObjectInserter()) { + ObjectId empty = inserter.insert(new TreeFormatter()); + + TreeFormatter A = new TreeFormatter(); + A.append("A", FileMode.TREE, empty); + A.append("B", FileMode.TREE, empty); + ObjectId idA = inserter.insert(A); + + TreeFormatter root = new TreeFormatter(); + root.append("A", FileMode.TREE, idA); + root.append("B", FileMode.REGULAR_FILE, bId); + ObjectId idRoot = inserter.insert(root); + inserter.flush(); + + tree_root = objw.parseTree(idRoot); + tree_A = objw.parseTree(idA); + tree_AB = objw.parseTree(empty); + b = commit(tree_root); } markStart(b); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,14 +57,15 @@ private RevCommitList list; public void setup(int count) throws Exception { - Git git = new Git(db); - for (int i = 0; i < count; i++) - git.commit().setCommitter(committer).setAuthor(author) - .setMessage("commit " + i).call(); - list = new RevCommitList(); - RevWalk w = new RevWalk(db); - w.markStart(w.lookupCommit(db.resolve(Constants.HEAD))); - list.source(w); + try (Git git = new Git(db); + RevWalk w = new RevWalk(db);) { + for (int i = 0; i < count; i++) + git.commit().setCommitter(committer).setAuthor(author) + .setMessage("commit " + i).call(); + list = new RevCommitList<>(); + w.markStart(w.lookupCommit(db.resolve(Constants.HEAD))); + list.source(w); + } } @Test @@ -107,17 +108,18 @@ public void testFillToCommit() throws Exception { setup(3); - RevWalk w = new RevWalk(db); - w.markStart(w.lookupCommit(db.resolve(Constants.HEAD))); + try (RevWalk w = new RevWalk(db)) { + w.markStart(w.lookupCommit(db.resolve(Constants.HEAD))); - w.next(); - RevCommit c = w.next(); - assertNotNull("should have found 2. commit", c); - - list.fillTo(c, 5); - assertEquals(2, list.size()); - assertEquals("commit 1", list.get(1).getFullMessage()); - assertNull(list.get(3)); + w.next(); + RevCommit c = w.next(); + assertNotNull("should have found 2. commit", c); + + list.fillTo(c, 5); + assertEquals(2, list.size()); + assertEquals("commit 1", list.get(1).getFullMessage()); + assertNull(list.get(3)); + } } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,13 +43,19 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import java.util.TimeZone; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -108,7 +114,7 @@ assertNull(c.getTree()); assertNull(c.parents); - c.parseCanonical(rw, body.toString().getBytes("UTF-8")); + c.parseCanonical(rw, body.toString().getBytes(UTF_8)); assertNotNull(c.getTree()); assertEquals(treeId, c.getTree().getId()); assertSame(rw.lookupTree(treeId), c.getTree()); @@ -142,7 +148,7 @@ final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); - c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8)); return c; } @@ -155,7 +161,7 @@ final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); - c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8)); assertEquals("", c.getFullMessage()); assertEquals("", c.getShortMessage()); @@ -170,7 +176,7 @@ final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); - c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8)); assertEquals(new PersonIdent("", "a_u_thor@example.com", 1218123387000l, 7), c.getAuthorIdent()); assertEquals(new PersonIdent("", "", 1218123390000l, -5), c.getCommitterIdent()); @@ -179,13 +185,13 @@ @Test public void testParse_implicit_UTF8_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); - b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); - b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("UTF-8")); - b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -199,13 +205,13 @@ @Test public void testParse_implicit_mixed_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); - b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); - b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("ISO-8859-1")); - b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes(ISO_8859_1)); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -254,14 +260,14 @@ @Test public void testParse_explicit_bad_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); - b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); - b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("ISO-8859-1")); - b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); - b.write("encoding EUC-JP\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Hi\n".getBytes("UTF-8")); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes(ISO_8859_1)); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding EUC-JP\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Hi\n".getBytes(UTF_8)); final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -285,14 +291,14 @@ @Test public void testParse_explicit_bad_encoded2() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); - b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); - b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("UTF-8")); - b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); - b.write("encoding ISO-8859-1\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Hi\n".getBytes("UTF-8")); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding ISO-8859-1\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Hi\n".getBytes(UTF_8)); final RevCommit c; c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -304,6 +310,86 @@ } @Test + public void testParse_incorrectUtf8Name() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n" + .getBytes(UTF_8)); + b.write("encoding 'utf8'\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("'utf8'", c.getEncodingName()); + assertEquals("Sm\u00f6rg\u00e5sbord\n", c.getFullMessage()); + + try { + c.getEncoding(); + fail("Expected " + IllegalCharsetNameException.class); + } catch (IllegalCharsetNameException badName) { + assertEquals("'utf8'", badName.getMessage()); + } + } + + @Test + public void testParse_illegalEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("utf-8logoutputencoding=gbk", c.getEncodingName()); + assertEquals("message\n", c.getFullMessage()); + assertEquals("message", c.getShortMessage()); + assertTrue(c.getFooterLines().isEmpty()); + assertEquals("au", c.getAuthorIdent().getName()); + + try { + c.getEncoding(); + fail("Expected " + IllegalCharsetNameException.class); + } catch (IllegalCharsetNameException badName) { + assertEquals("utf-8logoutputencoding=gbk", badName.getMessage()); + } + } + + @Test + public void testParse_unsupportedEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding it_IT.UTF8\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("it_IT.UTF8", c.getEncodingName()); + assertEquals("message\n", c.getFullMessage()); + assertEquals("message", c.getShortMessage()); + assertTrue(c.getFooterLines().isEmpty()); + assertEquals("au", c.getAuthorIdent().getName()); + + try { + c.getEncoding(); + fail("Expected " + UnsupportedCharsetException.class); + } catch (UnsupportedCharsetException badName) { + assertEquals("it_IT.UTF8", badName.getMessage()); + } + } + + @Test public void testParse_NoMessage() throws Exception { final String msg = ""; final RevCommit c = create(msg); @@ -367,9 +453,10 @@ @Test public void testParse_PublicParseMethod() throws UnsupportedEncodingException { - ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); CommitBuilder src = new CommitBuilder(); - src.setTreeId(fmt.idFor(Constants.OBJ_TREE, new byte[] {})); + try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { + src.setTreeId(fmt.idFor(Constants.OBJ_TREE, new byte[] {})); + } src.setAuthor(author); src.setCommitter(committer); src.setMessage("Test commit\n\nThis is a test.\n"); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -146,6 +146,5 @@ set.add(flag1); assertTrue(set.contains(flag1)); assertFalse(set.contains(flag2)); - assertFalse(set.contains("bob")); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,7 @@ assertSame(a, a.getId()); } + @SuppressWarnings("unlikely-arg-type") @Test public void testEquals() throws Exception { final RevCommit a1 = commit(); @@ -73,9 +74,12 @@ assertTrue(a1.equals((Object) a1)); assertFalse(a1.equals("")); - final RevWalk rw2 = new RevWalk(db); - final RevCommit a2 = rw2.parseCommit(a1); - final RevCommit b2 = rw2.parseCommit(b1); + final RevCommit a2; + final RevCommit b2; + try (final RevWalk rw2 = new RevWalk(db)) { + a2 = rw2.parseCommit(a1); + b2 = rw2.parseCommit(b1); + } assertNotSame(a1, a2); assertNotSame(b1, b2); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,6 +53,7 @@ RevWalkTestCase { protected T q; + @Override public void setUp() throws Exception { super.setUp(); q = create(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -96,7 +98,7 @@ assertNull(c.getObject()); assertNull(c.getTagName()); - c.parseCanonical(rw, b.toString().getBytes("UTF-8")); + c.parseCanonical(rw, b.toString().getBytes(UTF_8)); assertNotNull(c.getObject()); assertEquals(id, c.getObject().getId()); assertSame(rw.lookupAny(id, typeCode), c.getObject()); @@ -139,7 +141,7 @@ assertNull(c.getObject()); assertNull(c.getTagName()); - c.parseCanonical(rw, body.toString().getBytes("UTF-8")); + c.parseCanonical(rw, body.toString().getBytes(UTF_8)); assertNotNull(c.getObject()); assertEquals(treeId, c.getObject().getId()); assertSame(rw.lookupTree(treeId), c.getObject()); @@ -187,7 +189,7 @@ assertNull(c.getObject()); assertNull(c.getTagName()); - c.parseCanonical(rw, body.toString().getBytes("UTF-8")); + c.parseCanonical(rw, body.toString().getBytes(UTF_8)); assertNotNull(c.getObject()); assertEquals(treeId, c.getObject().getId()); assertSame(rw.lookupTree(treeId), c.getObject()); @@ -211,7 +213,7 @@ final RevTag c; c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); - c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8)); return c; } @@ -219,17 +221,17 @@ public void testParse_implicit_UTF8_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" - .getBytes("UTF-8")); - b.write("type tree\n".getBytes("UTF-8")); - b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.2.3.4.5\n".getBytes(UTF_8)); b .write("tagger F\u00f6r fattare 1218123387 +0700\n" - .getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); final RevTag c; c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -244,16 +246,15 @@ public void testParse_implicit_mixed_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" - .getBytes("UTF-8")); - b.write("type tree\n".getBytes("UTF-8")); - b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); - b - .write("tagger F\u00f6r fattare 1218123387 +0700\n" - .getBytes("ISO-8859-1")); - b.write("\n".getBytes("UTF-8")); - b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.2.3.4.5\n".getBytes(UTF_8)); + b.write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes(ISO_8859_1)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); final RevTag c; c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -306,17 +307,17 @@ public void testParse_explicit_bad_encoded() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" - .getBytes("UTF-8")); - b.write("type tree\n".getBytes("UTF-8")); - b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.2.3.4.5\n".getBytes(UTF_8)); b .write("tagger F\u00f6r fattare 1218123387 +0700\n" - .getBytes("ISO-8859-1")); - b.write("encoding EUC-JP\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Hi\n".getBytes("UTF-8")); + .getBytes(ISO_8859_1)); + b.write("encoding EUC-JP\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Hi\n".getBytes(UTF_8)); final RevTag c; c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -341,17 +342,17 @@ public void testParse_explicit_bad_encoded2() throws Exception { final ByteArrayOutputStream b = new ByteArrayOutputStream(); b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" - .getBytes("UTF-8")); - b.write("type tree\n".getBytes("UTF-8")); - b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.2.3.4.5\n".getBytes(UTF_8)); b .write("tagger F\u00f6r fattare 1218123387 +0700\n" - .getBytes("UTF-8")); - b.write("encoding ISO-8859-1\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); - b.write("\n".getBytes("UTF-8")); - b.write("Hi\n".getBytes("UTF-8")); + .getBytes(UTF_8)); + b.write("encoding ISO-8859-1\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("\u304d\u308c\u3044\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Hi\n".getBytes(UTF_8)); final RevTag c; c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); c.parseCanonical(new RevWalk(db), b.toByteArray()); @@ -362,6 +363,44 @@ } @Test + public void testParse_illegalEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.0\n".getBytes(UTF_8)); + b.write("tagger t 1218123387 +0700\n".getBytes(UTF_8)); + b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + t.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("t", t.getTaggerIdent().getName()); + assertEquals("message", t.getShortMessage()); + assertEquals("message\n", t.getFullMessage()); + } + + @Test + public void testParse_unsupportedEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.0\n".getBytes(UTF_8)); + b.write("tagger t 1218123387 +0700\n".getBytes(UTF_8)); + b.write("encoding it_IT.UTF8\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + t.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("t", t.getTaggerIdent().getName()); + assertEquals("message", t.getShortMessage()); + assertEquals("message\n", t.getFullMessage()); + } + + @Test public void testParse_NoMessage() throws Exception { final String msg = ""; final RevTag c = create(msg); @@ -424,10 +463,11 @@ @Test public void testParse_PublicParseMethod() throws CorruptObjectException { - ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); TagBuilder src = new TagBuilder(); - src.setObjectId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}), - Constants.OBJ_TREE); + try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { + src.setObjectId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}), + Constants.OBJ_TREE); + } src.setTagger(committer); src.setTag("a.test"); src.setMessage("Test tag\n\nThis is a test.\n"); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.revwalk; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class RevWalkCarryFlagsTest extends RevWalkTestCase { + /** + * Test that the uninteresting flag is carried over correctly. Every commit + * should have the uninteresting flag resulting in a RevWalk returning no + * commit. + * + * @throws Exception + */ + @Test + public void testRevWalkCarryUninteresting_fastClock() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d = commit(c); + final RevCommit e = commit(b, d); + + markStart(d); + markUninteresting(e); + assertNull("Found an unexpected commit", rw.next()); + } + + /** + * Similar to {@link #testRevWalkCarryUninteresting_fastClock()} but the + * last merge commit is created so fast that he has the same creationdate as + * the previous commit. This will cause the underlying {@link DateRevQueue} + * is not able to sort the commits in a way matching the topology. A parent + * (one of the commits which are merged) is handled before the child (the + * merge commit). This makes carrying over flags more complicated + * + * @throws Exception + */ + @Test + public void testRevWalkCarryUninteresting_SlowClock() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d = commit(c); + final RevCommit e = commit(0, b, d); + + markStart(d); + markUninteresting(e); + assertNull("Found an unexpected commit", rw.next()); + } + + /** + * Similar to {@link #testRevWalkCarryUninteresting_SlowClock()} but the + * last merge commit is created with a inconsistent creationdate. The merge + * commit has a older creationdate then one of the commits he is merging. + * + * @throws Exception + */ + @Test + public void testRevWalkCarryUninteresting_WrongClock() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d = commit(c); + final RevCommit e = commit(-1, b, d); + + markStart(d); + markUninteresting(e); + assertNull("Found an unexpected commit", rw.next()); + } + + /** + * Same as {@link #testRevWalkCarryUninteresting_SlowClock()} but this time + * we focus on the carrying over a custom flag. + * + * @throws Exception + */ + @Test + public void testRevWalkCarryCustom_SlowClock() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d = commit(c); + final RevCommit e = commit(0, b, d); + + markStart(d); + markStart(e); + RevFlag customFlag = rw.newFlag("CUSTOM"); + e.flags |= customFlag.mask; + rw.carry(customFlag); + + // the merge commit has the flag and it should be carried over -> every + // commit should have this flag + int count = 0; + for (RevCommit cm : rw) { + assertTrue( + "Found a commit which doesn't have the custom flag: " + cm, + cm.has(customFlag)); + count++; + } + assertTrue("Didn't walked over all commits", count == 5); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -250,14 +250,14 @@ final RevCommit b = commit(a); tick(100); - Date since = getClock(); + Date since = getDate(); final RevCommit c1 = commit(b); tick(100); final RevCommit c2 = commit(b); tick(100); - Date until = getClock(); + Date until = getDate(); final RevCommit d = commit(c1, c2); tick(100); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,7 +58,7 @@ public class RevWalkFollowFilterTest extends RevWalkTestCase { private static class DiffCollector extends RenameCallback { - List diffs = new ArrayList(); + List diffs = new ArrayList<>(); @Override public void renamed(DiffEntry diff) { @@ -145,8 +145,9 @@ /** * Assert which renames should have happened, in traversal order. + * * @param expectedRenames - * the rename specs, each one in the form "srcPath->destPath" + * the rename specs, each one in the form "srcPath->destPath" */ protected void assertRenames(String... expectedRenames) { Assert.assertEquals("Unexpected number of renames. Expected: " + diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -146,4 +146,29 @@ assertCommit(b, rw.next()); assertNull(rw.next()); } + + @Test + public void testInconsistentCommitTimes() throws Exception { + // When commit times are inconsistent (a parent is younger than a child) + // make sure that not both, parent and child, are reported as merge + // base. In the following repo the merge base between C,D should be B. + // But when A is younger than B the MergeBaseGenerator used to generate + // A before it detected that B is also a merge base. + // + // +---C + // / / + // A---B---D + + final RevCommit a = commit(2); + final RevCommit b = commit(-1, a); + final RevCommit c = commit(2, b, a); + final RevCommit d = commit(1, b); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(d); + markStart(c); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } + } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java 2019-09-03 12:37:49.000000000 +0000 @@ -69,6 +69,7 @@ private HashMap byName; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -94,7 +95,7 @@ h = commit(f.getTree(), g, f); i = commit(tree(file(pA, zS), file(pE, zY), file(pF, zF)), h); - byName = new HashMap(); + byName = new HashMap<>(); for (Field z : RevWalkPathFilter6012Test.class.getDeclaredFields()) { if (z.getType() == RevCommit.class) byName.put((RevCommit) z.get(this), z.getName()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,7 +62,7 @@ @Override public void setUp() throws Exception { super.setUp(); - util = new TestRepository(db, createRevWalk()); + util = new TestRepository<>(db, createRevWalk()); rw = util.getRevWalk(); } @@ -70,8 +70,8 @@ return new RevWalk(db); } - protected Date getClock() { - return util.getClock(); + protected Date getDate() { + return util.getDate(); } protected void tick(final int secDelta) { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -81,4 +81,4 @@ public void testSkipRevFilterNegative() throws Exception { SkipRevFilter.create(-1); } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,6 +42,8 @@ */ package org.eclipse.jgit.storage.file; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.util.FileUtils.pathToString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -103,7 +105,7 @@ @Test public void testUTF8withoutBOM() throws IOException, ConfigInvalidException { - final File file = createFile(CONTENT1.getBytes("UTF-8")); + final File file = createFile(CONTENT1.getBytes(UTF_8)); final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); config.load(); assertEquals(ALICE, config.getString(USER, null, NAME)); @@ -119,7 +121,7 @@ bos1.write(0xEF); bos1.write(0xBB); bos1.write(0xBF); - bos1.write(CONTENT1.getBytes("UTF-8")); + bos1.write(CONTENT1.getBytes(UTF_8)); final File file = createFile(bos1.toByteArray()); final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); @@ -133,7 +135,7 @@ bos2.write(0xEF); bos2.write(0xBB); bos2.write(0xBF); - bos2.write(CONTENT2.getBytes("UTF-8")); + bos2.write(CONTENT2.getBytes(UTF_8)); assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); } @@ -157,14 +159,91 @@ assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); } + @Test + public void testIncludeAbsolute() + throws IOException, ConfigInvalidException { + final File includedFile = createFile(CONTENT1.getBytes()); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("[include]\npath=".getBytes()); + bos.write(pathToString(includedFile).getBytes()); + + final File file = createFile(bos.toByteArray()); + final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); + config.load(); + assertEquals(ALICE, config.getString(USER, null, NAME)); + } + + @Test + public void testIncludeRelativeDot() + throws IOException, ConfigInvalidException { + final File includedFile = createFile(CONTENT1.getBytes(), "dir1"); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("[include]\npath=".getBytes()); + bos.write(("./" + includedFile.getName()).getBytes()); + + final File file = createFile(bos.toByteArray(), "dir1"); + final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); + config.load(); + assertEquals(ALICE, config.getString(USER, null, NAME)); + } + + @Test + public void testIncludeRelativeDotDot() + throws IOException, ConfigInvalidException { + final File includedFile = createFile(CONTENT1.getBytes(), "dir1"); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("[include]\npath=".getBytes()); + bos.write(("../" + includedFile.getParentFile().getName() + "/" + + includedFile.getName()).getBytes()); + + final File file = createFile(bos.toByteArray(), "dir2"); + final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); + config.load(); + assertEquals(ALICE, config.getString(USER, null, NAME)); + } + + @Test + public void testIncludeRelativeDotDotNotFound() + throws IOException, ConfigInvalidException { + final File includedFile = createFile(CONTENT1.getBytes()); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("[include]\npath=".getBytes()); + bos.write(("../" + includedFile.getName()).getBytes()); + + final File file = createFile(bos.toByteArray()); + final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); + config.load(); + assertEquals(null, config.getString(USER, null, NAME)); + } + + @Test + public void testIncludeWithTilde() + throws IOException, ConfigInvalidException { + final File includedFile = createFile(CONTENT1.getBytes(), "home"); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("[include]\npath=".getBytes()); + bos.write(("~/" + includedFile.getName()).getBytes()); + + final File file = createFile(bos.toByteArray(), "repo"); + final FS fs = FS.DETECTED.newInstance(); + fs.setUserHome(includedFile.getParentFile()); + + final FileBasedConfig config = new FileBasedConfig(file, fs); + config.load(); + assertEquals(ALICE, config.getString(USER, null, NAME)); + } + private File createFile(byte[] content) throws IOException { - trash.mkdirs(); - File f = File.createTempFile(getClass().getName(), null, trash); - FileOutputStream os = new FileOutputStream(f, true); - try { + return createFile(content, null); + } + + private File createFile(byte[] content, String subdir) throws IOException { + File dir = subdir != null ? new File(trash, subdir) : trash; + dir.mkdirs(); + + File f = File.createTempFile(getClass().getName(), null, dir); + try (FileOutputStream os = new FileOutputStream(f, true)) { os.write(content); - } finally { - os.close(); } return f; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -119,36 +119,37 @@ @Test public void addSubmodule() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); - - SubmoduleAddCommand command = new SubmoduleAddCommand(db); - String path = "sub"; - command.setPath(path); - String uri = db.getDirectory().toURI().toString(); - command.setURI(uri); - Repository repo = command.call(); - assertNotNull(repo); - ObjectId subCommit = repo.resolve(Constants.HEAD); - repo.close(); - - SubmoduleWalk generator = SubmoduleWalk.forIndex(db); - assertTrue(generator.next()); - assertEquals(path, generator.getPath()); - assertEquals(commit, generator.getObjectId()); - assertEquals(uri, generator.getModulesUrl()); - assertEquals(path, generator.getModulesPath()); - assertEquals(uri, generator.getConfigUrl()); - Repository subModRepo = generator.getRepository(); - assertNotNull(subModRepo); - assertEquals(subCommit, commit); - subModRepo.close(); - - Status status = Git.wrap(db).status().call(); - assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); - assertTrue(status.getAdded().contains(path)); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + command.setPath(path); + String uri = db.getDirectory().toURI().toString(); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + ObjectId subCommit = repo.resolve(Constants.HEAD); + repo.close(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + assertEquals(uri, generator.getConfigUrl()); + Repository subModRepo = generator.getRepository(); + assertNotNull(subModRepo); + assertEquals(subCommit, commit); + subModRepo.close(); + + Status status = Git.wrap(db).status().call(); + assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); + assertTrue(status.getAdded().contains(path)); + } } @Test @@ -160,6 +161,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -181,46 +183,79 @@ } @Test - public void addSubmoduleWithRelativeUri() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - RevCommit commit = git.commit().setMessage("create file").call(); + public void addSubmoduleWithInvalidPath() throws Exception { + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + command.setPath("-invalid-path"); + // TODO(ms) set name to a valid value in 5.1.0 and adapt expected + // message below + command.setURI("http://example.com/repo/x.git"); + try { + command.call().close(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + // TODO(ms) should check for submodule path, but can't set name + // before 5.1.0 + assertEquals("Invalid submodule name '-invalid-path'", + e.getMessage()); + } + } + @Test + public void addSubmoduleWithInvalidUri() throws Exception { SubmoduleAddCommand command = new SubmoduleAddCommand(db); - String path = "sub"; - String uri = "./.git"; - command.setPath(path); - command.setURI(uri); - Repository repo = command.call(); - assertNotNull(repo); - addRepoToClose(repo); - - SubmoduleWalk generator = SubmoduleWalk.forIndex(db); - assertTrue(generator.next()); - assertEquals(path, generator.getPath()); - assertEquals(commit, generator.getObjectId()); - assertEquals(uri, generator.getModulesUrl()); - assertEquals(path, generator.getModulesPath()); - String fullUri = db.getDirectory().getAbsolutePath(); - if (File.separatorChar == '\\') - fullUri = fullUri.replace('\\', '/'); - assertEquals(fullUri, generator.getConfigUrl()); - Repository subModRepo = generator.getRepository(); - assertNotNull(subModRepo); - assertEquals( - fullUri, - subModRepo - .getConfig() - .getString(ConfigConstants.CONFIG_REMOTE_SECTION, - Constants.DEFAULT_REMOTE_NAME, - ConfigConstants.CONFIG_KEY_URL)); - subModRepo.close(); - assertEquals(commit, repo.resolve(Constants.HEAD)); - - Status status = Git.wrap(db).status().call(); - assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); - assertTrue(status.getAdded().contains(path)); + command.setPath("valid-path"); + command.setURI("-upstream"); + try { + command.call().close(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid submodule URL '-upstream'", e.getMessage()); + } + } + + @Test + public void addSubmoduleWithRelativeUri() throws Exception { + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + String uri = "./.git"; + command.setPath(path); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + addRepoToClose(repo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + String fullUri = db.getDirectory().getAbsolutePath(); + if (File.separatorChar == '\\') { + fullUri = fullUri.replace('\\', '/'); + } + assertEquals(fullUri, generator.getConfigUrl()); + Repository subModRepo = generator.getRepository(); + assertNotNull(subModRepo); + assertEquals( + fullUri, + subModRepo + .getConfig() + .getString(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, + ConfigConstants.CONFIG_KEY_URL)); + subModRepo.close(); + assertEquals(commit, repo.resolve(Constants.HEAD)); + + Status status = Git.wrap(db).status().call(); + assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); + assertTrue(status.getAdded().contains(path)); + } } @Test @@ -237,31 +272,32 @@ path1, ConfigConstants.CONFIG_KEY_URL, url1); modulesConfig.save(); - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - assertNotNull(git.commit().setMessage("create file").call()); - - SubmoduleAddCommand command = new SubmoduleAddCommand(db); - command.setPath(path2); - String url2 = db.getDirectory().toURI().toString(); - command.setURI(url2); - Repository r = command.call(); - assertNotNull(r); - addRepoToClose(r); - - modulesConfig.load(); - assertEquals(path1, modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path1, - ConfigConstants.CONFIG_KEY_PATH)); - assertEquals(url1, modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path1, - ConfigConstants.CONFIG_KEY_URL)); - assertEquals(path2, modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path2, - ConfigConstants.CONFIG_KEY_PATH)); - assertEquals(url2, modulesConfig.getString( - ConfigConstants.CONFIG_SUBMODULE_SECTION, path2, - ConfigConstants.CONFIG_KEY_URL)); + try (Git git = new Git(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + assertNotNull(git.commit().setMessage("create file").call()); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + command.setPath(path2); + String url2 = db.getDirectory().toURI().toString(); + command.setURI(url2); + Repository r = command.call(); + assertNotNull(r); + addRepoToClose(r); + + modulesConfig.load(); + assertEquals(path1, modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path1, + ConfigConstants.CONFIG_KEY_PATH)); + assertEquals(url1, modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path1, + ConfigConstants.CONFIG_KEY_URL)); + assertEquals(path2, modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path2, + ConfigConstants.CONFIG_KEY_PATH)); + assertEquals(url2, modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path2, + ConfigConstants.CONFIG_KEY_URL)); + } } -} \ No newline at end of file +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017, Two Sigma Open Source + * 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.submodule; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.SubmoduleDeinitCommand; +import org.eclipse.jgit.api.SubmoduleDeinitResult; +import org.eclipse.jgit.api.SubmoduleUpdateCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests of {@link SubmoduleDeinitCommand} + */ +public class SubmoduleDeinitTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() throws GitAPIException { + SubmoduleDeinitCommand command = new SubmoduleDeinitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertTrue(modules.isEmpty()); + } + + @Test + public void alreadyClosedSubmodule() throws Exception { + final String path = "sub"; + Git git = Git.wrap(db); + + commitSubmoduleCreation(path, git); + + SubmoduleDeinitResult result = runDeinit(new SubmoduleDeinitCommand(db).addPath("sub")); + assertEquals(path, result.getPath()); + assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.ALREADY_DEINITIALIZED, result.getStatus()); + } + + @Test + public void dirtySubmoduleBecauseUntracked() throws Exception { + final String path = "sub"; + Git git = Git.wrap(db); + + commitSubmoduleCreation(path, git); + + Collection updated = new SubmoduleUpdateCommand(db).addPath(path).setFetch(false).call(); + assertEquals(1, updated.size()); + + File submoduleDir = assertSubmoduleIsInitialized(); + SubmoduleWalk generator; + + write(new File(submoduleDir, "untracked"), "untracked"); + + SubmoduleDeinitResult result = runDeinit(new SubmoduleDeinitCommand(db).addPath("sub")); + assertEquals(path, result.getPath()); + assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.DIRTY, result.getStatus()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertTrue(submoduleDir.isDirectory()); + assertNotEquals(0, submoduleDir.list().length); + } + + @Test + public void dirtySubmoduleBecauseNewCommit() throws Exception { + final String path = "sub"; + Git git = Git.wrap(db); + + commitSubmoduleCreation(path, git); + + Collection updated = new SubmoduleUpdateCommand(db).addPath(path).setFetch(false).call(); + assertEquals(1, updated.size()); + + File submoduleDir = assertSubmoduleIsInitialized(); + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + generator.next(); + + //want to create a commit inside the repo... + Repository submoduleLocalRepo = generator.getRepository(); + JGitTestUtil.writeTrashFile(submoduleLocalRepo, "file.txt", "new data"); + Git.wrap(submoduleLocalRepo).commit().setAll(true).setMessage("local commit").call(); + + SubmoduleDeinitResult result = runDeinit(new SubmoduleDeinitCommand(db).addPath("sub")); + assertEquals(path, result.getPath()); + assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.DIRTY, result.getStatus()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertTrue(submoduleDir.isDirectory()); + assertNotEquals(0, submoduleDir.list().length); + } + + private File assertSubmoduleIsInitialized() throws IOException { + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + File submoduleDir = new File(db.getWorkTree(), generator.getPath()); + assertTrue(submoduleDir.isDirectory()); + assertNotEquals(0, submoduleDir.list().length); + return submoduleDir; + } + + @Test + public void dirtySubmoduleWithForce() throws Exception { + final String path = "sub"; + Git git = Git.wrap(db); + + commitSubmoduleCreation(path, git); + + Collection updated = new SubmoduleUpdateCommand(db).addPath(path).setFetch(false).call(); + assertEquals(1, updated.size()); + + File submoduleDir = assertSubmoduleIsInitialized(); + + write(new File(submoduleDir, "untracked"), "untracked"); + + SubmoduleDeinitCommand command = new SubmoduleDeinitCommand(db).addPath("sub").setForce(true); + SubmoduleDeinitResult result = runDeinit(command); + assertEquals(path, result.getPath()); + assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.FORCED, result.getStatus()); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertTrue(submoduleDir.isDirectory()); + assertEquals(0, submoduleDir.list().length); + } + + @Test + public void cleanSubmodule() throws Exception { + final String path = "sub"; + Git git = Git.wrap(db); + + commitSubmoduleCreation(path, git); + + Collection updated = new SubmoduleUpdateCommand(db).addPath(path).setFetch(false).call(); + assertEquals(1, updated.size()); + + File submoduleDir = assertSubmoduleIsInitialized(); + + SubmoduleDeinitResult result = runDeinit(new SubmoduleDeinitCommand(db).addPath("sub")); + assertEquals(path, result.getPath()); + assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.SUCCESS, result.getStatus()); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertTrue(submoduleDir.isDirectory()); + assertEquals(0, submoduleDir.list().length); + } + + private SubmoduleDeinitResult runDeinit(SubmoduleDeinitCommand command) throws GitAPIException { + Collection deinitialized = command.call(); + assertNotNull(deinitialized); + assertEquals(1, deinitialized.size()); + return deinitialized.iterator().next(); + } + + + private RevCommit commitSubmoduleCreation(String path, Git git) throws IOException, GitAPIException { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit commit = git.commit().setMessage("create file").call(); + + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(commit); + } + }); + editor.commit(); + + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, db.getDirectory().toURI() + .toString()); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.save(); + + new File(db.getWorkTree(), "sub").mkdir(); + git.add().addFilepattern(Constants.DOT_GIT_MODULES).call(); + git.commit().setMessage("create submodule").call(); + return commit; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -321,6 +321,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -59,11 +59,11 @@ import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -92,6 +92,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -124,6 +125,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -164,6 +166,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -215,6 +218,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -252,15 +256,21 @@ } @Test - public void repositoryWithInitializedSubmodule() throws IOException, - GitAPIException { - final ObjectId id = ObjectId - .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); - final String path = "sub"; + public void repositoryWithInitializedSubmodule() throws Exception { + String path = "sub"; + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + TestRepository subTr = new TestRepository<>(subRepo); + ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy(); + DirCache cache = db.lockDirCache(); DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -282,15 +292,6 @@ ConfigConstants.CONFIG_KEY_URL, url); modulesConfig.save(); - Repository subRepo = Git.init().setBare(false) - .setDirectory(new File(db.getWorkTree(), path)).call() - .getRepository(); - assertNotNull(subRepo); - - RefUpdate update = subRepo.updateRef(Constants.HEAD, true); - update.setNewObjectId(id); - update.forceUpdate(); - SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); Map statuses = command.call(); assertNotNull(statuses); @@ -307,15 +308,21 @@ } @Test - public void repositoryWithDifferentRevCheckedOutSubmodule() - throws IOException, GitAPIException { - final ObjectId id = ObjectId - .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); - final String path = "sub"; + public void repositoryWithDifferentRevCheckedOutSubmodule() throws Exception { + String path = "sub"; + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + TestRepository subTr = new TestRepository<>(subRepo); + ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy(); + DirCache cache = db.lockDirCache(); DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -337,15 +344,7 @@ ConfigConstants.CONFIG_KEY_URL, url); modulesConfig.save(); - Repository subRepo = Git.init().setBare(false) - .setDirectory(new File(db.getWorkTree(), path)).call() - .getRepository(); - assertNotNull(subRepo); - - RefUpdate update = subRepo.updateRef(Constants.HEAD, true); - update.setNewObjectId(ObjectId - .fromString("aaaa0000aaaa0000aaaa0000aaaa0000aaaa0000")); - update.forceUpdate(); + ObjectId newId = subTr.branch(Constants.HEAD).commit().create().copy(); SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); Map statuses = command.call(); @@ -359,7 +358,7 @@ assertNotNull(status); assertEquals(path, status.getPath()); assertEquals(id, status.getIndexId()); - assertEquals(update.getNewObjectId(), status.getHeadId()); + assertEquals(newId, status.getHeadId()); assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, status.getType()); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -95,6 +95,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -156,6 +157,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,6 +93,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(commit); @@ -136,6 +137,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -171,6 +173,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -87,10 +87,11 @@ public class SubmoduleWalkTest extends RepositoryTestCase { private TestRepository testDb; + @Override @Before public void setUp() throws Exception { super.setUp(); - testDb = new TestRepository(db); + testDb = new TestRepository<>(db); } @Test @@ -118,6 +119,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -165,6 +167,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -217,6 +220,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -253,6 +257,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id); @@ -286,6 +291,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path1) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id1); @@ -293,6 +299,7 @@ }); editor.add(new PathEdit(path2) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(id2); @@ -330,6 +337,7 @@ DirCacheEditor editor = cache.editor(); editor.add(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(subId); @@ -337,6 +345,7 @@ }); editor.add(new PathEdit(DOT_GIT_MODULES) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setObjectId(gitmodulesBlob); @@ -375,6 +384,7 @@ .add(DOT_GIT_MODULES, gitmodules.toText()) .edit(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(subId); @@ -412,6 +422,7 @@ .add(DOT_GIT_MODULES, gitmodules.toText()) .edit(new PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(subId); @@ -421,6 +432,46 @@ final CanonicalTreeParser p = new CanonicalTreeParser(); p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree()); + SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub"); + assertEquals(path, gen.getPath()); + assertEquals(subId, gen.getObjectId()); + assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); + assertNull(gen.getConfigUpdate()); + assertNull(gen.getConfigUrl()); + assertEquals("sub", gen.getModulesPath()); + assertNull(gen.getModulesUpdate()); + assertEquals("git://example.com/sub", gen.getModulesUrl()); + assertNull(gen.getRepository()); + assertFalse(gen.next()); + } + + @Test + public void testTreeIteratorWithGitmodulesNameNotPath() throws Exception { + final ObjectId subId = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + final String arbitraryName = "x"; + + final Config gitmodules = new Config(); + gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName, + CONFIG_KEY_PATH, "sub"); + gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName, + CONFIG_KEY_URL, "git://example.com/sub"); + + RevCommit commit = testDb.getRevWalk() + .parseCommit(testDb.commit().noParents() + .add(DOT_GIT_MODULES, gitmodules.toText()) + .edit(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(subId); + } + }).create()); + + final CanonicalTreeParser p = new CanonicalTreeParser(); + p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree()); SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub"); assertEquals(path, gen.getPath()); assertEquals(subId, gen.getObjectId()); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -75,26 +75,27 @@ */ @Test public void fileModeTestFileThenSymlink() throws Exception { - Git git = new Git(db); - writeTrashFile("a", "Hello world a"); - writeTrashFile("b", "Hello world b"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("add files a & b").call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - git.rm().addFilepattern("a").call(); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add symlink a").call(); - - FileEntry entry = new FileTreeIterator.FileEntry(new File( - db.getWorkTree(), "a"), db.getFS()); - assertEquals(FileMode.SYMLINK, entry.getMode()); - - git.checkout().setName(branch_1.getName()).call(); - - entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), - db.getFS()); - assertEquals(FileMode.REGULAR_FILE, entry.getMode()); + try (Git git = new Git(db)) { + writeTrashFile("a", "Hello world a"); + writeTrashFile("b", "Hello world b"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("add files a & b").call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add symlink a").call(); + + FileEntry entry = new FileTreeIterator.FileEntry(new File( + db.getWorkTree(), "a"), db.getFS()); + assertEquals(FileMode.SYMLINK, entry.getMode()); + + git.checkout().setName(branch_1.getName()).call(); + + entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), + db.getFS()); + assertEquals(FileMode.REGULAR_FILE, entry.getMode()); + } } /** @@ -108,26 +109,27 @@ */ @Test public void fileModeTestSymlinkThenFile() throws Exception { - Git git = new Git(db); - writeTrashFile("b", "Hello world b"); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("add file b & symlink a").call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - git.rm().addFilepattern("a").call(); - writeTrashFile("a", "Hello world a"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add file a").call(); - - FileEntry entry = new FileTreeIterator.FileEntry(new File( - db.getWorkTree(), "a"), db.getFS()); - assertEquals(FileMode.REGULAR_FILE, entry.getMode()); - - git.checkout().setName(branch_1.getName()).call(); - - entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), - db.getFS()); - assertEquals(FileMode.SYMLINK, entry.getMode()); + try (Git git = new Git(db)) { + writeTrashFile("b", "Hello world b"); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("add file b & symlink a").call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + writeTrashFile("a", "Hello world a"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add file a").call(); + + FileEntry entry = new FileTreeIterator.FileEntry(new File( + db.getWorkTree(), "a"), db.getFS()); + assertEquals(FileMode.REGULAR_FILE, entry.getMode()); + + git.checkout().setName(branch_1.getName()).call(); + + entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), + db.getFS()); + assertEquals(FileMode.SYMLINK, entry.getMode()); + } } /** @@ -141,27 +143,28 @@ */ @Test public void fileModeTestFolderThenSymlink() throws Exception { - Git git = new Git(db); - FileUtils.mkdirs(new File(db.getWorkTree(), "a")); - writeTrashFile("a/b", "Hello world b"); - writeTrashFile("c", "Hello world c"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("add folder a").call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - git.rm().addFilepattern("a").call(); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add symlink a").call(); - - FileEntry entry = new FileTreeIterator.FileEntry(new File( - db.getWorkTree(), "a"), db.getFS()); - assertEquals(FileMode.SYMLINK, entry.getMode()); - - git.checkout().setName(branch_1.getName()).call(); - - entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), - db.getFS()); - assertEquals(FileMode.TREE, entry.getMode()); + try (Git git = new Git(db)) { + FileUtils.mkdirs(new File(db.getWorkTree(), "a")); + writeTrashFile("a/b", "Hello world b"); + writeTrashFile("c", "Hello world c"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("add folder a").call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add symlink a").call(); + + FileEntry entry = new FileTreeIterator.FileEntry(new File( + db.getWorkTree(), "a"), db.getFS()); + assertEquals(FileMode.SYMLINK, entry.getMode()); + + git.checkout().setName(branch_1.getName()).call(); + + entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), + db.getFS()); + assertEquals(FileMode.TREE, entry.getMode()); + } } /** @@ -175,58 +178,60 @@ */ @Test public void fileModeTestSymlinkThenFolder() throws Exception { - Git git = new Git(db); - writeTrashFile("c", "Hello world c"); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c"); - git.add().addFilepattern(".").call(); - git.commit().setMessage("add symlink a").call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - git.rm().addFilepattern("a").call(); - FileUtils.mkdirs(new File(db.getWorkTree(), "a")); - writeTrashFile("a/b", "Hello world b"); - git.add().addFilepattern("a").call(); - git.commit().setMessage("add folder a").call(); - - FileEntry entry = new FileTreeIterator.FileEntry(new File( - db.getWorkTree(), "a"), db.getFS()); - assertEquals(FileMode.TREE, entry.getMode()); - - git.checkout().setName(branch_1.getName()).call(); - - entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), - db.getFS()); - assertEquals(FileMode.SYMLINK, entry.getMode()); + try (Git git = new Git(db)) { + writeTrashFile("c", "Hello world c"); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("add symlink a").call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + FileUtils.mkdirs(new File(db.getWorkTree(), "a")); + writeTrashFile("a/b", "Hello world b"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("add folder a").call(); + + FileEntry entry = new FileTreeIterator.FileEntry(new File( + db.getWorkTree(), "a"), db.getFS()); + assertEquals(FileMode.TREE, entry.getMode()); + + git.checkout().setName(branch_1.getName()).call(); + + entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"), + db.getFS()); + assertEquals(FileMode.SYMLINK, entry.getMode()); + } } /** * Steps: 1.Add file 'b' 2.Commit 3.Create branch '1' 4.Add symlink 'a' * 5.Commit 6.Checkout branch '1' * - * The working tree should not contain 'a' -> FileMode.MISSING after the + * The working tree should not contain 'a' -> FileMode.MISSING after the * checkout. * * @throws Exception */ @Test public void fileModeTestMissingThenSymlink() throws Exception { - Git git = new Git(db); - writeTrashFile("b", "Hello world b"); - git.add().addFilepattern(".").call(); - RevCommit commit1 = git.commit().setMessage("add file b").call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); - git.add().addFilepattern("a").call(); - RevCommit commit2 = git.commit().setMessage("add symlink a").call(); - - git.checkout().setName(branch_1.getName()).call(); - - TreeWalk tw = new TreeWalk(db); - tw.addTree(commit1.getTree()); - tw.addTree(commit2.getTree()); - List scan = DiffEntry.scan(tw); - assertEquals(1, scan.size()); - assertEquals(FileMode.SYMLINK, scan.get(0).getNewMode()); - assertEquals(FileMode.MISSING, scan.get(0).getOldMode()); + try (Git git = new Git(db); + TreeWalk tw = new TreeWalk(db);) { + writeTrashFile("b", "Hello world b"); + git.add().addFilepattern(".").call(); + RevCommit commit1 = git.commit().setMessage("add file b").call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); + git.add().addFilepattern("a").call(); + RevCommit commit2 = git.commit().setMessage("add symlink a").call(); + + git.checkout().setName(branch_1.getName()).call(); + + tw.addTree(commit1.getTree()); + tw.addTree(commit2.getTree()); + List scan = DiffEntry.scan(tw); + assertEquals(1, scan.size()); + assertEquals(FileMode.SYMLINK, scan.get(0).getNewMode()); + assertEquals(FileMode.MISSING, scan.get(0).getOldMode()); + } } /** @@ -240,97 +245,101 @@ */ @Test public void fileModeTestSymlinkThenMissing() throws Exception { - Git git = new Git(db); - writeTrashFile("b", "Hello world b"); - FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); - git.add().addFilepattern(".").call(); - RevCommit commit1 = git.commit().setMessage("add file b & symlink a") - .call(); - Ref branch_1 = git.branchCreate().setName("branch_1").call(); - git.rm().addFilepattern("a").call(); - RevCommit commit2 = git.commit().setMessage("delete symlink a").call(); - - git.checkout().setName(branch_1.getName()).call(); - - TreeWalk tw = new TreeWalk(db); - tw.addTree(commit1.getTree()); - tw.addTree(commit2.getTree()); - List scan = DiffEntry.scan(tw); - assertEquals(1, scan.size()); - assertEquals(FileMode.MISSING, scan.get(0).getNewMode()); - assertEquals(FileMode.SYMLINK, scan.get(0).getOldMode()); + try (Git git = new Git(db); + TreeWalk tw = new TreeWalk(db);) { + writeTrashFile("b", "Hello world b"); + FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b"); + git.add().addFilepattern(".").call(); + RevCommit commit1 = git.commit().setMessage("add file b & symlink a") + .call(); + Ref branch_1 = git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + RevCommit commit2 = git.commit().setMessage("delete symlink a").call(); + + git.checkout().setName(branch_1.getName()).call(); + + tw.addTree(commit1.getTree()); + tw.addTree(commit2.getTree()); + List scan = DiffEntry.scan(tw); + assertEquals(1, scan.size()); + assertEquals(FileMode.MISSING, scan.get(0).getNewMode()); + assertEquals(FileMode.SYMLINK, scan.get(0).getOldMode()); + } } @Test public void createSymlinkAfterTarget() throws Exception { - Git git = new Git(db); - writeTrashFile("a", "start"); - git.add().addFilepattern("a").call(); - RevCommit base = git.commit().setMessage("init").call(); - writeTrashFile("target", "someData"); - FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target"); - git.add().addFilepattern("target").addFilepattern("link").call(); - git.commit().setMessage("add target").call(); - assertEquals(4, db.getWorkTree().list().length); // self-check - git.checkout().setName(base.name()).call(); - assertEquals(2, db.getWorkTree().list().length); // self-check - git.checkout().setName("master").call(); - assertEquals(4, db.getWorkTree().list().length); - String data = read(new File(db.getWorkTree(), "target")); - assertEquals(8, new File(db.getWorkTree(), "target").length()); - assertEquals("someData", data); - data = read(new File(db.getWorkTree(), "link")); - assertEquals("target", - FileUtils.readSymLink(new File(db.getWorkTree(), "link"))); - assertEquals("someData", data); + try (Git git = new Git(db)) { + writeTrashFile("a", "start"); + git.add().addFilepattern("a").call(); + RevCommit base = git.commit().setMessage("init").call(); + writeTrashFile("target", "someData"); + FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target"); + git.add().addFilepattern("target").addFilepattern("link").call(); + git.commit().setMessage("add target").call(); + assertEquals(4, db.getWorkTree().list().length); // self-check + git.checkout().setName(base.name()).call(); + assertEquals(2, db.getWorkTree().list().length); // self-check + git.checkout().setName("master").call(); + assertEquals(4, db.getWorkTree().list().length); + String data = read(new File(db.getWorkTree(), "target")); + assertEquals(8, new File(db.getWorkTree(), "target").length()); + assertEquals("someData", data); + data = read(new File(db.getWorkTree(), "link")); + assertEquals("target", + FileUtils.readSymLink(new File(db.getWorkTree(), "link"))); + assertEquals("someData", data); + } } @Test public void createFileSymlinkBeforeTarget() throws Exception { - Git git = new Git(db); - writeTrashFile("a", "start"); - git.add().addFilepattern("a").call(); - RevCommit base = git.commit().setMessage("init").call(); - writeTrashFile("target", "someData"); - FileUtils.createSymLink(new File(db.getWorkTree(), "tlink"), "target"); - git.add().addFilepattern("target").addFilepattern("tlink").call(); - git.commit().setMessage("add target").call(); - assertEquals(4, db.getWorkTree().list().length); // self-check - git.checkout().setName(base.name()).call(); - assertEquals(2, db.getWorkTree().list().length); // self-check - git.checkout().setName("master").call(); - assertEquals(4, db.getWorkTree().list().length); - String data = read(new File(db.getWorkTree(), "target")); - assertEquals(8, new File(db.getWorkTree(), "target").length()); - assertEquals("someData", data); - data = read(new File(db.getWorkTree(), "tlink")); - assertEquals("target", - FileUtils.readSymLink(new File(db.getWorkTree(), "tlink"))); - assertEquals("someData", data); + try (Git git = new Git(db)) { + writeTrashFile("a", "start"); + git.add().addFilepattern("a").call(); + RevCommit base = git.commit().setMessage("init").call(); + writeTrashFile("target", "someData"); + FileUtils.createSymLink(new File(db.getWorkTree(), "tlink"), "target"); + git.add().addFilepattern("target").addFilepattern("tlink").call(); + git.commit().setMessage("add target").call(); + assertEquals(4, db.getWorkTree().list().length); // self-check + git.checkout().setName(base.name()).call(); + assertEquals(2, db.getWorkTree().list().length); // self-check + git.checkout().setName("master").call(); + assertEquals(4, db.getWorkTree().list().length); + String data = read(new File(db.getWorkTree(), "target")); + assertEquals(8, new File(db.getWorkTree(), "target").length()); + assertEquals("someData", data); + data = read(new File(db.getWorkTree(), "tlink")); + assertEquals("target", + FileUtils.readSymLink(new File(db.getWorkTree(), "tlink"))); + assertEquals("someData", data); + } } @Test public void createDirSymlinkBeforeTarget() throws Exception { - Git git = new Git(db); - writeTrashFile("a", "start"); - git.add().addFilepattern("a").call(); - RevCommit base = git.commit().setMessage("init").call(); - FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target"); - FileUtils.mkdir(new File(db.getWorkTree(), "target")); - writeTrashFile("target/file", "someData"); - git.add().addFilepattern("target").addFilepattern("link").call(); - git.commit().setMessage("add target").call(); - assertEquals(4, db.getWorkTree().list().length); // self-check - git.checkout().setName(base.name()).call(); - assertEquals(2, db.getWorkTree().list().length); // self-check - git.checkout().setName("master").call(); - assertEquals(4, db.getWorkTree().list().length); - String data = read(new File(db.getWorkTree(), "target/file")); - assertEquals(8, new File(db.getWorkTree(), "target/file").length()); - assertEquals("someData", data); - data = read(new File(db.getWorkTree(), "link/file")); - assertEquals("target", - FileUtils.readSymLink(new File(db.getWorkTree(), "link"))); - assertEquals("someData", data); + try (Git git = new Git(db)) { + writeTrashFile("a", "start"); + git.add().addFilepattern("a").call(); + RevCommit base = git.commit().setMessage("init").call(); + FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target"); + FileUtils.mkdir(new File(db.getWorkTree(), "target")); + writeTrashFile("target/file", "someData"); + git.add().addFilepattern("target").addFilepattern("link").call(); + git.commit().setMessage("add target").call(); + assertEquals(4, db.getWorkTree().list().length); // self-check + git.checkout().setName(base.name()).call(); + assertEquals(2, db.getWorkTree().list().length); // self-check + git.checkout().setName("master").call(); + assertEquals(4, db.getWorkTree().list().length); + String data = read(new File(db.getWorkTree(), "target/file")); + assertEquals(8, new File(db.getWorkTree(), "target/file").length()); + assertEquals("someData", data); + data = read(new File(db.getWorkTree(), "link/file")); + assertEquals("target", + FileUtils.readSymLink(new File(db.getWorkTree(), "link"))); + assertEquals("someData", data); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,9 @@ package org.eclipse.jgit.test.resources; import java.io.File; +import java.io.IOException; +import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -57,7 +59,17 @@ @Override public void setUp() throws Exception { super.setUp(); + copyCGitTestPacks(db); + } + /** + * Copy C Git generated pack files into given repository for testing + * + * @param repo + * test repository to receive packfile copies + * @throws IOException + */ + public static void copyCGitTestPacks(FileRepository repo) throws IOException { final String[] packs = { "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f", "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371", @@ -67,13 +79,13 @@ "pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa", "pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12" }; - final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); + final File packDir = repo.getObjectDatabase().getPackDirectory(); for (String n : packs) { JGitTestUtil.copyTestResource(n + ".pack", new File(packDir, n + ".pack")); JGitTestUtil.copyTestResource(n + ".idx", new File(packDir, n + ".idx")); } JGitTestUtil.copyTestResource("packed-refs", - new File(db.getDirectory(), "packed-refs")); + new File(repo.getDirectory(), "packed-refs")); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,191 @@ +/* + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AtomicPushTest { + private URIish uri; + private TestProtocol testProtocol; + private Object ctx = new Object(); + private InMemoryRepository server; + private InMemoryRepository client; + private ObjectId obj1; + private ObjectId obj2; + + @Before + public void setUp() throws Exception { + server = newRepo("server"); + client = newRepo("client"); + testProtocol = new TestProtocol<>( + null, + new ReceivePackFactory() { + @Override + public ReceivePack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + return new ReceivePack(db); + } + }); + uri = testProtocol.register(ctx, server); + + try (ObjectInserter ins = client.newObjectInserter()) { + obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test")); + obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file")); + ins.flush(); + } + } + + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + @Test + public void pushNonAtomic() throws Exception { + PushResult r; + server.setPerformsAtomicTransactions(false); + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(false); + r = tn.push(NullProgressMonitor.INSTANCE, commands()); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + assertSame(RemoteRefUpdate.Status.OK, one.getStatus()); + assertSame( + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + } + + @Test + public void pushAtomicClientGivesUpEarly() throws Exception { + PushResult r; + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(true); + r = tn.push(NullProgressMonitor.INSTANCE, commands()); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + assertSame( + RemoteRefUpdate.Status.REJECTED_OTHER_REASON, + one.getStatus()); + assertSame( + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + assertEquals(JGitText.get().transactionAborted, one.getMessage()); + } + + @Test + public void pushAtomicDisabled() throws Exception { + List cmds = new ArrayList<>(); + cmds.add(new RemoteRefUpdate( + null, null, + obj1, "refs/heads/one", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + cmds.add(new RemoteRefUpdate( + null, null, + obj2, "refs/heads/two", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + + server.setPerformsAtomicTransactions(false); + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(true); + tn.push(NullProgressMonitor.INSTANCE, cmds); + fail("did not throw TransportException"); + } catch (TransportException e) { + assertEquals( + uri + ": " + JGitText.get().atomicPushNotSupported, + e.getMessage()); + } + } + + private List commands() throws IOException { + List cmds = new ArrayList<>(); + cmds.add(new RemoteRefUpdate( + null, null, + obj1, "refs/heads/one", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + cmds.add(new RemoteRefUpdate( + null, null, + obj2, "refs/heads/two", + true /* force update */, + null /* no local tracking ref */, + obj1)); + return cmds; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,10 @@ 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -59,10 +62,16 @@ import java.util.Set; import org.eclipse.jgit.errors.MissingBundlePrerequisiteException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -126,24 +135,26 @@ assertNull(newRepo.resolve("refs/heads/a")); // Next an incremental bundle - bundle = makeBundle("refs/heads/cc", db.resolve("c").name(), - new RevWalk(db).parseCommit(db.resolve("a").toObjectId())); - fetchResult = fetchFromBundle(newRepo, bundle); - advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc"); - assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name()); - assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc") - .name()); - assertNull(newRepo.resolve("refs/heads/c")); - assertNull(newRepo.resolve("refs/heads/a")); // still unknown - - try { - // Check that we actually needed the first bundle - Repository newRepo2 = createBareRepository(); - fetchResult = fetchFromBundle(newRepo2, bundle); - fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled"); - } catch (MissingBundlePrerequisiteException e) { - assertTrue(e.getMessage() - .indexOf(db.resolve("refs/heads/a").name()) >= 0); + try (RevWalk rw = new RevWalk(db)) { + bundle = makeBundle("refs/heads/cc", db.resolve("c").name(), + rw.parseCommit(db.resolve("a").toObjectId())); + fetchResult = fetchFromBundle(newRepo, bundle); + advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc"); + assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name()); + assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc") + .name()); + assertNull(newRepo.resolve("refs/heads/c")); + assertNull(newRepo.resolve("refs/heads/a")); // still unknown + + try { + // Check that we actually needed the first bundle + Repository newRepo2 = createBareRepository(); + fetchResult = fetchFromBundle(newRepo2, bundle); + fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled"); + } catch (MissingBundlePrerequisiteException e) { + assertTrue(e.getMessage() + .indexOf(db.resolve("refs/heads/a").name()) >= 0); + } } } @@ -159,6 +170,39 @@ assertTrue(caught); } + @Test + public void testCustomObjectReader() throws Exception { + String refName = "refs/heads/blob"; + String data = "unflushed data"; + ObjectId id; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (Repository repo = new InMemoryRepository( + new DfsRepositoryDescription("repo")); + ObjectInserter ins = repo.newObjectInserter(); + ObjectReader or = ins.newReader()) { + id = ins.insert(OBJ_BLOB, Constants.encode(data)); + BundleWriter bw = new BundleWriter(or); + bw.include(refName, id); + bw.writeBundle(NullProgressMonitor.INSTANCE, out); + assertNull(repo.exactRef(refName)); + try { + repo.open(id, OBJ_BLOB); + fail("We should not be able to open the unflushed blob"); + } catch (MissingObjectException e) { + // Expected. + } + } + + try (Repository repo = new InMemoryRepository( + new DfsRepositoryDescription("copy"))) { + fetchFromBundle(repo, out.toByteArray()); + Ref ref = repo.exactRef(refName); + assertNotNull(ref); + assertEquals(id, ref.getObjectId()); + assertEquals(data, new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8)); + } + } + private static FetchResult fetchFromBundle(final Repository newRepo, final byte[] bundle) throws URISyntaxException, NotSupportedException, TransportException { @@ -166,8 +210,10 @@ final ByteArrayInputStream in = new ByteArrayInputStream(bundle); final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*"); final Set refs = Collections.singleton(rs); - return new TransportBundleStream(newRepo, uri, in).fetch( - NullProgressMonitor.INSTANCE, refs); + try (TransportBundleStream transport = new TransportBundleStream( + newRepo, uri, in)) { + return transport.fetch(NullProgressMonitor.INSTANCE, refs); + } } private byte[] makeBundle(final String name, diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 Thomas Wolf + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; + +import org.junit.Test; + +/** + * Daemon tests. + */ +public class DaemonTest { + + @Test + public void testDaemonStop() throws Exception { + Daemon d = new Daemon(); + d.start(); + InetSocketAddress address = d.getAddress(); + assertTrue("Port should be allocated", address.getPort() > 0); + assertTrue("Daemon should be running", d.isRunning()); + Thread.sleep(1000); // Give it time to enter accept() + d.stopAndWait(); + // Try to start a new Daemon again on the same port + d = new Daemon(address); + d.start(); + InetSocketAddress newAddress = d.getAddress(); + assertEquals("New daemon should run on the same port", address, + newAddress); + assertTrue("Daemon should be running", d.isRunning()); + Thread.sleep(1000); + d.stopAndWait(); + } + + @Test + public void testDaemonRestart() throws Exception { + Daemon d = new Daemon(); + d.start(); + InetSocketAddress address = d.getAddress(); + assertTrue("Port should be allocated", address.getPort() > 0); + assertTrue("Daemon should be running", d.isRunning()); + Thread.sleep(1000); + d.stopAndWait(); + // Re-start the same daemon + d.start(); + InetSocketAddress newAddress = d.getAddress(); + assertEquals("Daemon should again run on the same port", address, + newAddress); + assertTrue("Daemon should be running", d.isRunning()); + Thread.sleep(1000); + d.stopAndWait(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -100,7 +100,7 @@ } catch (IOException e) { fail("Couldn't instantiate AuthHeadersResponse: " + e.toString()); } - HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(response); + HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(response, null); if (!expectedAuthMethod.equals(getAuthMethodName(authMethod))) { fail("Wrong authentication method: expected " + expectedAuthMethod @@ -113,7 +113,7 @@ } private static class AuthHeadersResponse extends JDKHttpConnection { - Map> headerFields = new HashMap>(); + Map> headerFields = new HashMap<>(); public AuthHeadersResponse(String[] authHeaders) throws MalformedURLException, IOException { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.lib.Config; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for correctly resolving URIs when reading http.* values from a + * {@link Config}. + */ +public class HttpConfigTest { + + private static final String DEFAULT = "[http]\n" + "\tpostBuffer = 1\n" + + "\tsslVerify= true\n" + "\tfollowRedirects = true\n" + + "\tmaxRedirects = 5\n\n"; + + private Config config; + + @Before + public void setUp() { + config = new Config(); + } + + @Test + public void testDefault() throws Exception { + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024 * 1024, http.getPostBuffer()); + assertTrue(http.isSslVerify()); + assertEquals(HttpConfig.HttpRedirectMode.INITIAL, + http.getFollowRedirects()); + } + + @Test + public void testMatchSuccess() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("https://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.org/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com:80/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com:8080/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithOnlySchemeInConfig() throws Exception { + config.fromText( + DEFAULT + "[http \"http://\"]\n" + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithPrefixUriInConfig() throws Exception { + config.fromText(DEFAULT + "[http \"http://example\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchCaseSensitivity() throws Exception { + config.fromText(DEFAULT + "[http \"http://exAMPle.com\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchWithInvalidUriInConfig() throws Exception { + config.fromText( + DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithInvalidAndValidUriInConfig() throws Exception { + config.fromText(DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n" + + "[http \"http://example.com\"]\n" + "\tpostBuffer = 2048\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(2048, http.getPostBuffer()); + } + + @Test + public void testMatchWithHostEndingInSlash() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchWithUser() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n" + + "\tpostBuffer = 1024\n" + + "[http \"http://example.com/path/repo\"]\n" + + "\tpostBuffer = 2048\n" + + "[http \"http://user@example.com/path\"]\n" + + "\tpostBuffer = 4096\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/repo.git")); + assertEquals(4096, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/repo/foo.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/foo.git")); + assertEquals(4096, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/path/foo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://User@example.com/path/repo/foo.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://User@example.com/path/foo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchLonger() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n" + + "\tpostBuffer = 1024\n" + + "[http \"http://example.com/path/repo\"]\n" + + "\tpostBuffer = 2048\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/foo/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("https://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/path/repo/.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, new URIish("http://example.com/path")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path")); + assertEquals(1024, http.getPostBuffer()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +/** + * Basic URI path prefix match tests for {@link HttpConfig}. + */ +public class HttpConfigUriPathTest { + + @Test + public void testNormalizationEmptyPaths() { + assertEquals("/", HttpConfig.normalize("")); + assertEquals("/", HttpConfig.normalize("/")); + } + + @Test + public void testNormalization() { + assertEquals("/f", HttpConfig.normalize("f")); + assertEquals("/f", HttpConfig.normalize("/f")); + assertEquals("/f/", HttpConfig.normalize("/f/")); + assertEquals("/foo", HttpConfig.normalize("foo")); + assertEquals("/foo", HttpConfig.normalize("/foo")); + assertEquals("/foo/", HttpConfig.normalize("/foo/")); + assertEquals("/foo/bar", HttpConfig.normalize("foo/bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/")); + } + + @Test + public void testNormalizationWithDot() { + assertEquals("/", HttpConfig.normalize(".")); + assertEquals("/", HttpConfig.normalize("/.")); + assertEquals("/", HttpConfig.normalize("/./")); + assertEquals("/foo", HttpConfig.normalize("foo/.")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/./bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/./././bar")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/./././bar/")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/././.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./././")); + assertEquals("/foo/bar/.baz/bam", + HttpConfig.normalize("/foo/bar/.baz/bam")); + assertEquals("/foo/bar/.baz/bam/", + HttpConfig.normalize("/foo/bar/.baz/bam/")); + } + + @Test + public void testNormalizationWithDotDot() { + assertEquals("/", HttpConfig.normalize("foo/..")); + assertEquals("/", HttpConfig.normalize("/foo/..")); + assertEquals("/", HttpConfig.normalize("/foo/../bar/..")); + assertEquals("/", HttpConfig.normalize("/foo/.././bar/..")); + assertEquals("/bar", HttpConfig.normalize("foo/../bar")); + assertEquals("/bar", HttpConfig.normalize("/foo/../bar")); + assertEquals("/bar", HttpConfig.normalize("/foo/./.././bar")); + assertEquals("/bar/", HttpConfig.normalize("/foo/../bar/")); + assertEquals("/bar/", HttpConfig.normalize("/foo/./.././bar/")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/baz/..")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/baz/../")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/.././..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../././..")); + assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz")); + assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/")); + assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz/.")); + assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/./")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/../baz/..")); + assertEquals("/foo/", HttpConfig.normalize("/foo/bar/../baz/../")); + assertEquals("/baz", HttpConfig.normalize("/foo/bar/../../baz")); + assertEquals("/baz/", HttpConfig.normalize("/foo/bar/../../baz/")); + assertEquals("/foo/.b/bar", HttpConfig.normalize("/foo/.b/bar")); + assertEquals("/.f/foo/.b/bar/", HttpConfig.normalize(".f/foo/.b/bar/")); + assertEquals("/foo/bar/..baz/bam", + HttpConfig.normalize("/foo/bar/..baz/bam")); + assertEquals("/foo/bar/..baz/bam/", + HttpConfig.normalize("/foo/bar/..baz/bam/")); + assertEquals("/foo/bar/.../baz/bam", + HttpConfig.normalize("/foo/bar/.../baz/bam")); + assertEquals("/foo/bar/.../baz/bam/", + HttpConfig.normalize("/foo/bar/.../baz/bam/")); + } + + @Test + public void testNormalizationWithDoubleSlash() { + assertEquals("/", HttpConfig.normalize("//")); + assertEquals("/foo/", HttpConfig.normalize("///foo//")); + assertEquals("/foo", HttpConfig.normalize("///foo//.")); + assertEquals("/foo/", HttpConfig.normalize("///foo//.////")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar//.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo//bar//./")); + } + + @Test + public void testNormalizationWithDotDotFailing() { + assertNull(HttpConfig.normalize("..")); + assertNull(HttpConfig.normalize("/..")); + assertNull(HttpConfig.normalize("/../")); + assertNull(HttpConfig.normalize("/../foo")); + assertNull(HttpConfig.normalize("./../foo")); + assertNull(HttpConfig.normalize("/./../foo")); + assertNull(HttpConfig.normalize("/foo/./.././..")); + assertNull(HttpConfig.normalize("/foo/../bar/../..")); + assertNull(HttpConfig.normalize("/foo/../bar/../../baz")); + } + + @Test + public void testSegmentCompare() { + // 2nd parameter is the match, will be normalized + assertSuccess("/foo", ""); + assertSuccess("/foo", "/"); + assertSuccess("/foo", "//"); + assertSuccess("/foo", "foo"); + assertSuccess("/foo", "/foo"); + assertSuccess("/foo/", "foo"); + assertSuccess("/foo/", "/foo"); + assertSuccess("/foo/", "foo/"); + assertSuccess("/foo/", "/foo/"); + assertSuccess("/foo/bar", "foo"); + assertSuccess("/foo/bar", "foo/"); + assertSuccess("/foo/bar", "foo/bar"); + assertSuccess("/foo/bar/", "foo/bar"); + assertSuccess("/foo/bar/", "foo/bar/"); + assertSuccess("/foo/bar", "/foo/bar"); + assertSuccess("/foo/bar/", "/foo/bar"); + assertSuccess("/foo/bar/", "/foo/bar/"); + assertSuccess("/foo/bar", "/foo/bar/.."); + assertSuccess("/foo/bar/", "/foo/bar/.."); + assertSuccess("/foo/bar/", "/foo/bar/../"); + assertSuccess("/foo/bar", "/foo/./bar"); + assertSuccess("/foo/bar/", "/foo/./bar/"); + assertSuccess("/some/repo/.git", "/some/repo"); + assertSuccess("/some/repo/bare.git", "/some/repo"); + assertSuccess("/some/repo/.git", "/some/repo/.git"); + assertSuccess("/some/repo/bare.git", "/some/repo/bare.git"); + } + + @Test + public void testSegmentCompareFailing() { + // 2nd parameter is the match, will be normalized + assertEquals(-1, HttpConfig.segmentCompare("/foo", "foo/")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/foo/")); + assertEquals(-1, HttpConfig.segmentCompare("/foobar", "foo")); + assertEquals(-1, HttpConfig.segmentCompare("/foobar", "/foo")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "foo/bar")); + assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar", "/foo/bar")); + assertEquals(-1, + HttpConfig.segmentCompare("/some/repo.git", "/some/repo")); + assertEquals(-1, + HttpConfig.segmentCompare("/some/repo.git", "/some/repo.g")); + assertEquals(-1, HttpConfig.segmentCompare("/some/repo/bare.git", + "/some/repo/bar")); + assertSuccess("/some/repo/bare.git", "/some/repo"); + // Just to make sure we don't use the PathMatchers... + assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar/baz", "**")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "**/foo")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "/*/barbar/**")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/*")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/???")); + assertEquals(-1, HttpConfig.segmentCompare("/foo/bar/baz", "bar")); + // Failing to normalize + assertEquals(-1, + HttpConfig.segmentCompare("/foo/bar/baz", "bar/../..")); + } + + private void assertSuccess(String uri, String match) { + String normalized = HttpConfig.normalize(match); + assertEquals(normalized.length(), + HttpConfig.segmentCompare(uri, match)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2018, Thomas Wolf + * 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.junit.Assert.assertEquals; + +import java.io.File; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.util.FS; +import org.junit.After; +import org.junit.Test; + +import com.jcraft.jsch.Session; + +/** + * Tests for correctly interpreting ssh config values when Jsch sessions are + * used. + */ +public class JschConfigSessionFactoryTest { + + File tmpConfigFile; + + OpenSshConfig tmpConfig; + + DefaultSshSessionFactory factory = new DefaultSshSessionFactory(); + + @After + public void removeTmpConfig() { + if (tmpConfigFile == null) { + return; + } + if (tmpConfigFile.exists() && !tmpConfigFile.delete()) { + tmpConfigFile.deleteOnExit(); + } + tmpConfigFile = null; + } + + @Test + public void testNoConfigEntry() throws Exception { + tmpConfigFile = File.createTempFile("jsch", "test"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://egit/egit/egit"); + assertEquals("egit", session.getHost()); + // No user in URI, none in ssh config: default is OS user name + assertEquals(System.getProperty("user.name"), session.getUserName()); + assertEquals(22, session.getPort()); + } + + @Test + public void testAlias() throws Exception { + tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org", + "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://egit/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("foo", session.getUserName()); + assertEquals(29418, session.getPort()); + } + + @Test + public void testAliasWithUser() throws Exception { + tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org", + "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://bar@egit/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(29418, session.getPort()); + } + + @Test + public void testAliasWithPort() throws Exception { + tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org", + "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://bar@egit:22/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(22, session.getPort()); + } + + @Test + public void testAliasIdentical() throws Exception { + tmpConfigFile = createConfig("Host git.eclipse.org", + "Hostname git.eclipse.org", "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://git.eclipse.org/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("foo", session.getUserName()); + assertEquals(29418, session.getPort()); + } + + @Test + public void testAliasIdenticalWithUser() throws Exception { + tmpConfigFile = createConfig("Host git.eclipse.org", + "Hostname git.eclipse.org", "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://bar@git.eclipse.org/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(29418, session.getPort()); + } + + @Test + public void testAliasIdenticalWithPort() throws Exception { + tmpConfigFile = createConfig("Host git.eclipse.org", + "Hostname git.eclipse.org", "User foo", "Port 29418"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession( + "ssh://bar@git.eclipse.org:300/egit/egit"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(300, session.getPort()); + } + + @Test + public void testConnectTimout() throws Exception { + tmpConfigFile = createConfig("Host git.eclipse.org", + "Hostname git.eclipse.org", "User foo", "Port 29418", + "ConnectTimeout 10"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://git.eclipse.org/something"); + assertEquals("git.eclipse.org", session.getHost()); + assertEquals("foo", session.getUserName()); + assertEquals(29418, session.getPort()); + assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout()); + } + + @Test + public void testAliasCaseDifferenceUpcase() throws Exception { + tmpConfigFile = createConfig("Host Bitbucket.org", + "Hostname bitbucket.org", "User foo", "Port 29418", + "ConnectTimeout 10", // + "Host bitbucket.org", "Hostname bitbucket.org", "User bar", + "Port 22", "ConnectTimeout 5"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://Bitbucket.org/something"); + assertEquals("bitbucket.org", session.getHost()); + assertEquals("foo", session.getUserName()); + assertEquals(29418, session.getPort()); + assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout()); + } + + @Test + public void testAliasCaseDifferenceLowcase() throws Exception { + tmpConfigFile = createConfig("Host Bitbucket.org", + "Hostname bitbucket.org", "User foo", "Port 29418", + "ConnectTimeout 10", // + "Host bitbucket.org", "Hostname bitbucket.org", "User bar", + "Port 22", "ConnectTimeout 5"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://bitbucket.org/something"); + assertEquals("bitbucket.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(22, session.getPort()); + assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout()); + } + + @Test + public void testAliasCaseDifferenceUpcaseInverted() throws Exception { + tmpConfigFile = createConfig("Host bitbucket.org", + "Hostname bitbucket.org", "User bar", "Port 22", + "ConnectTimeout 5", // + "Host Bitbucket.org", "Hostname bitbucket.org", "User foo", + "Port 29418", "ConnectTimeout 10"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://Bitbucket.org/something"); + assertEquals("bitbucket.org", session.getHost()); + assertEquals("foo", session.getUserName()); + assertEquals(29418, session.getPort()); + assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout()); + } + + @Test + public void testAliasCaseDifferenceLowcaseInverted() throws Exception { + tmpConfigFile = createConfig("Host bitbucket.org", + "Hostname bitbucket.org", "User bar", "Port 22", + "ConnectTimeout 5", // + "Host Bitbucket.org", "Hostname bitbucket.org", "User foo", + "Port 29418", "ConnectTimeout 10"); + tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(), + tmpConfigFile); + factory.setConfig(tmpConfig); + Session session = createSession("ssh://bitbucket.org/something"); + assertEquals("bitbucket.org", session.getHost()); + assertEquals("bar", session.getUserName()); + assertEquals(22, session.getPort()); + assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout()); + } + + private File createConfig(String... lines) throws Exception { + File f = File.createTempFile("jsch", "test"); + Files.write(f.toPath(), Arrays.asList(lines)); + return f; + } + + private Session createSession(String uriText) throws Exception { + // For this test to make sense, these few lines must correspond to the + // code in JschConfigSessionFactory.getSession(). Because of + // side-effects we cannot encapsulate that there properly and so we have + // to duplicate this bit here. We also can't test getSession() itself + // since it would try to actually connect to a server. + URIish uri = new URIish(uriText); + String host = uri.getHost(); + String user = uri.getUser(); + String password = uri.getPass(); + int port = uri.getPort(); + OpenSshConfig.Host hostConfig = tmpConfig.lookup(host); + if (port <= 0) { + port = hostConfig.getPort(); + } + if (user == null) { + user = hostConfig.getUser(); + } + return factory.createSession(null, FS.DETECTED, user, password, host, + port, hostConfig); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2009, 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.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import org.junit.Before; -import org.junit.Test; - -public class LongMapTest { - private LongMap map; - - @Before - public void setUp() throws Exception { - map = new LongMap(); - } - - @Test - public void testEmptyMap() { - assertFalse(map.containsKey(0)); - assertFalse(map.containsKey(1)); - - assertNull(map.get(0)); - assertNull(map.get(1)); - - assertNull(map.remove(0)); - assertNull(map.remove(1)); - } - - @Test - public void testInsertMinValue() { - final Long min = Long.valueOf(Long.MIN_VALUE); - assertNull(map.put(Long.MIN_VALUE, min)); - assertTrue(map.containsKey(Long.MIN_VALUE)); - assertSame(min, map.get(Long.MIN_VALUE)); - assertFalse(map.containsKey(Integer.MIN_VALUE)); - } - - @Test - public void testReplaceMaxValue() { - final Long min = Long.valueOf(Long.MAX_VALUE); - final Long one = Long.valueOf(1); - assertNull(map.put(Long.MAX_VALUE, min)); - assertSame(min, map.get(Long.MAX_VALUE)); - assertSame(min, map.put(Long.MAX_VALUE, one)); - assertSame(one, map.get(Long.MAX_VALUE)); - } - - @Test - public void testRemoveOne() { - final long start = 1; - assertNull(map.put(start, Long.valueOf(start))); - assertEquals(Long.valueOf(start), map.remove(start)); - assertFalse(map.containsKey(start)); - } - - @Test - public void testRemoveCollision1() { - // This test relies upon the fact that we always >>> 1 the value - // to derive an unsigned hash code. Thus, 0 and 1 fall into the - // same hash bucket. Further it relies on the fact that we add - // the 2nd put at the top of the chain, so removing the 1st will - // cause a different code path. - // - assertNull(map.put(0, Long.valueOf(0))); - assertNull(map.put(1, Long.valueOf(1))); - assertEquals(Long.valueOf(0), map.remove(0)); - - assertFalse(map.containsKey(0)); - assertTrue(map.containsKey(1)); - } - - @Test - public void testRemoveCollision2() { - // This test relies upon the fact that we always >>> 1 the value - // to derive an unsigned hash code. Thus, 0 and 1 fall into the - // same hash bucket. Further it relies on the fact that we add - // the 2nd put at the top of the chain, so removing the 2nd will - // cause a different code path. - // - assertNull(map.put(0, Long.valueOf(0))); - assertNull(map.put(1, Long.valueOf(1))); - assertEquals(Long.valueOf(1), map.remove(1)); - - assertTrue(map.containsKey(0)); - assertFalse(map.containsKey(1)); - } - - @Test - public void testSmallMap() { - final long start = 12; - final long n = 8; - for (long i = start; i < start + n; i++) - assertNull(map.put(i, Long.valueOf(i))); - for (long i = start; i < start + n; i++) - assertEquals(Long.valueOf(i), map.get(i)); - } - - @Test - public void testLargeMap() { - final long start = Integer.MAX_VALUE; - final long n = 100000; - for (long i = start; i < start + n; i++) - assertNull(map.put(i, Long.valueOf(i))); - for (long i = start; i < start + n; i++) - assertEquals(Long.valueOf(i), map.get(i)); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -62,6 +62,7 @@ private File configFile; + @Override @Before public void setUp() throws Exception { super.setUp(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2014 Google Inc. + * Copyright (C) 2008, 2017 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -43,10 +43,13 @@ package org.eclipse.jgit.transport; +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.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.File; @@ -58,9 +61,12 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.OpenSshConfig.Host; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; +import com.jcraft.jsch.ConfigRepository; + public class OpenSshConfigTest extends RepositoryTestCase { private File home; @@ -68,6 +74,7 @@ private OpenSshConfig osc; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -78,15 +85,18 @@ configFile = new File(new File(home, ".ssh"), Constants.CONFIG); FileUtils.mkdir(configFile.getParentFile()); - System.setProperty("user.name", "jex_junit"); + mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit"); osc = new OpenSshConfig(home, configFile); } private void config(final String data) throws IOException { - final OutputStreamWriter fw = new OutputStreamWriter( - new FileOutputStream(configFile), "UTF-8"); - fw.write(data); - fw.close(); + long lastMtime = configFile.lastModified(); + do { + try (final OutputStreamWriter fw = new OutputStreamWriter( + new FileOutputStream(configFile), "UTF-8")) { + fw.write(data); + } + } while (lastMtime == configFile.lastModified()); } @Test @@ -154,13 +164,18 @@ @Test public void testAlias_DoesNotMatch() throws Exception { - config("Host orcz\n" + "\tHostName repo.or.cz\n"); + config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n"); final Host h = osc.lookup("repo.or.cz"); assertNotNull(h); assertEquals("repo.or.cz", h.getHostName()); assertEquals("jex_junit", h.getUser()); assertEquals(22, h.getPort()); assertNull(h.getIdentityFile()); + final Host h2 = osc.lookup("orcz"); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex_junit", h.getUser()); + assertEquals(29418, h2.getPort()); + assertNull(h.getIdentityFile()); } @Test @@ -281,4 +296,198 @@ assertNotNull(h); assertEquals(1, h.getConnectionAttempts()); } + + @Test + public void testDefaultBlock() throws Exception { + config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(5, h.getConnectionAttempts()); + } + + @Test + public void testHostCaseInsensitive() throws Exception { + config("hOsT orcz\nConnectionAttempts 3\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(3, h.getConnectionAttempts()); + } + + @Test + public void testListValueSingle() throws Exception { + config("Host orcz\nUserKnownHostsFile /foo/bar\n"); + final ConfigRepository.Config c = osc.getConfig("orcz"); + assertNotNull(c); + assertEquals("/foo/bar", c.getValue("UserKnownHostsFile")); + } + + @Test + public void testListValueMultiple() throws Exception { + // Tilde expansion occurs within the parser + config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n"); + final ConfigRepository.Config c = osc.getConfig("orcz"); + assertNotNull(c); + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar" }, + c.getValues("UserKnownHostsFile")); + } + + @Test + public void testRepeatedLookups() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts 5\n"); + final Host h1 = osc.lookup("orcz"); + final Host h2 = osc.lookup("orcz"); + assertNotNull(h1); + assertSame(h1, h2); + assertEquals(5, h1.getConnectionAttempts()); + assertEquals(h1.getConnectionAttempts(), h2.getConnectionAttempts()); + final ConfigRepository.Config c = osc.getConfig("orcz"); + assertNotNull(c); + assertSame(c, h1.getConfig()); + assertSame(c, h2.getConfig()); + } + + @Test + public void testRepeatedLookupsWithModification() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts -1\n"); + final Host h1 = osc.lookup("orcz"); + assertNotNull(h1); + assertEquals(1, h1.getConnectionAttempts()); + config("Host orcz\n" + "\tConnectionAttempts 5\n"); + final Host h2 = osc.lookup("orcz"); + assertNotNull(h2); + assertNotSame(h1, h2); + assertEquals(5, h2.getConnectionAttempts()); + assertEquals(1, h1.getConnectionAttempts()); + assertNotSame(h1.getConfig(), h2.getConfig()); + } + + @Test + public void testIdentityFile() throws Exception { + config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + File f = h.getIdentityFile(); + assertNotNull(f); + // Host does tilde replacement + assertEquals(new File(home, "foo/ba z"), f); + final ConfigRepository.Config c = h.getConfig(); + // Config does tilde replacement, too + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar" }, + c.getValues("IdentityFile")); + } + + @Test + public void testMultiIdentityFile() throws Exception { + config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + File f = h.getIdentityFile(); + assertNotNull(f); + // Host does tilde replacement + assertEquals(new File(home, "foo/ba z"), f); + final ConfigRepository.Config c = h.getConfig(); + // Config does tilde replacement, too + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar", "/foo/baz" }, + c.getValues("IdentityFile")); + } + + @Test + public void testNegatedPattern() throws Exception { + config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz"); + final Host h = osc.lookup("repo.or.cz"); + assertNotNull(h); + assertEquals(new File(home, "foo/bar"), h.getIdentityFile()); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() }, + h.getConfig().getValues("IdentityFile")); + } + + @Test + public void testPattern() throws Exception { + config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz"); + final Host h = osc.lookup("repo.or.cz"); + assertNotNull(h); + assertEquals(new File(home, "foo/bar"), h.getIdentityFile()); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(), + "/foo/baz" }, + h.getConfig().getValues("IdentityFile")); + } + + @Test + public void testMultiHost() throws Exception { + config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz"); + final Host h1 = osc.lookup("repo.or.cz"); + assertNotNull(h1); + assertEquals(new File(home, "foo/bar"), h1.getIdentityFile()); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(), + "/foo/baz" }, + h1.getConfig().getValues("IdentityFile")); + final Host h2 = osc.lookup("orcz"); + assertNotNull(h2); + assertEquals(new File(home, "foo/bar"), h2.getIdentityFile()); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() }, + h2.getConfig().getValues("IdentityFile")); + } + + @Test + public void testEqualsSign() throws Exception { + config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(5, h.getConnectionAttempts()); + assertEquals("foobar", h.getUser()); + } + + @Test + public void testMissingArgument() throws Exception { + config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("foobar", h.getUser()); + assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv")); + assertNull(h.getIdentityFile()); + assertNull(h.getConfig().getValue("ForwardX11")); + } + + @Test + public void testHomeDirUserReplacement() throws Exception { + config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"), + h.getIdentityFile()); + } + + @Test + public void testHostnameReplacement() throws Exception { + config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("orcz.example.org", h.getHostName()); + } + + @Test + public void testRemoteUserReplacement() throws Exception { + config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n" + + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals( + new File(new File(home, ".ssh"), + "orcz.ex%20ample.org_foo_id_dsa"), + h.getIdentityFile()); + } + + @Test + public void testLocalhostFQDNReplacement() throws Exception { + String localhost = SystemReader.getInstance().getHostname(); + config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals( + new File(new File(home, ".ssh"), localhost + "_id_dsa"), + h.getIdentityFile()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -47,7 +47,6 @@ package org.eclipse.jgit.transport; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -169,7 +168,7 @@ @Test public void testPackWithDuplicateBlob() throws Exception { final byte[] data = Constants.encode("0123456789abcdefg"); - TestRepository d = new TestRepository(db); + TestRepository d = new TestRepository<>(db); assertTrue(db.hasObject(d.blob(data))); TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); @@ -270,7 +269,7 @@ fail("PackParser should have failed"); } catch (TooLargeObjectInPackException e) { assertTrue(e.getMessage().contains("13")); // max obj size - assertFalse(e.getMessage().contains("14")); // no delta size + assertTrue(e.getMessage().contains("14")); // delta size } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017, David Pursehouse + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.transport.PushConfig.PushRecurseSubmodulesMode; +import org.junit.Test; + +public class PushConfigTest { + @Test + public void pushRecurseSubmoduleMatch() throws Exception { + assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("check")); + assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("CHECK")); + + assertTrue(PushRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("on-demand")); + assertTrue(PushRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ON-DEMAND")); + assertTrue(PushRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("on_demand")); + assertTrue(PushRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ON_DEMAND")); + + assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("no")); + assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("NO")); + assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("false")); + assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("FALSE")); + } + + @Test + public void pushRecurseSubmoduleNoMatch() throws Exception { + assertFalse(PushRecurseSubmodulesMode.NO.matchConfigValue("N")); + assertFalse(PushRecurseSubmodulesMode.ON_DEMAND + .matchConfigValue("ONDEMAND")); + } + + @Test + public void pushRecurseSubmoduleToConfigValue() { + assertEquals("on-demand", + PushRecurseSubmodulesMode.ON_DEMAND.toConfigValue()); + assertEquals("check", PushRecurseSubmodulesMode.CHECK.toConfigValue()); + assertEquals("false", PushRecurseSubmodulesMode.NO.toConfigValue()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,199 @@ +/* + * 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.BasePackPushConnection.CAPABILITY_REPORT_STATUS; +import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class PushConnectionTest { + private URIish uri; + private TestProtocol testProtocol; + private Object ctx = new Object(); + private InMemoryRepository server; + private InMemoryRepository client; + private ObjectId obj1; + private ObjectId obj2; + private ObjectId obj3; + private String refName = "refs/tags/blob"; + + @Before + public void setUp() throws Exception { + server = newRepo("server"); + client = newRepo("client"); + testProtocol = new TestProtocol<>( + null, + new ReceivePackFactory() { + @Override + public ReceivePack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + return new ReceivePack(db); + } + }); + uri = testProtocol.register(ctx, server); + + try (ObjectInserter ins = server.newObjectInserter()) { + obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test")); + obj3 = ins.insert(Constants.OBJ_BLOB, Constants.encode("not")); + ins.flush(); + + RefUpdate u = server.updateRef(refName); + u.setNewObjectId(obj1); + assertEquals(RefUpdate.Result.NEW, u.update()); + } + + try (ObjectInserter ins = client.newObjectInserter()) { + obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file")); + ins.flush(); + } + } + + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + @Test + public void testWrongOldIdDoesNotReplace() throws IOException { + RemoteRefUpdate rru = new RemoteRefUpdate(null, null, obj2, refName, + false, null, obj3); + + Map updates = new HashMap<>(); + updates.put(rru.getRemoteName(), rru); + + try (Transport tn = testProtocol.open(uri, client, "server"); + PushConnection connection = tn.openPush()) { + connection.push(NullProgressMonitor.INSTANCE, updates); + } + + assertEquals(REJECTED_OTHER_REASON, rru.getStatus()); + assertEquals("invalid old id sent", rru.getMessage()); + } + + @Test + public void invalidCommand() throws IOException { + try (Transport tn = testProtocol.open(uri, client, "server"); + InternalPushConnection c = (InternalPushConnection) tn.openPush()) { + StringWriter msgs = new StringWriter(); + PacketLineOut pckOut = c.pckOut; + + @SuppressWarnings("resource") + SideBandInputStream in = new SideBandInputStream(c.in, + NullProgressMonitor.INSTANCE, msgs, null); + + // Explicitly invalid command, but sane enough capabilities. + StringBuilder buf = new StringBuilder(); + buf.append("42"); + buf.append(' '); + buf.append(obj2.name()); + buf.append(' '); + buf.append("refs/heads/A" + obj2.name()); + buf.append('\0').append(CAPABILITY_SIDE_BAND_64K); + buf.append(' ').append(CAPABILITY_REPORT_STATUS); + buf.append('\n'); + pckOut.writeString(buf.toString()); + pckOut.end(); + + try { + in.read(); + fail("expected TransportException"); + } catch (TransportException e) { + assertEquals( + "remote: error: invalid protocol: wanted 'old new ref'", + e.getMessage()); + } + } + } + + @Test + public void limitCommandBytes() throws IOException { + Map updates = new HashMap<>(); + for (int i = 0; i < 4; i++) { + RemoteRefUpdate rru = new RemoteRefUpdate( + null, null, obj2, "refs/test/T" + i, + false, null, ObjectId.zeroId()); + updates.put(rru.getRemoteName(), rru); + } + + server.getConfig().setInt("receive", null, "maxCommandBytes", 195); + try (Transport tn = testProtocol.open(uri, client, "server"); + PushConnection connection = tn.openPush()) { + try { + connection.push(NullProgressMonitor.INSTANCE, updates); + fail("server did not abort"); + } catch (TransportException e) { + String msg = e.getMessage(); + assertEquals("remote: Too many commands", msg); + } + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2016, 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.PushCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class PushOptionsTest extends RepositoryTestCase { + private URIish uri; + private TestProtocol testProtocol; + private Object ctx = new Object(); + private InMemoryRepository server; + private InMemoryRepository client; + private ObjectId obj1; + private ObjectId obj2; + private ReceivePack receivePack; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + server = newRepo("server"); + client = newRepo("client"); + + testProtocol = new TestProtocol<>(null, + new ReceivePackFactory() { + @Override + public ReceivePack create(Object req, Repository git) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + receivePack = new ReceivePack(git); + receivePack.setAllowPushOptions(true); + receivePack.setAtomic(true); + return receivePack; + } + }); + + uri = testProtocol.register(ctx, server); + + try (ObjectInserter ins = client.newObjectInserter()) { + obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test")); + obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file")); + ins.flush(); + } + } + + @Override + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + private List commands(boolean atomicSafe) + throws IOException { + List cmds = new ArrayList<>(); + cmds.add(new RemoteRefUpdate(null, null, obj1, "refs/heads/one", + true /* force update */, null /* no local tracking ref */, + ObjectId.zeroId())); + cmds.add(new RemoteRefUpdate(null, null, obj2, "refs/heads/two", + true /* force update */, null /* no local tracking ref */, + atomicSafe ? ObjectId.zeroId() : obj1)); + return cmds; + } + + private void connectLocalToRemote(Git local, Git remote) + throws URISyntaxException, IOException { + StoredConfig config = local.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + remoteConfig.addURI(new URIish( + remote.getRepository().getDirectory().toURI().toURL())); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + } + + private RevCommit addCommit(Git git) + throws IOException, NoFilepatternException, GitAPIException { + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + return git.commit().setMessage("adding f").call(); + } + + @Test + public void testNonAtomicPushWithOptions() throws Exception { + PushResult r; + server.setPerformsAtomicTransactions(false); + List pushOptions = Arrays.asList("Hello", "World!"); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(false); + tn.setPushOptions(pushOptions); + + r = tn.push(NullProgressMonitor.INSTANCE, commands(false)); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + + assertSame(RemoteRefUpdate.Status.OK, one.getStatus()); + assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + assertEquals(pushOptions, receivePack.getPushOptions()); + } + + @Test + public void testAtomicPushWithOptions() throws Exception { + PushResult r; + server.setPerformsAtomicTransactions(true); + List pushOptions = Arrays.asList("Hello", "World!"); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(true); + tn.setPushOptions(pushOptions); + + r = tn.push(NullProgressMonitor.INSTANCE, commands(true)); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + + assertSame(RemoteRefUpdate.Status.OK, one.getStatus()); + assertSame(RemoteRefUpdate.Status.OK, two.getStatus()); + assertEquals(pushOptions, receivePack.getPushOptions()); + } + + @Test + public void testFailedAtomicPushWithOptions() throws Exception { + PushResult r; + server.setPerformsAtomicTransactions(true); + List pushOptions = Arrays.asList("Hello", "World!"); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushAtomic(true); + tn.setPushOptions(pushOptions); + + r = tn.push(NullProgressMonitor.INSTANCE, commands(false)); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + + assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON, + one.getStatus()); + assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + assertNull(receivePack.getPushOptions()); + } + + @Test + public void testThinPushWithOptions() throws Exception { + PushResult r; + List pushOptions = Arrays.asList("Hello", "World!"); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.setPushThin(true); + tn.setPushOptions(pushOptions); + + r = tn.push(NullProgressMonitor.INSTANCE, commands(false)); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + + assertSame(RemoteRefUpdate.Status.OK, one.getStatus()); + assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + assertEquals(pushOptions, receivePack.getPushOptions()); + } + + @Test + public void testPushWithoutOptions() throws Exception { + try (Git local = new Git(db); + Git remote = new Git(createBareRepository())) { + connectLocalToRemote(local, remote); + + final StoredConfig config2 = remote.getRepository().getConfig(); + config2.setBoolean("receive", null, "pushoptions", true); + config2.save(); + + RevCommit commit = addCommit(local); + + local.checkout().setName("not-pushed").setCreateBranch(true).call(); + local.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertNull(remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + + PushCommand pushCommand = local.push().setRemote("test"); + pushCommand.call(); + + assertEquals(commit.getId(), + remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + } + } + + @Test + public void testPushWithEmptyOptions() throws Exception { + try (Git local = new Git(db); + Git remote = new Git(createBareRepository())) { + connectLocalToRemote(local, remote); + + final StoredConfig config2 = remote.getRepository().getConfig(); + config2.setBoolean("receive", null, "pushoptions", true); + config2.save(); + + RevCommit commit = addCommit(local); + + local.checkout().setName("not-pushed").setCreateBranch(true).call(); + local.checkout().setName("branchtopush").setCreateBranch(true).call(); + assertNull(remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + + List pushOptions = new ArrayList<>(); + PushCommand pushCommand = local.push().setRemote("test") + .setPushOptions(pushOptions); + pushCommand.call(); + + assertEquals(commit.getId(), + remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + } + } + + @Test + public void testAdvertisedButUnusedPushOptions() throws Exception { + try (Git local = new Git(db); + Git remote = new Git(createBareRepository())) { + connectLocalToRemote(local, remote); + + final StoredConfig config2 = remote.getRepository().getConfig(); + config2.setBoolean("receive", null, "pushoptions", true); + config2.save(); + + RevCommit commit = addCommit(local); + + local.checkout().setName("not-pushed").setCreateBranch(true).call(); + local.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertNull(remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + + PushCommand pushCommand = local.push().setRemote("test") + .setPushOptions(null); + pushCommand.call(); + + assertEquals(commit.getId(), + remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + } + } + + @Test(expected = TransportException.class) + public void testPushOptionsNotSupported() throws Exception { + try (Git local = new Git(db); + Git remote = new Git(createBareRepository())) { + connectLocalToRemote(local, remote); + + final StoredConfig config2 = remote.getRepository().getConfig(); + config2.setBoolean("receive", null, "pushoptions", false); + config2.save(); + + addCommit(local); + + local.checkout().setName("not-pushed").setCreateBranch(true).call(); + local.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertNull(remote.getRepository().resolve("refs/heads/branchtopush")); + assertNull(remote.getRepository().resolve("refs/heads/not-pushed")); + assertNull(remote.getRepository().resolve("refs/heads/master")); + + List pushOptions = new ArrayList<>(); + PushCommand pushCommand = local.push().setRemote("test") + .setPushOptions(pushOptions); + pushCommand.call(); + + fail("should already have thrown TransportException"); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -83,8 +83,8 @@ public void setUp() throws Exception { super.setUp(); transport = new MockTransport(db, new URIish()); - refUpdates = new HashSet(); - advertisedRefs = new HashSet(); + refUpdates = new HashSet<>(); + advertisedRefs = new HashSet<>(); connectionUpdateStatus = Status.OK; } @@ -421,7 +421,7 @@ private class MockPushConnection extends BaseConnection implements PushConnection { MockPushConnection() { - final Map refsMap = new HashMap(); + final Map refsMap = new HashMap<>(); for (final Ref r : advertisedRefs) refsMap.put(r.getName(), r); available(refsMap); @@ -432,12 +432,14 @@ // nothing here } + @Override public void push(ProgressMonitor monitor, Map refsToUpdate, OutputStream out) throws TransportException { push(monitor, refsToUpdate); } + @Override public void push(ProgressMonitor monitor, Map refsToUpdate) throws TransportException { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,6 +58,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.Deflater; import org.eclipse.jgit.errors.MissingObjectException; @@ -108,7 +110,7 @@ // Fill dst with a some common history. // - TestRepository d = new TestRepository(dst); + TestRepository d = new TestRepository<>(dst); a = d.blob("a"); A = d.commit(d.tree(d.file("a", a))); B = d.commit().parent(A).create(); @@ -116,12 +118,9 @@ // Clone from dst into src // - Transport t = Transport.open(src, uriOf(dst)); - try { + try (Transport t = Transport.open(src, uriOf(dst))) { t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*"))); assertEquals(B, src.resolve(R_MASTER)); - } finally { - t.close(); } // Now put private stuff into dst. @@ -131,20 +130,11 @@ d.update(R_PRIVATE, P); } - @Override - @After - public void tearDown() throws Exception { - if (src != null) - src.close(); - if (dst != null) - dst.close(); - super.tearDown(); - } - @Test public void testFilterHidesPrivate() throws Exception { Map refs; - TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { + try (TransportLocal t = new TransportLocal(src, uriOf(dst), + dst.getDirectory()) { @Override ReceivePack createReceivePack(final Repository db) { db.close(); @@ -154,16 +144,10 @@ rp.setAdvertiseRefsHook(new HidePrivateHook()); return rp; } - }; - try { - PushConnection c = t.openPush(); - try { + }) { + try (PushConnection c = t.openPush()) { refs = c.getRefsMap(); - } finally { - c.close(); } - } finally { - t.close(); } assertNotNull(refs); @@ -177,6 +161,45 @@ } @Test + public void resetsHaves() throws Exception { + AtomicReference> haves = new AtomicReference<>(); + try (TransportLocal t = new TransportLocal(src, uriOf(dst), + dst.getDirectory()) { + @Override + ReceivePack createReceivePack(Repository db) { + dst.incrementOpen(); + + ReceivePack rp = super.createReceivePack(dst); + rp.setAdvertiseRefsHook(new AdvertiseRefsHook() { + @Override + public void advertiseRefs(BaseReceivePack rp2) + throws ServiceMayNotContinueException { + rp.setAdvertisedRefs(rp.getRepository().getAllRefs(), + null); + new HidePrivateHook().advertiseRefs(rp); + haves.set(rp.getAdvertisedObjects()); + } + + @Override + public void advertiseRefs(UploadPack uploadPack) + throws ServiceMayNotContinueException { + throw new UnsupportedOperationException(); + } + }); + return rp; + } + }) { + try (PushConnection c = t.openPush()) { + // Just has to open/close for advertisement. + } + } + + assertEquals(1, haves.get().size()); + assertTrue(haves.get().contains(B)); + assertFalse(haves.get().contains(P)); + } + + @Test public void testSuccess() throws Exception { // Manually force a delta of an object so we reuse it later. // @@ -201,7 +224,7 @@ // Now use b but in a different commit than what is hidden. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository<>(src); RevCommit N = s.commit().parent(B).add("q", b).create(); s.update(R_MASTER, N); @@ -292,7 +315,7 @@ @Test public void testUsingHiddenDeltaBaseFails() throws Exception { byte[] delta = { 0x1, 0x1, 0x1, 'c' }; - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository<>(src); RevCommit N = s.commit().parent(B).add("q", s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta))) .create(); @@ -345,7 +368,7 @@ public void testUsingHiddenCommonBlobFails() throws Exception { // Try to use the 'b' blob that is hidden. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository<>(src); RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); // But don't include it in the pack. @@ -395,7 +418,7 @@ public void testUsingUnknownBlobFails() throws Exception { // Try to use the 'n' blob that is not on the server. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository<>(src); RevBlob n = s.blob("n"); RevCommit N = s.commit().parent(B).add("q", n).create(); @@ -443,8 +466,73 @@ } @Test + public void testIncludesInvalidGitmodules() throws Exception { + final TemporaryBuffer.Heap inBuf = setupSourceRepoInvalidGitmodules(); + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof IOException); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + String errorLine = r.readString(); + System.out.println(errorLine); + assertTrue(errorLine.startsWith( + "unpack error Invalid submodule URL '-")); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + private TemporaryBuffer.Heap setupSourceRepoInvalidGitmodules() + throws IOException, Exception, MissingObjectException { + String fakeGitmodules = new StringBuilder() + .append("[submodule \"test\"]\n") + .append(" path = xlib\n") + .append(" url = https://example.com/repo/xlib.git\n\n") + .append("[submodule \"test2\"]\n") + .append(" path = zlib\n") + .append(" url = -upayload.sh\n") + .toString(); + + TestRepository s = new TestRepository<>(src); + RevBlob blob = s.blob(fakeGitmodules); + RevCommit N = s.commit().parent(B) + .add(".gitmodules", blob).create(); + RevTree t = s.parseBody(N).getTree(); + + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 3); + copy(pack, src.open(N)); + copy(pack, src.open(t)); + copy(pack, src.open(blob)); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + return inBuf; + } + + @Test public void testUsingUnknownTreeFails() throws Exception { - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository<>(src); RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); RevTree t = s.parseBody(N).getTree(); @@ -564,8 +652,9 @@ } private static final class HidePrivateHook extends AbstractAdvertiseRefsHook { + @Override public Map getAdvertisedRefs(Repository r, RevWalk revWalk) { - Map refs = new HashMap(r.getAllRefs()); + Map refs = new HashMap<>(r.getAllRefs()); assertNotNull(refs.remove(R_PRIVATE)); return refs; } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016, 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; +import org.eclipse.jgit.util.NB; +import org.junit.Test; + +public class RefAdvertiserTest { + @Test + public void advertiser() throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PacketLineOut pckOut = new PacketLineOut(buf); + PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut); + + // Advertisement of capability and id both happen in order of call, + // which may not match Git standards. Callers are responsible for + // making calls in the correct ordering. Here in this test we do them + // in a "wrong" order to assert the method just writes to the network. + + adv.advertiseCapability("test-1"); + adv.advertiseCapability("sideband"); + adv.advertiseId(id(1), "refs/heads/master"); + adv.advertiseId(id(4), "refs/" + padStart("F", 987)); + adv.advertiseId(id(2), "refs/heads/next"); + adv.advertiseId(id(3), "refs/Iñtërnâtiônàlizætiøn☃💩"); + adv.end(); + assertFalse(adv.isEmpty()); + + PacketLineIn pckIn = new PacketLineIn( + new ByteArrayInputStream(buf.toByteArray())); + String s = pckIn.readStringRaw(); + assertEquals(id(1).name() + " refs/heads/master\0 test-1 sideband\n", + s); + + s = pckIn.readStringRaw(); + assertEquals(id(4).name() + " refs/" + padStart("F", 987) + '\n', s); + + s = pckIn.readStringRaw(); + assertEquals(id(2).name() + " refs/heads/next\n", s); + + s = pckIn.readStringRaw(); + assertEquals(id(3).name() + " refs/Iñtërnâtiônàlizætiøn☃💩\n", s); + + s = pckIn.readStringRaw(); + assertSame(PacketLineIn.END, s); + } + + private static ObjectId id(int i) { + try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) { + byte[] tmp = new byte[4]; + NB.encodeInt32(tmp, 0, i); + return f.idFor(Constants.OBJ_BLOB, tmp); + } + } + + private static String padStart(String s, int len) { + StringBuilder b = new StringBuilder(len); + for (int i = s.length(); i < len; i++) { + b.append((char) ('a' + (i % 26))); + } + return b.append(s).toString(); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,6 +55,7 @@ import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.RefSpec.WildcardMode; import org.junit.Test; public class RefSpecTest { @@ -341,6 +342,41 @@ } @Test + public void testWildcardAfterText1() { + RefSpec a = new RefSpec("refs/heads/*/for-linus:refs/remotes/mine/*-blah"); + assertTrue(a.isWildcard()); + assertTrue(a.matchDestination("refs/remotes/mine/x-blah")); + assertTrue(a.matchDestination("refs/remotes/mine/foo-blah")); + assertTrue(a.matchDestination("refs/remotes/mine/foo/x-blah")); + assertFalse(a.matchDestination("refs/remotes/origin/foo/x-blah")); + + RefSpec b = a.expandFromSource("refs/heads/foo/for-linus"); + assertEquals("refs/remotes/mine/foo-blah", b.getDestination()); + RefSpec c = a.expandFromDestination("refs/remotes/mine/foo-blah"); + assertEquals("refs/heads/foo/for-linus", c.getSource()); + } + + @Test + public void testWildcardAfterText2() { + RefSpec a = new RefSpec("refs/heads*/for-linus:refs/remotes/mine/*"); + assertTrue(a.isWildcard()); + assertTrue(a.matchSource("refs/headsx/for-linus")); + assertTrue(a.matchSource("refs/headsfoo/for-linus")); + assertTrue(a.matchSource("refs/headsx/foo/for-linus")); + assertFalse(a.matchSource("refs/headx/for-linus")); + + RefSpec b = a.expandFromSource("refs/headsx/for-linus"); + assertEquals("refs/remotes/mine/x", b.getDestination()); + RefSpec c = a.expandFromDestination("refs/remotes/mine/x"); + assertEquals("refs/headsx/for-linus", c.getSource()); + + RefSpec d = a.expandFromSource("refs/headsx/foo/for-linus"); + assertEquals("refs/remotes/mine/x/foo", d.getDestination()); + RefSpec e = a.expandFromDestination("refs/remotes/mine/x/foo"); + assertEquals("refs/headsx/foo/for-linus", e.getSource()); + } + + @Test public void testWildcardMirror() { RefSpec a = new RefSpec("*:*"); assertTrue(a.isWildcard()); @@ -374,6 +410,16 @@ } @Test(expected = IllegalArgumentException.class) + public void invalidWhenSourceEndsWithSlash() { + assertNotNull(new RefSpec("refs/heads/")); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidWhenDestinationEndsWithSlash() { + assertNotNull(new RefSpec("refs/heads/master:refs/heads/")); + } + + @Test(expected = IllegalArgumentException.class) public void invalidWhenSourceOnlyAndWildcard() { assertNotNull(new RefSpec("refs/heads/*")); } @@ -404,21 +450,6 @@ } @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardAfterText() { - assertNotNull(new RefSpec("refs/heads/wrong*:refs/heads/right/*")); - } - - @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardBeforeText() { - assertNotNull(new RefSpec("*wrong:right/*")); - } - - @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardBeforeTextAtEnd() { - assertNotNull(new RefSpec("refs/heads/*wrong:right/*")); - } - - @Test(expected = IllegalArgumentException.class) public void invalidSourceDoubleSlashes() { assertNotNull(new RefSpec("refs/heads//wrong")); } @@ -444,4 +475,28 @@ RefSpec a = new RefSpec("refs/heads/*:refs/remotes/origin/*"); a.setDestination("refs/remotes/origin/*/*"); } + + @Test + public void sourceOnlywithWildcard() { + RefSpec a = new RefSpec("refs/heads/*", + WildcardMode.ALLOW_MISMATCH); + assertTrue(a.matchSource("refs/heads/master")); + assertNull(a.getDestination()); + } + + @Test + public void destinationWithWildcard() { + RefSpec a = new RefSpec("refs/heads/master:refs/heads/*", + WildcardMode.ALLOW_MISMATCH); + assertTrue(a.matchSource("refs/heads/master")); + assertTrue(a.matchDestination("refs/heads/master")); + assertTrue(a.matchDestination("refs/heads/foo")); + } + + @Test + public void onlyWildCard() { + RefSpec a = new RefSpec("*", WildcardMode.ALLOW_MISMATCH); + assertTrue(a.matchSource("refs/heads/master")); + assertNull(a.getDestination()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,6 +51,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -498,19 +499,30 @@ } @Test - public void singlePushInsteadOf() throws Exception { + public void pushInsteadOfNotAppliedToPushUri() throws Exception { config.setString("remote", "origin", "pushurl", "short:project.git"); config.setString("url", "https://server/repos/", "pushInsteadOf", "short:"); RemoteConfig rc = new RemoteConfig(config, "origin"); assertFalse(rc.getPushURIs().isEmpty()); - assertEquals("https://server/repos/project.git", rc.getPushURIs() - .get(0).toASCIIString()); + assertEquals("short:project.git", + rc.getPushURIs().get(0).toASCIIString()); + } + + @Test + public void pushInsteadOfAppliedToUri() throws Exception { + config.setString("remote", "origin", "url", "short:project.git"); + config.setString("url", "https://server/repos/", "pushInsteadOf", + "short:"); + RemoteConfig rc = new RemoteConfig(config, "origin"); + assertFalse(rc.getPushURIs().isEmpty()); + assertEquals("https://server/repos/project.git", + rc.getPushURIs().get(0).toASCIIString()); } @Test public void multiplePushInsteadOf() throws Exception { - config.setString("remote", "origin", "pushurl", "prefixproject.git"); + config.setString("remote", "origin", "url", "prefixproject.git"); config.setStringList("url", "https://server/repos/", "pushInsteadOf", Arrays.asList("pre", "prefix", "pref", "perf")); RemoteConfig rc = new RemoteConfig(config, "origin"); @@ -518,4 +530,17 @@ assertEquals("https://server/repos/project.git", rc.getPushURIs() .get(0).toASCIIString()); } + + @Test + public void pushInsteadOfNoPushUrl() throws Exception { + config.setString("remote", "origin", "url", + "http://git.eclipse.org/gitroot/jgit/jgit"); + config.setStringList("url", "ssh://someone@git.eclipse.org:29418/", + "pushInsteadOf", + Collections.singletonList("http://git.eclipse.org/gitroot/")); + RemoteConfig rc = new RemoteConfig(config, "origin"); + assertFalse(rc.getPushURIs().isEmpty()); + assertEquals("ssh://someone@git.eclipse.org:29418/jgit/jgit", + rc.getPushURIs().get(0).toASCIIString()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,6 @@ package org.eclipse.jgit.transport; import static java.lang.Integer.valueOf; -import static java.lang.Long.valueOf; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; @@ -195,24 +194,31 @@ assertEquals(1, flushCnt[0]); } + private void createSideBandOutputStream(int chan, int sz, OutputStream os) + throws Exception { + try (SideBandOutputStream s = new SideBandOutputStream(chan, sz, os)) { + // Unused + } + } + @Test - public void testConstructor_RejectsBadChannel() { + public void testConstructor_RejectsBadChannel() throws Exception { try { - new SideBandOutputStream(-1, MAX_BUF, rawOut); + createSideBandOutputStream(-1, MAX_BUF, rawOut); fail("Accepted -1 channel number"); } catch (IllegalArgumentException e) { assertEquals("channel -1 must be in range [1, 255]", e.getMessage()); } try { - new SideBandOutputStream(0, MAX_BUF, rawOut); + createSideBandOutputStream(0, MAX_BUF, rawOut); fail("Accepted 0 channel number"); } catch (IllegalArgumentException e) { assertEquals("channel 0 must be in range [1, 255]", e.getMessage()); } try { - new SideBandOutputStream(256, MAX_BUF, rawOut); + createSideBandOutputStream(256, MAX_BUF, rawOut); fail("Accepted 256 channel number"); } catch (IllegalArgumentException e) { assertEquals("channel 256 must be in range [1, 255]", e @@ -221,30 +227,30 @@ } @Test - public void testConstructor_RejectsBadBufferSize() { + public void testConstructor_RejectsBadBufferSize() throws Exception { try { - new SideBandOutputStream(CH_DATA, -1, rawOut); + createSideBandOutputStream(CH_DATA, -1, rawOut); fail("Accepted -1 for buffer size"); } catch (IllegalArgumentException e) { assertEquals("packet size -1 must be >= 5", e.getMessage()); } try { - new SideBandOutputStream(CH_DATA, 0, rawOut); + createSideBandOutputStream(CH_DATA, 0, rawOut); fail("Accepted 0 for buffer size"); } catch (IllegalArgumentException e) { assertEquals("packet size 0 must be >= 5", e.getMessage()); } try { - new SideBandOutputStream(CH_DATA, 1, rawOut); + createSideBandOutputStream(CH_DATA, 1, rawOut); fail("Accepted 1 for buffer size"); } catch (IllegalArgumentException e) { assertEquals("packet size 1 must be >= 5", e.getMessage()); } try { - new SideBandOutputStream(CH_DATA, Integer.MAX_VALUE, rawOut); + createSideBandOutputStream(CH_DATA, Integer.MAX_VALUE, rawOut); fail("Accepted " + Integer.MAX_VALUE + " for buffer size"); } catch (IllegalArgumentException e) { assertEquals(MessageFormat.format( diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java 2019-09-03 12:37:49.000000000 +0000 @@ -64,14 +64,17 @@ */ public static final TransportProtocol PROTO = new TransportProtocol() { + @Override public String getName() { return "Test SPI Transport Protocol"; } + @Override public Set getSchemes() { return Collections.singleton(SCHEME); } + @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException { throw new NotSupportedException("not supported"); @@ -82,16 +85,19 @@ super(local, uri); } + @Override public FetchConnection openFetch() throws NotSupportedException, TransportException { throw new NotSupportedException("not supported"); } + @Override public PushConnection openPush() throws NotSupportedException, TransportException { throw new NotSupportedException("not supported"); } + @Override public void close() { // Intentionally left blank } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,6 +60,9 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackStatistics; +import org.eclipse.jgit.transport.BasePackFetchConnection.FetchConfig; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; @@ -70,6 +73,11 @@ public class TestProtocolTest { private static final RefSpec HEADS = new RefSpec("+refs/heads/*:refs/heads/*"); + private static final RefSpec MASTER = new RefSpec( + "+refs/heads/master:refs/heads/master"); + + private static final int HAVES_PER_ROUND = 32; + private static class User { private final String name; @@ -81,7 +89,14 @@ private static class DefaultUpload implements UploadPackFactory { @Override public UploadPack create(User req, Repository db) { - return new UploadPack(db); + UploadPack up = new UploadPack(db); + up.setPostUploadHook(new PostUploadHook() { + @Override + public void onPostUpload(PackStatistics stats) { + havesCount = stats.getHaves(); + } + }); + return up; } } @@ -92,16 +107,18 @@ } } + private static long havesCount; + private List protos; private TestRepository local; private TestRepository remote; @Before public void setUp() throws Exception { - protos = new ArrayList(); - local = new TestRepository( + protos = new ArrayList<>(); + local = new TestRepository<>( new InMemoryRepository(new DfsRepositoryDescription("local"))); - remote = new TestRepository( + remote = new TestRepository<>( new InMemoryRepository(new DfsRepositoryDescription("remote"))); } @@ -125,7 +142,7 @@ .setRefSpecs(HEADS) .call(); assertEquals(master, - local.getRepository().getRef("master").getObjectId()); + local.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -142,7 +159,69 @@ .setRefSpecs(HEADS) .call(); assertEquals(master, - remote.getRepository().getRef("master").getObjectId()); + remote.getRepository().exactRef("refs/heads/master").getObjectId()); + } + } + + @Test + public void testFullNegotiation() throws Exception { + TestProtocol proto = registerDefault(); + URIish uri = proto.register(new User("user"), remote.getRepository()); + + // Enough local branches to cause 10 rounds of negotiation, + // and a unique remote master branch commit with a later timestamp. + for (int i = 0; i < 10 * HAVES_PER_ROUND; i++) { + local.branch("local-branch-" + i).commit().create(); + } + remote.tick(11 * HAVES_PER_ROUND); + RevCommit master = remote.branch("master").commit() + .add("readme.txt", "unique commit").create(); + + try (Git git = new Git(local.getRepository())) { + assertNull(local.getRepository().exactRef("refs/heads/master")); + git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call(); + assertEquals(master, local.getRepository() + .exactRef("refs/heads/master").getObjectId()); + assertEquals(10 * HAVES_PER_ROUND, havesCount); + } + } + + @Test + public void testMinimalNegotiation() throws Exception { + TestProtocol proto = registerDefault(); + URIish uri = proto.register(new User("user"), remote.getRepository()); + + // Enough local branches to cause 10 rounds of negotiation, + // and a unique remote master branch commit with a later timestamp. + for (int i = 0; i < 10 * HAVES_PER_ROUND; i++) { + local.branch("local-branch-" + i).commit().create(); + } + remote.tick(11 * HAVES_PER_ROUND); + RevCommit master = remote.branch("master").commit() + .add("readme.txt", "unique commit").create(); + + TestProtocol.setFetchConfig(new FetchConfig(true, true)); + try (Git git = new Git(local.getRepository())) { + assertNull(local.getRepository().exactRef("refs/heads/master")); + git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call(); + assertEquals(master, local.getRepository() + .exactRef("refs/heads/master").getObjectId()); + assertTrue(havesCount <= 2 * HAVES_PER_ROUND); + + // Update the remote master and add local branches for 5 more rounds + // of negotiation, where the local branch commits have newer + // timestamps. Negotiation should send 5 rounds for those newer + // branches before getting to the round that sends its stale version + // of master. + master = remote.branch("master").commit().parent(master).create(); + local.tick(2 * HAVES_PER_ROUND); + for (int i = 0; i < 5 * HAVES_PER_ROUND; i++) { + local.branch("local-" + i).commit().create(); + } + git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call(); + assertEquals(master, local.getRepository() + .exactRef("refs/heads/master").getObjectId()); + assertEquals(6 * HAVES_PER_ROUND, havesCount); } } @@ -171,21 +250,21 @@ try { git.fetch() .setRemote(user1Uri.toString()) - .setRefSpecs(HEADS) + .setRefSpecs(MASTER) .call(); } catch (InvalidRemoteException expected) { // Expected. } assertEquals(1, rejected.get()); - assertNull(local.getRepository().getRef("master")); + assertNull(local.getRepository().exactRef("refs/heads/master")); git.fetch() .setRemote(user2Uri.toString()) - .setRefSpecs(HEADS) + .setRefSpecs(MASTER) .call(); assertEquals(1, rejected.get()); assertEquals(master, - local.getRepository().getRef("master").getObjectId()); + local.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -222,7 +301,7 @@ JGitText.get().pushNotPermitted)); } assertEquals(1, rejected.get()); - assertNull(remote.getRepository().getRef("master")); + assertNull(remote.getRepository().exactRef("refs/heads/master")); git.push() .setRemote(user2Uri.toString()) @@ -230,7 +309,7 @@ .call(); assertEquals(1, rejected.get()); assertEquals(master, - remote.getRepository().getRef("master").getObjectId()); + remote.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -240,7 +319,7 @@ private TestProtocol registerProto(UploadPackFactory upf, ReceivePackFactory rpf) { - TestProtocol proto = new TestProtocol(upf, rpf); + TestProtocol proto = new TestProtocol<>(upf, rpf); protos.add(proto); Transport.register(proto); return proto; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; @@ -61,13 +63,10 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; -import org.junit.After; import org.junit.Before; import org.junit.Test; public class TransportTest extends SampleDataRepositoryTestCase { - private Transport transport; - private RemoteConfig remoteConfig; @Override @@ -77,17 +76,6 @@ final Config config = db.getConfig(); remoteConfig = new RemoteConfig(config, "test"); remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2")); - transport = null; - } - - @Override - @After - public void tearDown() throws Exception { - if (transport != null) { - transport.close(); - transport = null; - } - super.tearDown(); } /** @@ -99,10 +87,11 @@ @Test public void testFindRemoteRefUpdatesNoWildcardNoTracking() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "refs/heads/master:refs/heads/x"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("refs/heads/master:refs/heads/x"))); + } assertEquals(1, result.size()); final RemoteRefUpdate rru = result.iterator().next(); @@ -122,10 +111,11 @@ @Test public void testFindRemoteRefUpdatesNoWildcardNoDestination() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/master"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor( + Collections.nCopies(1, new RefSpec("+refs/heads/master"))); + } assertEquals(1, result.size()); final RemoteRefUpdate rru = result.iterator().next(); @@ -143,10 +133,11 @@ */ @Test public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/*:refs/heads/test/*"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("+refs/heads/*:refs/heads/test/*"))); + } assertEquals(12, result.size()); boolean foundA = false; @@ -171,12 +162,14 @@ */ @Test public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException { - transport = Transport.open(db, remoteConfig); final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b"); final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d"); final Collection specs = Arrays.asList(specA, specC); - final Collection result = transport - .findRemoteRefUpdatesFor(specs); + + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(specs); + } assertEquals(2, result.size()); boolean foundA = false; @@ -202,10 +195,12 @@ public void testFindRemoteRefUpdatesTrackingRef() throws IOException { remoteConfig.addFetchRefSpec(new RefSpec( "refs/heads/*:refs/remotes/test/*")); - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/a:refs/heads/a"))); + + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("+refs/heads/a:refs/heads/a"))); + } assertEquals(1, result.size()); final TrackingRefUpdate tru = result.iterator().next() @@ -216,6 +211,45 @@ assertEquals(ObjectId.zeroId(), tru.getOldObjectId()); } + /** + * Test RefSpec to RemoteRefUpdate conversion with leases. + * + * @throws IOException + */ + @Test + public void testFindRemoteRefUpdatesWithLeases() throws IOException { + final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b"); + final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d"); + final Collection specs = Arrays.asList(specA, specC); + final Map leases = new HashMap<>(); + leases.put("refs/heads/b", + new RefLeaseSpec("refs/heads/b", "refs/heads/c")); + + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(specs, leases); + } + + assertEquals(2, result.size()); + boolean foundA = false; + boolean foundC = false; + for (final RemoteRefUpdate rru : result) { + if ("refs/heads/a".equals(rru.getSrcRef()) + && "refs/heads/b".equals(rru.getRemoteName())) { + foundA = true; + assertEquals(db.exactRef("refs/heads/c").getObjectId(), + rru.getExpectedOldObjectId()); + } + if ("refs/heads/c".equals(rru.getSrcRef()) + && "refs/heads/d".equals(rru.getRemoteName())) { + foundC = true; + assertNull(rru.getExpectedOldObjectId()); + } + } + assertTrue(foundA); + assertTrue(foundC); + } + @Test public void testLocalTransportWithRelativePath() throws Exception { Repository other = createWorkRepository(); @@ -225,20 +259,18 @@ config.addURI(new URIish("../" + otherDir)); // Should not throw NoRemoteRepositoryException - transport = Transport.open(db, config); + Transport.open(db, config).close(); } @Test public void testLocalTransportFetchWithoutLocalRepository() throws Exception { URIish uri = new URIish("file://" + db.getWorkTree().getAbsolutePath()); - transport = Transport.open(uri); - FetchConnection fetchConnection = transport.openFetch(); - try { - Ref head = fetchConnection.getRef(Constants.HEAD); - assertNotNull(head); - } finally { - fetchConnection.close(); + try (Transport transport = Transport.open(uri)) { + try (FetchConnection fetchConnection = transport.openFetch()) { + Ref head = fetchConnection.getRef(Constants.HEAD); + assertNotNull(head); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,173 @@ +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.UploadPack.RequestPolicy; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * Tests for server upload-pack utilities. + */ +public class UploadPackTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private URIish uri; + + private TestProtocol testProtocol; + + private Object ctx = new Object(); + + private InMemoryRepository server; + + private InMemoryRepository client; + + private TestRepository remote; + + @Before + public void setUp() throws Exception { + server = newRepo("server"); + client = newRepo("client"); + + remote = new TestRepository<>(server); + } + + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + private void generateBitmaps(InMemoryRepository repo) throws Exception { + new DfsGarbageCollector(repo).pack(null); + repo.scanForRepoChanges(); + } + + private static TestProtocol generateReachableCommitUploadPackProtocol() { + return new TestProtocol<>( + new UploadPackFactory() { + @Override + public UploadPack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + UploadPack up = new UploadPack(db); + up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT); + return up; + } + }, null); + } + + @Test + public void testFetchParentOfShallowCommit() throws Exception { + RevCommit commit0 = remote.commit().message("0").create(); + RevCommit commit1 = remote.commit().message("1").parent(commit0).create(); + RevCommit tip = remote.commit().message("2").parent(commit1).create(); + remote.update("master", tip); + + testProtocol = new TestProtocol<>( + new UploadPackFactory() { + @Override + public UploadPack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + UploadPack up = new UploadPack(db); + up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT); + // assume client has a shallow commit + up.getRevWalk().assumeShallow( + Collections.singleton(commit1.getId())); + return up; + } + }, null); + uri = testProtocol.register(ctx, server); + + assertFalse(client.hasObject(commit0.toObjectId())); + + // Fetch of the parent of the shallow commit + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(commit0.name()))); + assertTrue(client.hasObject(commit0.toObjectId())); + } + } + + @Test + public void testFetchUnreachableBlobWithBitmap() throws Exception { + RevBlob blob = remote.blob("foo"); + remote.commit(remote.tree(remote.file("foo", blob))); + generateBitmaps(server); + + testProtocol = generateReachableCommitUploadPackProtocol(); + uri = testProtocol.register(ctx, server); + + assertFalse(client.hasObject(blob.toObjectId())); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + blob.name() + " not valid")); + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(blob.name()))); + } + } + + @Test + public void testFetchReachableBlobWithBitmap() throws Exception { + RevBlob blob = remote.blob("foo"); + RevCommit commit = remote.commit(remote.tree(remote.file("foo", blob))); + remote.update("master", commit); + generateBitmaps(server); + + testProtocol = generateReachableCommitUploadPackProtocol(); + uri = testProtocol.register(ctx, server); + + assertFalse(client.hasObject(blob.toObjectId())); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(blob.name()))); + assertTrue(client.hasObject(blob.toObjectId())); + } + } + + @Test + public void testFetchReachableBlobWithoutBitmap() throws Exception { + RevBlob blob = remote.blob("foo"); + RevCommit commit = remote.commit(remote.tree(remote.file("foo", blob))); + remote.update("master", commit); + + testProtocol = generateReachableCommitUploadPackProtocol(); + uri = testProtocol.register(ctx, server); + + assertFalse(client.hasObject(blob.toObjectId())); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + blob.name() + " not valid")); + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(blob.name()))); + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; @@ -64,24 +63,16 @@ private static final String GIT_SCHEME = "git://"; - @Test + @SuppressWarnings("unused") + @Test(expected = URISyntaxException.class) public void shouldRaiseErrorOnEmptyURI() throws Exception { - try { - new URIish(""); - fail("expecting an exception"); - } catch (URISyntaxException e) { - // expected - } + new URIish(""); } - @Test + @SuppressWarnings("unused") + @Test(expected = URISyntaxException.class) public void shouldRaiseErrorOnNullURI() throws Exception { - try { - new URIish((String) null); - fail("expecting an exception"); - } catch (URISyntaxException e) { - // expected - } + new URIish((String) null); } @Test @@ -207,6 +198,75 @@ URIish u = new URIish(str); assertEquals("file", u.getScheme()); assertFalse(u.isRemote()); + assertEquals(null, u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(null, u.getUser()); + assertEquals(null, u.getPass()); + assertEquals("D:/m y", u.getRawPath()); + assertEquals("D:/m y", u.getPath()); + assertEquals("file:///D:/m y", u.toString()); + assertEquals("file:///D:/m%20y", u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testFileProtoWindowsWithHost() throws Exception { + final String str = "file://localhost/D:/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("localhost", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(null, u.getUser()); + assertEquals(null, u.getPass()); + assertEquals("D:/m y", u.getRawPath()); + assertEquals("D:/m y", u.getPath()); + assertEquals("file://localhost/D:/m y", u.toString()); + assertEquals("file://localhost/D:/m%20y", u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testFileProtoWindowsWithHostAndPort() throws Exception { + final String str = "file://localhost:80/D:/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("localhost", u.getHost()); + assertEquals(80, u.getPort()); + assertEquals(null, u.getUser()); + assertEquals(null, u.getPass()); + assertEquals("D:/m y", u.getRawPath()); + assertEquals("D:/m y", u.getPath()); + assertEquals("file://localhost:80/D:/m y", u.toString()); + assertEquals("file://localhost:80/D:/m%20y", u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testFileProtoWindowsWithHostAndEmptyPortIsAmbiguous() + throws Exception { + final String str = "file://localhost:/D:/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); + assertEquals(null, u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(null, u.getUser()); + assertEquals(null, u.getPass()); + assertEquals("localhost:/D:/m y", u.getRawPath()); + assertEquals("localhost:/D:/m y", u.getPath()); + assertEquals("file:///localhost:/D:/m y", u.toString()); + assertEquals("file:///localhost:/D:/m%20y", u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testFileProtoWindowsMissingHostSlash() throws Exception { + final String str = "file://D:/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); assertEquals("D:/m y", u.getRawPath()); assertEquals("D:/m y", u.getPath()); assertEquals("file:///D:/m y", u.toString()); @@ -215,6 +275,19 @@ } @Test + public void testFileProtoWindowsMissingHostSlash2() throws Exception { + final String str = "file://D: /m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); + assertEquals("D: /m y", u.getRawPath()); + assertEquals("D: /m y", u.getPath()); + assertEquals("file:///D: /m y", u.toString()); + assertEquals("file:///D:%20/m%20y", u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test public void testGitProtoUnix() throws Exception { final String str = "git://example.com/home/m y"; URIish u = new URIish(str); @@ -430,6 +503,22 @@ } @Test + public void testSshProtoHostWithEmptyPortAndPath() throws Exception { + final String str = "ssh://example.com:/path"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/path", u.getRawPath()); + assertEquals("/path", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals("ssh://example.com/path", u.toString()); + assertEquals("ssh://example.com/path", u.toASCIIString()); + assertEquals(u, new URIish(str)); + assertEquals(u, new URIish("ssh://example.com/path")); + } + + @Test public void testSshProtoWithUserAndPort() throws Exception { final String str = "ssh://user@example.com:33/some/p ath"; URIish u = new URIish(str); @@ -469,6 +558,48 @@ } @Test + public void testSshProtoWithEmailUserAndPort() throws Exception { + final String str = "ssh://user.name@email.com@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getRawPath()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user.name@email.com", u.getUser()); + assertNull(u.getPass()); + assertEquals(33, u.getPort()); + assertEquals("ssh://user.name%40email.com@example.com:33/some/p ath", + u.toPrivateString()); + assertEquals("ssh://user.name%40email.com@example.com:33/some/p%20ath", + u.toPrivateASCIIString()); + assertEquals(u.setPass(null).toPrivateString(), u.toString()); + assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testSshProtoWithEmailUserPassAndPort() throws Exception { + final String str = "ssh://user.name@email.com:pass@wor:d@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getRawPath()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user.name@email.com", u.getUser()); + assertEquals("pass@wor:d", u.getPass()); + assertEquals(33, u.getPort()); + assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p ath", + u.toPrivateString()); + assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p%20ath", + u.toPrivateASCIIString()); + assertEquals(u.setPass(null).toPrivateString(), u.toString()); + assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test public void testSshProtoWithADUserPassAndPort() throws Exception { final String str = "ssh://DOMAIN\\user:pass@example.com:33/some/p ath"; URIish u = new URIish(str); @@ -592,34 +723,19 @@ assertEquals(u, new URIish(str)); } - @Test + @Test(expected = IllegalArgumentException.class) public void testGetNullHumanishName() { - try { - new URIish().getHumanishName(); - fail("path must be not null"); - } catch (IllegalArgumentException e) { - // expected - } + new URIish().getHumanishName(); } - @Test + @Test(expected = IllegalArgumentException.class) public void testGetEmptyHumanishName() throws URISyntaxException { - try { - new URIish(GIT_SCHEME).getHumanishName(); - fail("empty path is useless"); - } catch (IllegalArgumentException e) { - // expected - } + new URIish(GIT_SCHEME).getHumanishName(); } - @Test + @Test(expected = IllegalArgumentException.class) public void testGetAbsEmptyHumanishName() { - try { - new URIish().getHumanishName(); - fail("empty path is useless"); - } catch (IllegalArgumentException e) { - // expected - } + new URIish().getHumanishName(); } @Test @@ -873,13 +989,6 @@ } @Test - public void testMissingPort() throws URISyntaxException { - final String incorrectSshUrl = "ssh://some-host:/path/to/repository.git"; - URIish u = new URIish(incorrectSshUrl); - assertFalse(TransportGitSsh.PROTO_SSH.canHandle(u)); - } - - @Test public void testALot() throws URISyntaxException { // user pass host port path // 1 2 3 4 5 @@ -928,4 +1037,47 @@ } } } + + @Test + public void testStringConstructor() throws Exception { + String str = "http://example.com/"; + URIish u = new URIish(str); + assertEquals("example.com", u.getHost()); + assertEquals("/", u.getPath()); + assertEquals(str, u.toString()); + + str = "http://example.com"; + u = new URIish(str); + assertEquals("example.com", u.getHost()); + assertEquals("", u.getPath()); + assertEquals(str, u.toString()); + } + + @Test + public void testEqualsHashcode() throws Exception + { + String[] urls = { "http://user:pass@example.com:8081/path", "../x", + "ssh://x.y:23/z", "ssh://example.com:/path", "D:\\m y", + "\\\\some\\place", "http://localhost:1234", + "user@example.com:some/p ath", "a", + "http://user:pwd@example.com:8081/path", + "http://user:pass@another.com:8081/path", + "http://user:pass@example.com:8083/path" }; + URIish w = new URIish("http://user:pass@example.com:8081/path/x"); + for (String s : urls) { + URIish u = new URIish(s); + URIish v = new URIish(s); + assertTrue(u.equals(v)); + assertTrue(v.equals(u)); + + assertFalse(u.equals(null)); + assertFalse(u.equals(new Object())); + assertFalse(new Object().equals(u)); + assertFalse(u.equals(w)); + assertFalse(w.equals(u)); + + assertTrue(u.hashCode() == v.hashCode()); + assertFalse(u.hashCode() == new Object().hashCode()); + } + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,1315 @@ +/* + * Copyright (C) 2015, Andrei Pozolotin. + * 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.transport.WalkEncryptionTest.Util.cryptoCipherListPBE; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.permitLongTests; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.policySetup; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.product; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.proxySetup; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.publicAddress; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.reportPolicy; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.securityProviderName; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.textWrite; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.transferStream; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.verifyFileContent; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.Security; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +import javax.crypto.SecretKeyFactory; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.eclipse.jgit.util.FileUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Suite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Amazon S3 encryption pipeline test. + * + * See {@link AmazonS3} {@link WalkEncryption} + * + * Note: CI server must provide amazon credentials (access key, secret key, + * bucket name) via one of methods available in {@link Names}. + * + * Note: long running tests are activated by Maven profile "test.long". There is + * also a separate Eclipse m2e launcher for that. See 'pom.xml' and + * 'WalkEncryptionTest.launch'. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ // + WalkEncryptionTest.Required.class, // + WalkEncryptionTest.MinimalSet.class, // + WalkEncryptionTest.TestablePBE.class, // + WalkEncryptionTest.TestableTransformation.class, // +}) +public class WalkEncryptionTest { + + /** + * Logger setup: ${project_loc}/tst-rsrc/log4j.properties + */ + static final Logger logger = LoggerFactory.getLogger(WalkEncryptionTest.class); + + /** + * Property names used in test session. + */ + interface Names { + + // Names of discovered test properties. + + String TEST_BUCKET = "test.bucket"; + + // Names of test environment variables for CI. + + String ENV_ACCESS_KEY = "JGIT_S3_ACCESS_KEY"; + + String ENV_SECRET_KEY = "JGIT_S3_SECRET_KEY"; + + String ENV_BUCKET_NAME = "JGIT_S3_BUCKET_NAME"; + + // Name of test environment variable file path for CI. + + String ENV_CONFIG_FILE = "JGIT_S3_CONFIG_FILE"; + + // Names of test system properties for CI. + + String SYS_ACCESS_KEY = "jgit.s3.access.key"; + + String SYS_SECRET_KEY = "jgit.s3.secret.key"; + + String SYS_BUCKET_NAME = "jgit.s3.bucket.name"; + + // Name of test system property file path for CI. + String SYS_CONFIG_FILE = "jgit.s3.config.file"; + + // Hard coded name of test properties file for CI. + // File format follows AmazonS3.Keys: + // # + // # Required entries: + // # + // accesskey = your-amazon-access-key # default AmazonS3.Keys + // secretkey = your-amazon-secret-key # default AmazonS3.Keys + // test.bucket = your-bucket-for-testing # custom name, for this test + String CONFIG_FILE = "jgit-s3-config.properties"; + + // Test properties file in [user home] of CI. + String HOME_CONFIG_FILE = System.getProperty("user.home") + + File.separator + CONFIG_FILE; + + // Test properties file in [project work directory] of CI. + String WORK_CONFIG_FILE = System.getProperty("user.dir") + + File.separator + CONFIG_FILE; + + // Test properties file in [project test source directory] of CI. + String TEST_CONFIG_FILE = System.getProperty("user.dir") + + File.separator + "tst-rsrc" + File.separator + CONFIG_FILE; + + } + + /** + * Find test properties from various sources in order of priority. + */ + static class Props implements WalkEncryptionTest.Names, AmazonS3.Keys { + + static boolean haveEnvVar(String name) { + return System.getenv(name) != null; + } + + static boolean haveEnvVarFile(String name) { + return haveEnvVar(name) && new File(name).exists(); + } + + static boolean haveSysProp(String name) { + return System.getProperty(name) != null; + } + + static boolean haveSysPropFile(String name) { + return haveSysProp(name) && new File(name).exists(); + } + + static void loadEnvVar(String source, String target, Properties props) { + props.put(target, System.getenv(source)); + } + + static void loadSysProp(String source, String target, + Properties props) { + props.put(target, System.getProperty(source)); + } + + static boolean haveProp(String name, Properties props) { + return props.containsKey(name); + } + + static boolean checkTestProps(Properties props) { + return haveProp(ACCESS_KEY, props) && haveProp(SECRET_KEY, props) + && haveProp(TEST_BUCKET, props); + } + + static Properties fromEnvVars() { + if (haveEnvVar(ENV_ACCESS_KEY) && haveEnvVar(ENV_SECRET_KEY) + && haveEnvVar(ENV_BUCKET_NAME)) { + Properties props = new Properties(); + loadEnvVar(ENV_ACCESS_KEY, ACCESS_KEY, props); + loadEnvVar(ENV_SECRET_KEY, SECRET_KEY, props); + loadEnvVar(ENV_BUCKET_NAME, TEST_BUCKET, props); + return props; + } else { + return null; + } + } + + static Properties fromEnvFile() throws Exception { + if (haveEnvVarFile(ENV_CONFIG_FILE)) { + Properties props = new Properties(); + props.load(new FileInputStream(ENV_CONFIG_FILE)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("Environment config file is incomplete."); + } + } else { + return null; + } + } + + static Properties fromSysProps() { + if (haveSysProp(SYS_ACCESS_KEY) && haveSysProp(SYS_SECRET_KEY) + && haveSysProp(SYS_BUCKET_NAME)) { + Properties props = new Properties(); + loadSysProp(SYS_ACCESS_KEY, ACCESS_KEY, props); + loadSysProp(SYS_SECRET_KEY, SECRET_KEY, props); + loadSysProp(SYS_BUCKET_NAME, TEST_BUCKET, props); + return props; + } else { + return null; + } + } + + static Properties fromSysFile() throws Exception { + if (haveSysPropFile(SYS_CONFIG_FILE)) { + Properties props = new Properties(); + props.load(new FileInputStream(SYS_CONFIG_FILE)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("System props config file is incomplete."); + } + } else { + return null; + } + } + + static Properties fromConfigFile(String path) throws Exception { + File file = new File(path); + if (file.exists()) { + Properties props = new Properties(); + props.load(new FileInputStream(file)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("Props config file is incomplete: " + path); + } + } else { + return null; + } + } + + /** + * Find test properties from various sources in order of priority. + * + * @return result + * @throws Exception + */ + static Properties discover() throws Exception { + Properties props; + if ((props = fromEnvVars()) != null) { + logger.debug( + "Using test properties from environment variables."); + return props; + } + if ((props = fromEnvFile()) != null) { + logger.debug( + "Using test properties from environment variable config file."); + return props; + } + if ((props = fromSysProps()) != null) { + logger.debug("Using test properties from system properties."); + return props; + } + if ((props = fromSysFile()) != null) { + logger.debug( + "Using test properties from system property config file."); + return props; + } + if ((props = fromConfigFile(HOME_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${user.home} file."); + return props; + } + if ((props = fromConfigFile(WORK_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${user.dir} file."); + return props; + } + if ((props = fromConfigFile(TEST_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${project.source} file."); + return props; + } + throw new Error("Can not load test properties form any source."); + } + + } + + /** + * Collection of test utility methods. + */ + static class Util { + + /** + * Read UTF-8 encoded text file into string. + * + * @param file + * @return result + * @throws Exception + */ + static String textRead(File file) throws Exception { + return new String(Files.readAllBytes(file.toPath()), UTF_8); + } + + /** + * Write string into UTF-8 encoded file. + * + * @param file + * @param text + * @throws Exception + */ + static void textWrite(File file, String text) throws Exception { + Files.write(file.toPath(), text.getBytes(UTF_8)); + } + + static void verifyFileContent(File fileOne, File fileTwo) + throws Exception { + assertTrue(fileOne.length() > 0); + assertTrue(fileTwo.length() > 0); + String textOne = textRead(fileOne); + String textTwo = textRead(fileTwo); + assertEquals(textOne, textTwo); + } + + /** + * Create local folder. + * + * @param folder + * @throws Exception + */ + static void folderCreate(String folder) throws Exception { + File path = new File(folder); + assertTrue(path.mkdirs()); + } + + /** + * Delete local folder. + * + * @param folder + * @throws Exception + */ + static void folderDelete(String folder) throws Exception { + File path = new File(folder); + FileUtils.delete(path, + FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + } + + /** + * Discover public address of CI server. + * + * @return result + * @throws Exception + */ + static String publicAddress() throws Exception { + try { + String service = "http://checkip.amazonaws.com"; + URL url = new URL(service); + URLConnection c = url.openConnection(); + c.setConnectTimeout(500); + c.setReadTimeout(500); + BufferedReader reader = new BufferedReader( + new InputStreamReader(c.getInputStream())); + try { + return reader.readLine(); + } finally { + reader.close(); + } + } catch (UnknownHostException | SocketTimeoutException e) { + return "Can't reach http://checkip.amazonaws.com to" + + " determine public address"; + } + } + + /** + * Discover Password-Based Encryption (PBE) engines providing both + * [SecretKeyFactory] and [AlgorithmParameters]. + * + * @return result + */ + // https://www.bouncycastle.org/specifications.html + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html + static List cryptoCipherListPBE() { + return cryptoCipherList(WalkEncryption.Vals.REGEX_PBE); + } + + // TODO returns inconsistent list. + static List cryptoCipherListTrans() { + return cryptoCipherList(WalkEncryption.Vals.REGEX_TRANS); + } + + static String securityProviderName(String algorithm) throws Exception { + return SecretKeyFactory.getInstance(algorithm).getProvider() + .getName(); + } + + static List cryptoCipherList(String regex) { + Set source = Security.getAlgorithms("Cipher"); + Set target = new TreeSet<>(); + for (String algo : source) { + algo = algo.toUpperCase(Locale.ROOT); + if (algo.matches(regex)) { + target.add(algo); + } + } + return new ArrayList<>(target); + } + + /** + * Stream copy. + * + * @param from + * @param into + * @return count + * @throws IOException + */ + static long transferStream(InputStream from, OutputStream into) + throws IOException { + byte[] array = new byte[1 * 1024]; + long total = 0; + while (true) { + int count = from.read(array); + if (count == -1) { + break; + } + into.write(array, 0, count); + total += count; + } + return total; + } + + /** + * Setup proxy during CI build. + * + * @throws Exception + */ + // https://wiki.eclipse.org/Hudson#Accessing_the_Internet_using_Proxy + // http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html + static void proxySetup() throws Exception { + String keyNoProxy = "no_proxy"; + String keyHttpProxy = "http_proxy"; + String keyHttpsProxy = "https_proxy"; + + String no_proxy = System.getProperty(keyNoProxy, + System.getenv(keyNoProxy)); + if (no_proxy != null) { + System.setProperty("http.nonProxyHosts", no_proxy); + logger.info("Proxy NOT: " + no_proxy); + } + + String http_proxy = System.getProperty(keyHttpProxy, + System.getenv(keyHttpProxy)); + if (http_proxy != null) { + URL url = new URL(http_proxy); + System.setProperty("http.proxyHost", url.getHost()); + System.setProperty("http.proxyPort", "" + url.getPort()); + logger.info("Proxy HTTP: " + http_proxy); + } + + String https_proxy = System.getProperty(keyHttpsProxy, + System.getenv(keyHttpsProxy)); + if (https_proxy != null) { + URL url = new URL(https_proxy); + System.setProperty("https.proxyHost", url.getHost()); + System.setProperty("https.proxyPort", "" + url.getPort()); + logger.info("Proxy HTTPS: " + https_proxy); + } + + if (no_proxy == null && http_proxy == null && https_proxy == null) { + logger.info("Proxy not used."); + } + + } + + /** + * Permit long tests on CI or with manual activation. + * + * @return result + */ + static boolean permitLongTests() { + return isBuildCI() || isProfileActive(); + } + + /** + * Using Maven profile activation, see pom.xml + * + * @return result + */ + static boolean isProfileActive() { + return Boolean.parseBoolean(System.getProperty("jgit.test.long")); + } + + /** + * Detect if build is running on CI. + * + * @return result + */ + static boolean isBuildCI() { + return System.getenv("HUDSON_HOME") != null; + } + + /** + * Setup JCE security policy restrictions. Can remove restrictions when + * restrictions are present, but can not impose them when restrictions + * are missing. + * + * @param restrictedOn + */ + // http://www.docjar.com/html/api/javax/crypto/JceSecurity.java.html + static void policySetup(boolean restrictedOn) { + try { + java.lang.reflect.Field isRestricted = Class + .forName("javax.crypto.JceSecurity") + .getDeclaredField("isRestricted"); + isRestricted.setAccessible(true); + isRestricted.set(null, Boolean.valueOf(restrictedOn)); + } catch (Throwable e) { + logger.info( + "Could not setup JCE security policy restrictions."); + } + } + + static void reportPolicy() { + try { + java.lang.reflect.Field isRestricted = Class + .forName("javax.crypto.JceSecurity") + .getDeclaredField("isRestricted"); + isRestricted.setAccessible(true); + logger.info("JCE security policy restricted=" + + isRestricted.get(null)); + } catch (Throwable e) { + logger.info( + "Could not report JCE security policy restrictions."); + } + } + + static List product(List one, List two) { + List result = new ArrayList<>(); + for (String s1 : one) { + for (String s2 : two) { + result.add(new Object[] { s1, s2 }); + } + } + return result; + } + + } + + /** + * Common base for encryption tests. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public abstract static class Base extends SampleDataRepositoryTestCase { + + /** + * S3 URI user used by JGIT to discover connection configuration file. + */ + static final String JGIT_USER = "tester-" + System.currentTimeMillis(); + + /** + * S3 content encoding password used for this test session. + */ + static final String JGIT_PASS = "secret-" + System.currentTimeMillis(); + + /** + * S3 repository configuration file expected by {@link AmazonS3}. + */ + static final String JGIT_CONF_FILE = System.getProperty("user.home") + + "/" + JGIT_USER; + + /** + * Name representing remote or local JGIT repository. + */ + static final String JGIT_REPO_DIR = JGIT_USER + ".jgit"; + + /** + * Local JGIT repository for this test session. + */ + static final String JGIT_LOCAL_DIR = System.getProperty("user.dir") + + "/target/" + JGIT_REPO_DIR; + + /** + * Remote JGIT repository for this test session. + */ + static final String JGIT_REMOTE_DIR = JGIT_REPO_DIR; + + /** + * Generate JGIT S3 connection configuration file. + * + * @param algorithm + * @throws Exception + */ + static void configCreate(String algorithm) throws Exception { + Properties props = Props.discover(); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(AmazonS3.Keys.CRYPTO_ALG, algorithm); + PrintWriter writer = new PrintWriter(JGIT_CONF_FILE); + props.store(writer, "JGIT S3 connection configuration file."); + writer.close(); + } + + /** + * Generate JGIT S3 connection configuration file. + * + * @param source + * @throws Exception + */ + static void configCreate(Properties source) throws Exception { + Properties target = Props.discover(); + target.putAll(source); + PrintWriter writer = new PrintWriter(JGIT_CONF_FILE); + target.store(writer, "JGIT S3 connection configuration file."); + writer.close(); + } + + /** + * Remove JGIT connection configuration file. + * + * @throws Exception + */ + static void configDelete() throws Exception { + File path = new File(JGIT_CONF_FILE); + FileUtils.delete(path, FileUtils.SKIP_MISSING); + } + + /** + * Generate remote URI for the test session. + * + * @return result + * @throws Exception + */ + static String amazonURI() throws Exception { + Properties props = Props.discover(); + String bucket = props.getProperty(Names.TEST_BUCKET); + assertNotNull(bucket); + return TransportAmazonS3.S3_SCHEME + "://" + JGIT_USER + "@" + + bucket + "/" + JGIT_REPO_DIR; + } + + /** + * Create S3 repository folder. + * + * @throws Exception + */ + static void remoteCreate() throws Exception { + Properties props = Props.discover(); + props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption. + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + String path = JGIT_REMOTE_DIR + "/"; + s3.put(bucket, path, new byte[0]); + logger.debug("remote create: " + JGIT_REMOTE_DIR); + } + + /** + * Delete S3 repository folder. + * + * @throws Exception + */ + static void remoteDelete() throws Exception { + Properties props = Props.discover(); + props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption. + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + List list = s3.list(bucket, JGIT_REMOTE_DIR); + for (String path : list) { + path = JGIT_REMOTE_DIR + "/" + path; + s3.delete(bucket, path); + } + logger.debug("remote delete: " + JGIT_REMOTE_DIR); + } + + /** + * Verify if we can create/delete remote file. + * + * @throws Exception + */ + static void remoteVerify() throws Exception { + Properties props = Props.discover(); + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + String file = JGIT_USER + "-" + UUID.randomUUID().toString(); + String path = JGIT_REMOTE_DIR + "/" + file; + s3.put(bucket, path, file.getBytes(UTF_8)); + s3.delete(bucket, path); + } + + /** + * Verify if any security provider published the algorithm. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmPresent(String algorithm) { + Set cipherSet = Security.getAlgorithms("Cipher"); + for (String source : cipherSet) { + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + String target = algorithm.toUpperCase(Locale.ROOT); + if (source.equalsIgnoreCase(target)) { + return true; + } + } + return false; + } + + static boolean isAlgorithmPresent(Properties props) { + String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER, + WalkEncryption.Vals.DEFAULT_VERS); + String cryptoAlgo; + String keyAlgo; + switch (version) { + case WalkEncryption.Vals.DEFAULT_VERS: + case WalkEncryption.JGitV1.VERSION: + cryptoAlgo = profile; + keyAlgo = profile; + break; + case WalkEncryption.JGitV2.VERSION: + cryptoAlgo = props + .getProperty(profile + WalkEncryption.Keys.X_ALGO); + keyAlgo = props + .getProperty(profile + WalkEncryption.Keys.X_KEY_ALGO); + break; + default: + return false; + } + try { + InsecureCipherFactory.create(cryptoAlgo); + SecretKeyFactory.getInstance(keyAlgo); + return true; + } catch (Throwable e) { + return false; + } + } + + /** + * Verify if JRE security policy allows the algorithm. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmAllowed(String algorithm) { + try { + WalkEncryption crypto = new WalkEncryption.JetS3tV2( + algorithm, JGIT_PASS); + verifyCrypto(crypto); + return true; + } catch (IOException e) { + return false; // Encryption failure. + } catch (GeneralSecurityException e) { + throw new Error(e); // Construction failure. + } + } + + static boolean isAlgorithmAllowed(Properties props) { + try { + WalkEncryption.instance(props); + return true; + } catch (GeneralSecurityException e) { + return false; + } + } + + /** + * Verify round trip encryption. + * + * @param crypto + * @throws IOException + */ + static void verifyCrypto(WalkEncryption crypto) throws IOException { + String charset = "UTF-8"; + String sourceText = "secret-message Свобода 老子"; + String targetText; + byte[] cipherText; + { + byte[] origin = sourceText.getBytes(charset); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + OutputStream source = crypto.encrypt(target); + source.write(origin); + source.flush(); + source.close(); + cipherText = target.toByteArray(); + } + { + InputStream source = new ByteArrayInputStream(cipherText); + InputStream target = crypto.decrypt(source); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + transferStream(target, result); + targetText = result.toString(charset); + } + assertEquals(sourceText, targetText); + } + + /** + * Algorithm is testable when it is present and allowed by policy. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmTestable(String algorithm) { + return isAlgorithmPresent(algorithm) + && isAlgorithmAllowed(algorithm); + } + + static boolean isAlgorithmTestable(Properties props) { + return isAlgorithmPresent(props) && isAlgorithmAllowed(props); + } + + /** + * Log algorithm, provider, testability. + * + * @param algorithm + * @throws Exception + */ + static void reportAlgorithmStatus(String algorithm) throws Exception { + final boolean present = isAlgorithmPresent(algorithm); + final boolean allowed = present && isAlgorithmAllowed(algorithm); + final String provider = present ? securityProviderName(algorithm) + : "N/A"; + String status = "Algorithm: " + algorithm + " @ " + provider + "; " + + "present/allowed : " + present + "/" + allowed; + if (allowed) { + logger.info("Testing " + status); + } else { + logger.warn("Missing " + status); + } + } + + static void reportAlgorithmStatus(Properties props) throws Exception { + final boolean present = isAlgorithmPresent(props); + final boolean allowed = present && isAlgorithmAllowed(props); + + String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER); + + StringBuilder status = new StringBuilder(); + status.append(" Version: " + version); + status.append(" Profile: " + profile); + status.append(" Present: " + present); + status.append(" Allowed: " + allowed); + + if (allowed) { + logger.info("Testing " + status); + } else { + logger.warn("Missing " + status); + } + } + + /** + * Verify if we can perform remote tests. + * + * @return result + */ + static boolean isTestConfigPresent() { + try { + Props.discover(); + return true; + } catch (Throwable e) { + return false; + } + } + + static void reportTestConfigPresent() { + if (isTestConfigPresent()) { + logger.info("Amazon S3 test configuration is present."); + } else { + logger.error( + "Amazon S3 test configuration is missing, tests will not run."); + } + } + + /** + * Log public address of CI. + * + * @throws Exception + */ + static void reportPublicAddress() throws Exception { + logger.info("Public address: " + publicAddress()); + } + + /** + * BouncyCastle provider class. + * + * Needs extra dependency, see pom.xml + */ + // http://search.maven.org/#artifactdetails%7Corg.bouncycastle%7Cbcprov-jdk15on%7C1.52%7Cjar + static final String PROVIDER_BC = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + + /** + * Load BouncyCastle provider if present. + */ + static void loadBouncyCastle() { + try { + Class provider = Class.forName(PROVIDER_BC); + Provider instance = (Provider) provider + .getConstructor(new Class[] {}) + .newInstance(new Object[] {}); + Security.addProvider(instance); + logger.info("Loaded " + PROVIDER_BC); + } catch (Throwable e) { + logger.warn("Failed to load " + PROVIDER_BC); + } + } + + static void reportLongTests() { + if (permitLongTests()) { + logger.info("Long running tests are enabled."); + } else { + logger.warn("Long running tests are disabled."); + } + } + + /** + * Non-PBE algorithm, for error check. + */ + static final String ALGO_ERROR = "PBKDF2WithHmacSHA1"; + + /** + * Default JetS3t algorithm present in most JRE. + */ + static final String ALGO_JETS3T = "PBEWithMD5AndDES"; + + /** + * Minimal strength AES based algorithm present in most JRE. + */ + static final String ALGO_MINIMAL_AES = "PBEWithHmacSHA1AndAES_128"; + + /** + * Selected non-AES algorithm present in BouncyCastle provider. + */ + static final String ALGO_BOUNCY_CASTLE_CBC = "PBEWithSHAAndTwofish-CBC"; + + ////////////////////////////////////////////////// + + @BeforeClass + public static void initialize() throws Exception { + Transport.register(TransportAmazonS3.PROTO_S3); + proxySetup(); + reportPolicy(); + reportLongTests(); + reportPublicAddress(); + reportTestConfigPresent(); + loadBouncyCastle(); + if (isTestConfigPresent()) { + remoteCreate(); + } + } + + @AfterClass + public static void terminate() throws Exception { + configDelete(); + folderDelete(JGIT_LOCAL_DIR); + if (isTestConfigPresent()) { + remoteDelete(); + } + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @After + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Optional encrypted amazon remote JGIT life cycle test. + * + * @param props + * @throws Exception + */ + void cryptoTestIfCan(Properties props) throws Exception { + reportAlgorithmStatus(props); + assumeTrue(isTestConfigPresent()); + assumeTrue(isAlgorithmTestable(props)); + cryptoTest(props); + } + + /** + * Required encrypted amazon remote JGIT life cycle test. + * + * @param props + * @throws Exception + */ + void cryptoTest(Properties props) throws Exception { + + remoteDelete(); + configCreate(props); + folderDelete(JGIT_LOCAL_DIR); + + String uri = amazonURI(); + + // Local repositories. + File dirOne = db.getWorkTree(); // Provided by setup. + File dirTwo = new File(JGIT_LOCAL_DIR); + + // Local verification files. + String nameStatic = "master.txt"; // Provided by setup. + String nameDynamic = JGIT_USER + "-" + UUID.randomUUID().toString(); + + String remote = "remote"; + RefSpec specs = new RefSpec("refs/heads/master:refs/heads/master"); + + { // Push into remote from local one. + + StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, remote); + remoteConfig.addURI(new URIish(uri)); + remoteConfig.update(config); + config.save(); + + Git git = Git.open(dirOne); + git.checkout().setName("master").call(); + git.push().setRemote(remote).setRefSpecs(specs).call(); + git.close(); + + File fileStatic = new File(dirOne, nameStatic); + assertTrue("Provided by setup", fileStatic.exists()); + + } + + { // Clone from remote into local two. + + File fileStatic = new File(dirTwo, nameStatic); + assertFalse("Not Provided by setup", fileStatic.exists()); + + Git git = Git.cloneRepository().setURI(uri).setDirectory(dirTwo) + .call(); + git.close(); + + assertTrue("Provided by clone", fileStatic.exists()); + } + + { // Verify static file content. + File fileOne = new File(dirOne, nameStatic); + File fileTwo = new File(dirTwo, nameStatic); + verifyFileContent(fileOne, fileTwo); + } + + { // Verify new file commit and push from local one. + + File fileDynamic = new File(dirOne, nameDynamic); + assertFalse("Not Provided by setup", fileDynamic.exists()); + FileUtils.createNewFile(fileDynamic); + textWrite(fileDynamic, nameDynamic); + assertTrue("Provided by create", fileDynamic.exists()); + assertTrue("Need content to encrypt", fileDynamic.length() > 0); + + Git git = Git.open(dirOne); + git.add().addFilepattern(nameDynamic).call(); + git.commit().setMessage(nameDynamic).call(); + git.push().setRemote(remote).setRefSpecs(specs).call(); + git.close(); + + } + + { // Verify new file pull from remote into local two. + + File fileDynamic = new File(dirTwo, nameDynamic); + assertFalse("Not Provided by setup", fileDynamic.exists()); + + Git git = Git.open(dirTwo); + git.pull().call(); + git.close(); + + assertTrue("Provided by pull", fileDynamic.exists()); + } + + { // Verify dynamic file content. + File fileOne = new File(dirOne, nameDynamic); + File fileTwo = new File(dirTwo, nameDynamic); + verifyFileContent(fileOne, fileTwo); + } + + } + + } + + /** + * Verify prerequisites. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class Required extends Base { + + @Test + public void test_A1_ValidURI() throws Exception { + assumeTrue(isTestConfigPresent()); + URIish uri = new URIish(amazonURI()); + assertTrue("uri=" + uri, TransportAmazonS3.PROTO_S3.canHandle(uri)); + } + + @Test(expected = Exception.class) + public void test_A2_CryptoError() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_ERROR); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTest(props); + } + + } + + /** + * Test minimal set of algorithms. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class MinimalSet extends Base { + + @Test + public void test_V0_Java7_JET() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T); + // Do not set version. + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTestIfCan(props); + } + + @Test + public void test_V1_Java7_GIT() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T); + props.put(AmazonS3.Keys.CRYPTO_VER, "1"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTestIfCan(props); + } + + @Test + public void test_V2_Java7_AES() throws Exception { + assumeTrue(isTestConfigPresent()); + // String profile = "default"; + String profile = "AES/CBC/PKCS5Padding+PBKDF2WithHmacSHA1"; + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, "2"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(profile + WalkEncryption.Keys.X_ALGO, "AES/CBC/PKCS5Padding"); + props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBKDF2WithHmacSHA1"); + props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "128"); + props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000"); + props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c"); + cryptoTestIfCan(props); + } + + @Test + public void test_V2_Java8_PBE_AES() throws Exception { + assumeTrue(isTestConfigPresent()); + String profile = "PBEWithHmacSHA512AndAES_256"; + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, "2"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(profile + WalkEncryption.Keys.X_ALGO, "PBEWithHmacSHA512AndAES_256"); + props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBEWithHmacSHA512AndAES_256"); + props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "256"); + props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000"); + props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c"); + policySetup(false); + cryptoTestIfCan(props); + } + + } + + /** + * Test all present and allowed PBE algorithms. + */ + // https://github.com/junit-team/junit/wiki/Parameterized-tests + @RunWith(Parameterized.class) + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class TestablePBE extends Base { + + @Parameters(name = "Profile: {0} Version: {1}") + public static Collection argsList() { + List algorithmList = new ArrayList<>(); + algorithmList.addAll(cryptoCipherListPBE()); + + List versionList = new ArrayList<>(); + versionList.add("0"); + versionList.add("1"); + + return product(algorithmList, versionList); + } + + final String profile; + + final String version; + + final String password = JGIT_PASS; + + public TestablePBE(String profile, String version) { + this.profile = profile; + this.version = version; + } + + @Test + public void testCrypto() throws Exception { + assumeTrue(permitLongTests()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, version); + props.put(AmazonS3.Keys.PASSWORD, password); + cryptoTestIfCan(props); + } + + } + + /** + * Test all present and allowed transformation algorithms. + */ + // https://github.com/junit-team/junit/wiki/Parameterized-tests + @RunWith(Parameterized.class) + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class TestableTransformation extends Base { + + @Parameters(name = "Profile: {0} Version: {1}") + public static Collection argsList() { + List algorithmList = new ArrayList<>(); + algorithmList.addAll(cryptoCipherListTrans()); + + List versionList = new ArrayList<>(); + versionList.add("1"); + + return product(algorithmList, versionList); + } + + final String profile; + + final String version; + + final String password = JGIT_PASS; + + public TestableTransformation(String profile, String version) { + this.profile = profile; + this.version = version; + } + + @Test + public void testCrypto() throws Exception { + assumeTrue(permitLongTests()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, version); + props.put(AmazonS3.Keys.PASSWORD, password); + cryptoTestIfCan(props); + } + + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,8 @@ package org.eclipse.jgit.treewalk; +import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE; +import static org.eclipse.jgit.lib.FileMode.SYMLINK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -50,9 +52,11 @@ import java.io.ByteArrayOutputStream; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.util.RawParseUtils; import org.junit.Before; import org.junit.Test; @@ -369,4 +373,41 @@ assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path, ctp.pathOffset, ctp.pathLen)); } + + @Test + public void testFindAttributesWhenFirst() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append(".gitattributes", REGULAR_FILE, hash_a); + ctp.reset(tree.toByteArray()); + + assertTrue(ctp.findFile(".gitattributes")); + assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode()); + assertEquals(".gitattributes", ctp.getEntryPathString()); + assertEquals(hash_a, ctp.getEntryObjectId()); + } + + @Test + public void testFindAttributesWhenSecond() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append(".config", SYMLINK, hash_a); + tree.append(".gitattributes", REGULAR_FILE, hash_foo); + ctp.reset(tree.toByteArray()); + + assertTrue(ctp.findFile(".gitattributes")); + assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode()); + assertEquals(".gitattributes", ctp.getEntryPathString()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + } + + @Test + public void testFindAttributesWhenMissing() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append("src", REGULAR_FILE, hash_a); + tree.append("zoo", REGULAR_FILE, hash_foo); + ctp.reset(tree.toByteArray()); + + assertFalse(ctp.findFile(".gitattributes")); + assertEquals(11, ctp.idOffset()); // Did not walk the entire tree. + assertEquals("src", ctp.getEntryPathString()); + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2012-2013, Robin Rosenberg - * 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.treewalk; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.ResetCommand.ResetType; -import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheEditor; -import org.eclipse.jgit.dircache.DirCacheEntry; -import org.eclipse.jgit.dircache.DirCacheIterator; -import org.eclipse.jgit.junit.RepositoryTestCase; -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.util.FS; -import org.eclipse.jgit.util.FileUtils; -import org.junit.Test; - -public class FileTreeIteratorJava7Test extends RepositoryTestCase { - @Test - public void testFileModeSymLinkIsNotATree() throws IOException { - FS fs = db.getFS(); - // mål = target in swedish, just to get som unicode in here - writeTrashFile("mål/data", "targetdata"); - fs.createSymLink(new File(trash, "länk"), "mål"); - FileTreeIterator fti = new FileTreeIterator(db); - assertFalse(fti.eof()); - assertEquals("länk", fti.getEntryPathString()); - assertEquals(FileMode.SYMLINK, fti.getEntryFileMode()); - fti.next(1); - assertFalse(fti.eof()); - assertEquals("mål", fti.getEntryPathString()); - assertEquals(FileMode.TREE, fti.getEntryFileMode()); - fti.next(1); - assertTrue(fti.eof()); - } - - @Test - public void testSymlinkNotModifiedThoughNormalized() throws Exception { - DirCache dc = db.lockDirCache(); - DirCacheEditor dce = dc.editor(); - final String UNNORMALIZED = "target/"; - final byte[] UNNORMALIZED_BYTES = Constants.encode(UNNORMALIZED); - try (ObjectInserter oi = db.newObjectInserter()) { - final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, - UNNORMALIZED_BYTES, 0, UNNORMALIZED_BYTES.length); - dce.add(new DirCacheEditor.PathEdit("link") { - @Override - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.SYMLINK); - ent.setObjectId(linkid); - ent.setLength(UNNORMALIZED_BYTES.length); - } - }); - assertTrue(dce.commit()); - } - new Git(db).commit().setMessage("Adding link").call(); - new Git(db).reset().setMode(ResetType.HARD).call(); - DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); - FileTreeIterator fti = new FileTreeIterator(db); - - // self-check - assertEquals("link", fti.getEntryPathString()); - assertEquals("link", dci.getEntryPathString()); - - // test - assertFalse(fti.isModified(dci.getDirCacheEntry(), true, - db.newObjectReader())); - } - - /** - * Like #testSymlinkNotModifiedThoughNormalized but there is no - * normalization being done. - * - * @throws Exception - */ - @Test - public void testSymlinkModifiedNotNormalized() throws Exception { - DirCache dc = db.lockDirCache(); - DirCacheEditor dce = dc.editor(); - final String NORMALIZED = "target"; - final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED); - try (ObjectInserter oi = db.newObjectInserter()) { - final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, - NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length); - dce.add(new DirCacheEditor.PathEdit("link") { - @Override - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.SYMLINK); - ent.setObjectId(linkid); - ent.setLength(NORMALIZED_BYTES.length); - } - }); - assertTrue(dce.commit()); - } - new Git(db).commit().setMessage("Adding link").call(); - new Git(db).reset().setMode(ResetType.HARD).call(); - DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); - FileTreeIterator fti = new FileTreeIterator(db); - - // self-check - assertEquals("link", fti.getEntryPathString()); - assertEquals("link", dci.getEntryPathString()); - - // test - assertFalse(fti.isModified(dci.getDirCacheEntry(), true, - db.newObjectReader())); - } - - /** - * Like #testSymlinkNotModifiedThoughNormalized but here the link is - * modified. - * - * @throws Exception - */ - @Test - public void testSymlinkActuallyModified() throws Exception { - final String NORMALIZED = "target"; - final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED); - try (ObjectInserter oi = db.newObjectInserter()) { - final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, - NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length); - DirCache dc = db.lockDirCache(); - DirCacheEditor dce = dc.editor(); - dce.add(new DirCacheEditor.PathEdit("link") { - @Override - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.SYMLINK); - ent.setObjectId(linkid); - ent.setLength(NORMALIZED_BYTES.length); - } - }); - assertTrue(dce.commit()); - } - new Git(db).commit().setMessage("Adding link").call(); - new Git(db).reset().setMode(ResetType.HARD).call(); - - FileUtils.delete(new File(trash, "link"), FileUtils.NONE); - FS.DETECTED.createSymLink(new File(trash, "link"), "newtarget"); - DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); - FileTreeIterator fti = new FileTreeIterator(db); - - // self-check - assertEquals("link", fti.getEntryPathString()); - assertEquals("link", dci.getEntryPathString()); - - // test - assertTrue(fti.isModified(dci.getDirCacheEntry(), true, - db.newObjectReader())); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, 2017, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -53,6 +53,7 @@ import java.security.MessageDigest; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheEditor; @@ -62,15 +63,20 @@ import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff; import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.RawParseUtils; import org.junit.Before; @@ -81,6 +87,7 @@ private long[] mtime; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -95,7 +102,7 @@ for (int i = paths.length - 1; i >= 0; i--) { final String s = paths[i]; writeTrashFile(s, s); - mtime[i] = new File(trash, s).lastModified(); + mtime[i] = FS.DETECTED.lastModified(new File(trash, s)); } } @@ -255,10 +262,11 @@ @Test public void testDirCacheMatchingId() throws Exception { File f = writeTrashFile("file", "content"); - Git git = new Git(db); - writeTrashFile("file", "content"); - fsTick(f); - git.add().addFilepattern("file").call(); + try (Git git = new Git(db)) { + writeTrashFile("file", "content"); + fsTick(f); + git.add().addFilepattern("file").call(); + } DirCacheEntry dce = db.readDirCache().getEntry("file"); TreeWalk tw = new TreeWalk(db); FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db @@ -280,13 +288,44 @@ } @Test + public void testTreewalkEnterSubtree() throws Exception { + try (Git git = new Git(db); TreeWalk tw = new TreeWalk(db)) { + writeTrashFile("b/c", "b/c"); + writeTrashFile("z/.git", "gitdir: /tmp/somewhere"); + git.add().addFilepattern(".").call(); + git.rm().addFilepattern("a,").addFilepattern("a,b") + .addFilepattern("a0b").call(); + assertEquals("[a/b, mode:100644][b/c, mode:100644][z, mode:160000]", + indexState(0)); + FileUtils.delete(new File(db.getWorkTree(), "b"), + FileUtils.RECURSIVE); + + tw.addTree(new DirCacheIterator(db.readDirCache())); + tw.addTree(new FileTreeIterator(db)); + assertTrue(tw.next()); + assertEquals("a", tw.getPathString()); + tw.enterSubtree(); + tw.next(); + assertEquals("a/b", tw.getPathString()); + tw.next(); + assertEquals("b", tw.getPathString()); + tw.enterSubtree(); + tw.next(); + assertEquals("b/c", tw.getPathString()); + assertNotNull(tw.getTree(0, AbstractTreeIterator.class)); + assertNotNull(tw.getTree(EmptyTreeIterator.class)); + } + } + + @Test public void testIsModifiedSymlinkAsFile() throws Exception { writeTrashFile("symlink", "content"); - Git git = new Git(db); - db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_SYMLINKS, "false"); - git.add().addFilepattern("symlink").call(); - git.commit().setMessage("commit").call(); + try (Git git = new Git(db)) { + db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_SYMLINKS, "false"); + git.add().addFilepattern("symlink").call(); + git.commit().setMessage("commit").call(); + } // Modify previously committed DirCacheEntry and write it back to disk DirCacheEntry dce = db.readDirCache().getEntry("symlink"); @@ -305,20 +344,21 @@ @Test public void testIsModifiedFileSmudged() throws Exception { File f = writeTrashFile("file", "content"); - Git git = new Git(db); - // The idea of this test is to check the smudged handling - // Hopefully fsTick will make sure our entry gets smudged - fsTick(f); - writeTrashFile("file", "content"); - long lastModified = f.lastModified(); - git.add().addFilepattern("file").call(); - writeTrashFile("file", "conten2"); - f.setLastModified(lastModified); - // We cannot trust this to go fast enough on - // a system with less than one-second lastModified - // resolution, so we force the index to have the - // same timestamp as the file we look at. - db.getIndexFile().setLastModified(lastModified); + try (Git git = new Git(db)) { + // The idea of this test is to check the smudged handling + // Hopefully fsTick will make sure our entry gets smudged + fsTick(f); + writeTrashFile("file", "content"); + long lastModified = f.lastModified(); + git.add().addFilepattern("file").call(); + writeTrashFile("file", "conten2"); + f.setLastModified(lastModified); + // We cannot trust this to go fast enough on + // a system with less than one-second lastModified + // resolution, so we force the index to have the + // same timestamp as the file we look at. + db.getIndexFile().setLastModified(lastModified); + } DirCacheEntry dce = db.readDirCache().getEntry("file"); FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db .getConfig().get(WorkingTreeOptions.KEY)); @@ -334,198 +374,443 @@ @Test public void submoduleHeadMatchesIndex() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - final RevCommit id = git.commit().setMessage("create file").call(); - final String path = "sub"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); - - Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) - .setDirectory(new File(db.getWorkTree(), path)).call() - .getRepository().close(); - - TreeWalk walk = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db); - walk.addTree(indexIter); - walk.addTree(workTreeIter); - walk.setFilter(PathFilter.create(path)); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit id = git.commit().setMessage("create file").call(); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository().close(); + + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db); + walk.addTree(indexIter); + walk.addTree(workTreeIter); + walk.setFilter(PathFilter.create(path)); - assertTrue(walk.next()); - assertTrue(indexIter.idEqual(workTreeIter)); + assertTrue(walk.next()); + assertTrue(indexIter.idEqual(workTreeIter)); + } } @Test public void submoduleWithNoGitDirectory() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - final RevCommit id = git.commit().setMessage("create file").call(); - final String path = "sub"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); - - File submoduleRoot = new File(db.getWorkTree(), path); - assertTrue(submoduleRoot.mkdir()); - assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir()); - - TreeWalk walk = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db); - walk.addTree(indexIter); - walk.addTree(workTreeIter); - walk.setFilter(PathFilter.create(path)); - - assertTrue(walk.next()); - assertFalse(indexIter.idEqual(workTreeIter)); - assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId()); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit id = git.commit().setMessage("create file").call(); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + File submoduleRoot = new File(db.getWorkTree(), path); + assertTrue(submoduleRoot.mkdir()); + assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir()); + + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db); + walk.addTree(indexIter); + walk.addTree(workTreeIter); + walk.setFilter(PathFilter.create(path)); + + assertTrue(walk.next()); + assertFalse(indexIter.idEqual(workTreeIter)); + assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId()); + } } @Test public void submoduleWithNoHead() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - final RevCommit id = git.commit().setMessage("create file").call(); - final String path = "sub"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit id = git.commit().setMessage("create file").call(); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path)) + .call().getRepository()); + + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db); + walk.addTree(indexIter); + walk.addTree(workTreeIter); + walk.setFilter(PathFilter.create(path)); + + assertTrue(walk.next()); + assertFalse(indexIter.idEqual(workTreeIter)); + assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId()); + } + } - assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path)) - .call().getRepository()); + @Test + public void submoduleDirectoryIterator() throws Exception { + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit id = git.commit().setMessage("create file").call(); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository().close(); + + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(), + db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY)); + walk.addTree(indexIter); + walk.addTree(workTreeIter); + walk.setFilter(PathFilter.create(path)); - TreeWalk walk = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db); - walk.addTree(indexIter); - walk.addTree(workTreeIter); - walk.setFilter(PathFilter.create(path)); - - assertTrue(walk.next()); - assertFalse(indexIter.idEqual(workTreeIter)); - assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId()); + assertTrue(walk.next()); + assertTrue(indexIter.idEqual(workTreeIter)); + } } @Test - public void submoduleDirectoryIterator() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - final RevCommit id = git.commit().setMessage("create file").call(); - final String path = "sub"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); + public void submoduleNestedWithHeadMatchingIndex() throws Exception { + try (Git git = new Git(db); + TreeWalk walk = new TreeWalk(db)) { + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + final RevCommit id = git.commit().setMessage("create file").call(); + final String path = "sub/dir1/dir2"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository().close(); + + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db); + walk.addTree(indexIter); + walk.addTree(workTreeIter); + walk.setFilter(PathFilter.create(path)); - Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) - .setDirectory(new File(db.getWorkTree(), path)).call() - .getRepository().close(); - - TreeWalk walk = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(), - db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY)); - walk.addTree(indexIter); - walk.addTree(workTreeIter); - walk.setFilter(PathFilter.create(path)); + assertTrue(walk.next()); + assertTrue(indexIter.idEqual(workTreeIter)); + } + } - assertTrue(walk.next()); - assertTrue(indexIter.idEqual(workTreeIter)); + @Test + public void idOffset() throws Exception { + try (Git git = new Git(db); + TreeWalk tw = new TreeWalk(db)) { + writeTrashFile("fileAinfsonly", "A"); + File fileBinindex = writeTrashFile("fileBinindex", "B"); + fsTick(fileBinindex); + git.add().addFilepattern("fileBinindex").call(); + writeTrashFile("fileCinfsonly", "C"); + DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); + FileTreeIterator workTreeIter = new FileTreeIterator(db); + tw.addTree(indexIter); + tw.addTree(workTreeIter); + workTreeIter.setDirCacheIterator(tw, 0); + assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw); + assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw); + assertEntry("0000000000000000000000000000000000000000", "a", tw); + assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw); + // The reason for adding this test. Check that the id is correct for + // mixed + assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220", + "fileAinfsonly", tw); + assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex", + tw); + assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c", + "fileCinfsonly", tw); + assertFalse(tw.next()); + } + } + + private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY = + new FileTreeIterator.FileModeStrategy() { + @Override + public FileMode getMode(File f, FS.Attributes attributes) { + if (attributes.isSymbolicLink()) { + return FileMode.SYMLINK; + } else if (attributes.isDirectory()) { + // NOTE: in the production DefaultFileModeStrategy, there is + // a check here for a subdirectory called '.git', and if it + // exists, we create a GITLINK instead of recursing into the + // tree. In this custom strategy, we ignore nested git dirs + // and treat all directories the same. + return FileMode.TREE; + } else if (attributes.isExecutable()) { + return FileMode.EXECUTABLE_FILE; + } else { + return FileMode.REGULAR_FILE; + } + } + }; + + private Repository createNestedRepo() throws IOException { + File gitdir = createUniqueTestGitDir(false); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + builder.setGitDir(gitdir); + Repository nestedRepo = builder.build(); + nestedRepo.create(); + + JGitTestUtil.writeTrashFile(nestedRepo, "sub", "a.txt", "content"); + + File nestedRepoPath = new File(nestedRepo.getWorkTree(), "sub/nested"); + FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder(); + nestedBuilder.setWorkTree(nestedRepoPath); + nestedBuilder.build().create(); + + JGitTestUtil.writeTrashFile(nestedRepo, "sub/nested", "b.txt", + "content b"); + + return nestedRepo; } @Test - public void submoduleNestedWithHeadMatchingIndex() throws Exception { - Git git = new Git(db); - writeTrashFile("file.txt", "content"); - git.add().addFilepattern("file.txt").call(); - final RevCommit id = git.commit().setMessage("create file").call(); - final String path = "sub/dir1/dir2"; - DirCache cache = db.lockDirCache(); - DirCacheEditor editor = cache.editor(); - editor.add(new PathEdit(path) { - - public void apply(DirCacheEntry ent) { - ent.setFileMode(FileMode.GITLINK); - ent.setObjectId(id); - } - }); - editor.commit(); + public void testCustomFileModeStrategy() throws Exception { + Repository nestedRepo = createNestedRepo(); + + try (Git git = new Git(nestedRepo)) { + // validate that our custom strategy is honored + WorkingTreeIterator customIterator = new FileTreeIterator( + nestedRepo, NO_GITLINKS_STRATEGY); + git.add().setWorkingTreeIterator(customIterator).addFilepattern(".") + .call(); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/nested/b.txt, mode:100644, content:content b]", + indexState(nestedRepo, CONTENT)); + } + } - Git.cloneRepository().setURI(db.getDirectory().toURI().toString()) - .setDirectory(new File(db.getWorkTree(), path)).call() - .getRepository().close(); - - TreeWalk walk = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db); - walk.addTree(indexIter); - walk.addTree(workTreeIter); - walk.setFilter(PathFilter.create(path)); + @Test + public void testCustomFileModeStrategyFromParentIterator() throws Exception { + Repository nestedRepo = createNestedRepo(); - assertTrue(walk.next()); - assertTrue(indexIter.idEqual(workTreeIter)); + try (Git git = new Git(nestedRepo)) { + FileTreeIterator customIterator = new FileTreeIterator(nestedRepo, + NO_GITLINKS_STRATEGY); + File r = new File(nestedRepo.getWorkTree(), "sub"); + + // here we want to validate that if we create a new iterator using + // the constructor that accepts a parent iterator, that the child + // iterator correctly inherits the FileModeStrategy from the parent + // iterator. + FileTreeIterator childIterator = new FileTreeIterator( + customIterator, r, nestedRepo.getFS()); + git.add().setWorkingTreeIterator(childIterator).addFilepattern(".") + .call(); + assertEquals( + "[sub/a.txt, mode:100644, content:content]" + + "[sub/nested/b.txt, mode:100644, content:content b]", + indexState(nestedRepo, CONTENT)); + } } @Test - public void idOffset() throws Exception { - Git git = new Git(db); - writeTrashFile("fileAinfsonly", "A"); - File fileBinindex = writeTrashFile("fileBinindex", "B"); - fsTick(fileBinindex); - git.add().addFilepattern("fileBinindex").call(); - writeTrashFile("fileCinfsonly", "C"); - TreeWalk tw = new TreeWalk(db); - DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache()); - FileTreeIterator workTreeIter = new FileTreeIterator(db); - tw.addTree(indexIter); - tw.addTree(workTreeIter); - workTreeIter.setDirCacheIterator(tw, 0); - assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw); - assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw); - assertEntry("0000000000000000000000000000000000000000", "a", tw); - assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw); - // The reason for adding this test. Check that the id is correct for - // mixed - assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220", - "fileAinfsonly", tw); - assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex", - tw); - assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c", - "fileCinfsonly", tw); - assertFalse(tw.next()); + public void testFileModeSymLinkIsNotATree() throws IOException { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + FS fs = db.getFS(); + // mål = target in swedish, just to get som unicode in here + writeTrashFile("mål/data", "targetdata"); + fs.createSymLink(new File(trash, "länk"), "mål"); + FileTreeIterator fti = new FileTreeIterator(db); + assertFalse(fti.eof()); + while (!fti.getEntryPathString().equals("länk")) { + fti.next(1); + } + assertEquals("länk", fti.getEntryPathString()); + assertEquals(FileMode.SYMLINK, fti.getEntryFileMode()); + fti.next(1); + assertFalse(fti.eof()); + assertEquals("mål", fti.getEntryPathString()); + assertEquals(FileMode.TREE, fti.getEntryFileMode()); + fti.next(1); + assertTrue(fti.eof()); + } + + @Test + public void testSymlinkNotModifiedThoughNormalized() throws Exception { + DirCache dc = db.lockDirCache(); + DirCacheEditor dce = dc.editor(); + final String UNNORMALIZED = "target/"; + final byte[] UNNORMALIZED_BYTES = Constants.encode(UNNORMALIZED); + try (ObjectInserter oi = db.newObjectInserter()) { + final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, + UNNORMALIZED_BYTES, 0, UNNORMALIZED_BYTES.length); + dce.add(new DirCacheEditor.PathEdit("link") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.SYMLINK); + ent.setObjectId(linkid); + ent.setLength(UNNORMALIZED_BYTES.length); + } + }); + assertTrue(dce.commit()); + } + try (Git git = new Git(db)) { + git.commit().setMessage("Adding link").call(); + git.reset().setMode(ResetType.HARD).call(); + DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); + FileTreeIterator fti = new FileTreeIterator(db); + + // self-check + while (!fti.getEntryPathString().equals("link")) { + fti.next(1); + } + assertEquals("link", fti.getEntryPathString()); + assertEquals("link", dci.getEntryPathString()); + + // test + assertFalse(fti.isModified(dci.getDirCacheEntry(), true, + db.newObjectReader())); + } + } + + /** + * Like #testSymlinkNotModifiedThoughNormalized but there is no + * normalization being done. + * + * @throws Exception + */ + @Test + public void testSymlinkModifiedNotNormalized() throws Exception { + DirCache dc = db.lockDirCache(); + DirCacheEditor dce = dc.editor(); + final String NORMALIZED = "target"; + final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED); + try (ObjectInserter oi = db.newObjectInserter()) { + final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, + NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length); + dce.add(new DirCacheEditor.PathEdit("link") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.SYMLINK); + ent.setObjectId(linkid); + ent.setLength(NORMALIZED_BYTES.length); + } + }); + assertTrue(dce.commit()); + } + try (Git git = new Git(db)) { + git.commit().setMessage("Adding link").call(); + git.reset().setMode(ResetType.HARD).call(); + DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); + FileTreeIterator fti = new FileTreeIterator(db); + + // self-check + while (!fti.getEntryPathString().equals("link")) { + fti.next(1); + } + assertEquals("link", fti.getEntryPathString()); + assertEquals("link", dci.getEntryPathString()); + + // test + assertFalse(fti.isModified(dci.getDirCacheEntry(), true, + db.newObjectReader())); + } + } + + /** + * Like #testSymlinkNotModifiedThoughNormalized but here the link is + * modified. + * + * @throws Exception + */ + @Test + public void testSymlinkActuallyModified() throws Exception { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + final String NORMALIZED = "target"; + final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED); + try (ObjectInserter oi = db.newObjectInserter()) { + final ObjectId linkid = oi.insert(Constants.OBJ_BLOB, + NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length); + DirCache dc = db.lockDirCache(); + DirCacheEditor dce = dc.editor(); + dce.add(new DirCacheEditor.PathEdit("link") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.SYMLINK); + ent.setObjectId(linkid); + ent.setLength(NORMALIZED_BYTES.length); + } + }); + assertTrue(dce.commit()); + } + try (Git git = new Git(db)) { + git.commit().setMessage("Adding link").call(); + git.reset().setMode(ResetType.HARD).call(); + + FileUtils.delete(new File(trash, "link"), FileUtils.NONE); + FS.DETECTED.createSymLink(new File(trash, "link"), "newtarget"); + DirCacheIterator dci = new DirCacheIterator(db.readDirCache()); + FileTreeIterator fti = new FileTreeIterator(db); + + // self-check + while (!fti.getEntryPathString().equals("link")) { + fti.next(1); + } + assertEquals("link", fti.getEntryPathString()); + assertEquals("link", dci.getEntryPathString()); + + // test + assertTrue(fti.isModified(dci.getDirCacheEntry(), true, + db.newObjectReader())); + } } private static void assertEntry(String sha1string, String path, TreeWalk tw) diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2010, Christian Halstrick - * 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.treewalk; - -import java.io.File; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.util.FS; - -/** - * A {@link FileTreeIterator} used in tests which allows to specify explicitly - * what will be returned by {@link #getEntryLastModified()}. This allows to - * write tests where certain files have to have the same modification time. - *

        - * This iterator is configured by a list of strictly increasing long values - * t(0), t(1), ..., t(n). For each file with a modification between t(x) and - * t(x+1) [ t(x) <= time < t(x+1) ] this iterator will report t(x). For files - * with a modification time smaller t(0) a modification time of 0 is returned. - * For files with a modification time greater or equal t(n) t(n) will be - * returned. - *

        - * This class was written especially to test racy-git problems - */ -public class FileTreeIteratorWithTimeControl extends FileTreeIterator { - private TreeSet modTimes; - - public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo, - TreeSet modTimes) { - super(p, repo.getWorkTree(), repo.getFS()); - this.modTimes = modTimes; - } - - public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs, - TreeSet modTimes) { - super(p, f, fs); - this.modTimes = modTimes; - } - - public FileTreeIteratorWithTimeControl(Repository repo, - TreeSet modTimes) { - super(repo); - this.modTimes = modTimes; - } - - public FileTreeIteratorWithTimeControl(File f, FS fs, - TreeSet modTimes) { - super(f, fs, new Config().get(WorkingTreeOptions.KEY)); - this.modTimes = modTimes; - } - - @Override - public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) { - return new FileTreeIteratorWithTimeControl(this, - ((FileEntry) current()).getFile(), fs, modTimes); - } - - @Override - public long getEntryLastModified() { - if (modTimes == null) - return 0; - Long cutOff = Long.valueOf(super.getEntryLastModified() + 1); - SortedSet head = modTimes.headSet(cutOff); - return head.isEmpty() ? 0 : head.last().longValue(); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -89,6 +89,7 @@ private Git git; + @Override @Before public void setUp() throws Exception { super.setUp(); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -66,6 +66,7 @@ private Repository db; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -76,10 +77,11 @@ public void testEmpty() throws IOException { DirCache dc1 = DirCache.newInCore(); DirCache dc2 = DirCache.newInCore(); - TreeWalk tw = new TreeWalk(db); - tw.addTree(new DirCacheIterator(dc1)); - tw.addTree(new DirCacheIterator(dc2)); - assertFalse(tw.next()); + try (TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new DirCacheIterator(dc1)); + tw.addTree(new DirCacheIterator(dc2)); + assertFalse(tw.next()); + } } static final class AddEdit extends PathEdit { @@ -124,14 +126,15 @@ editor.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, false)); editor.finish(); - TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc1)); - tw.addTree(new DirCacheIterator(dc2)); - tw.setFilter(InterIndexDiffFilter.INSTANCE); - assertTrue(tw.next()); - assertEquals("a/a", tw.getPathString()); - assertFalse(tw.next()); + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc1)); + tw.addTree(new DirCacheIterator(dc2)); + tw.setFilter(InterIndexDiffFilter.INSTANCE); + assertTrue(tw.next()); + assertEquals("a/a", tw.getPathString()); + assertFalse(tw.next()); + } } @Test @@ -145,13 +148,14 @@ ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, false)); ed2.finish(); - TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc1)); - tw.addTree(new DirCacheIterator(dc2)); - tw.setFilter(InterIndexDiffFilter.INSTANCE); + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc1)); + tw.addTree(new DirCacheIterator(dc2)); + tw.setFilter(InterIndexDiffFilter.INSTANCE); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } @Test @@ -165,15 +169,16 @@ ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, true)); ed2.finish(); - TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc1)); - tw.addTree(new DirCacheIterator(dc2)); - tw.setFilter(InterIndexDiffFilter.INSTANCE); - - assertTrue(tw.next()); - assertEquals("a/a", tw.getPathString()); - assertFalse(tw.next()); + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc1)); + tw.addTree(new DirCacheIterator(dc2)); + tw.setFilter(InterIndexDiffFilter.INSTANCE); + + assertTrue(tw.next()); + assertEquals("a/a", tw.getPathString()); + assertFalse(tw.next()); + } } @Test @@ -188,12 +193,13 @@ ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("b"), 1, true)); ed2.finish(); - TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new DirCacheIterator(dc1)); - tw.addTree(new DirCacheIterator(dc2)); - tw.setFilter(InterIndexDiffFilter.INSTANCE); + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc1)); + tw.addTree(new DirCacheIterator(dc2)); + tw.setFilter(InterIndexDiffFilter.INSTANCE); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,18 @@ package org.eclipse.jgit.treewalk.filter; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; @@ -58,6 +65,7 @@ import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.Before; import org.junit.Test; @@ -66,6 +74,8 @@ private TreeFilter filter; + private Map singles; + @Before public void setup() { // @formatter:off @@ -81,64 +91,75 @@ }; // @formatter:on filter = PathFilterGroup.createFromStrings(paths); + singles = new HashMap<>(); + for (String path : paths) { + singles.put(path, PathFilterGroup.createFromStrings(path)); + } } @Test public void testExact() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("a"))); - assertTrue(filter.include(fakeWalk("b/c"))); - assertTrue(filter.include(fakeWalk("c/d/e"))); - assertTrue(filter.include(fakeWalk("c/d/f"))); - assertTrue(filter.include(fakeWalk("d/e/f/g"))); - assertTrue(filter.include(fakeWalk("d/e/f/g.x"))); + assertMatches(Sets.of("a"), fakeWalk("a")); + assertMatches(Sets.of("b/c"), fakeWalk("b/c")); + assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e")); + assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g")); + assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x")); } @Test public void testNoMatchButClose() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertFalse(filter.include(fakeWalk("a+"))); - assertFalse(filter.include(fakeWalk("b+/c"))); - assertFalse(filter.include(fakeWalk("c+/d/e"))); - assertFalse(filter.include(fakeWalk("c+/d/f"))); - assertFalse(filter.include(fakeWalk("c/d.a"))); - assertFalse(filter.include(fakeWalk("d+/e/f/g"))); + assertNoMatches(fakeWalk("a+")); + assertNoMatches(fakeWalk("b+/c")); + assertNoMatches(fakeWalk("c+/d/e")); + assertNoMatches(fakeWalk("c+/d/f")); + assertNoMatches(fakeWalk("c/d.a")); + assertNoMatches(fakeWalk("d+/e/f/g")); } @Test public void testJustCommonPrefixIsNotMatch() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertFalse(filter.include(fakeWalk("b/a"))); - assertFalse(filter.include(fakeWalk("b/d"))); - assertFalse(filter.include(fakeWalk("c/d/a"))); - assertFalse(filter.include(fakeWalk("d/e/e"))); + assertNoMatches(fakeWalk("b/a")); + assertNoMatches(fakeWalk("b/d")); + assertNoMatches(fakeWalk("c/d/a")); + assertNoMatches(fakeWalk("d/e/e")); + assertNoMatches(fakeWalk("d/e/f/g.y")); } @Test public void testKeyIsPrefixOfFilter() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("b"))); - assertTrue(filter.include(fakeWalk("c/d"))); - assertTrue(filter.include(fakeWalk("c/d"))); - assertTrue(filter.include(fakeWalk("c"))); - assertTrue(filter.include(fakeWalk("d/e/f"))); - assertTrue(filter.include(fakeWalk("d/e"))); - assertTrue(filter.include(fakeWalk("d"))); + assertMatches(Sets.of("b/c"), fakeWalkAtSubtree("b")); + assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalkAtSubtree("c/d")); + assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalkAtSubtree("c")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), + fakeWalkAtSubtree("d/e/f")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), + fakeWalkAtSubtree("d/e")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), fakeWalkAtSubtree("d")); + + assertNoMatches(fakeWalk("b")); + assertNoMatches(fakeWalk("c/d")); + assertNoMatches(fakeWalk("c")); + assertNoMatches(fakeWalk("d/e/f")); + assertNoMatches(fakeWalk("d/e")); + assertNoMatches(fakeWalk("d")); + } @Test public void testFilterIsPrefixOfKey() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("a/b"))); - assertTrue(filter.include(fakeWalk("b/c/d"))); - assertTrue(filter.include(fakeWalk("c/d/e/f"))); - assertTrue(filter.include(fakeWalk("c/d/f/g"))); - assertTrue(filter.include(fakeWalk("d/e/f/g/h"))); - assertTrue(filter.include(fakeWalk("d/e/f/g/y"))); - assertTrue(filter.include(fakeWalk("d/e/f/g.x/h"))); - // listed before g/y, so can't StopWalk here, but it's not included - // either - assertFalse(filter.include(fakeWalk("d/e/f/g.y"))); + assertMatches(Sets.of("a"), fakeWalk("a/b")); + assertMatches(Sets.of("b/c"), fakeWalk("b/c/d")); + assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e/f")); + assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f/g")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/h")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/y")); + assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x/h")); } @Test @@ -182,6 +203,10 @@ // less obvious #2 due to git sorting order filter.include(fakeWalk("d/e/f/g/h.txt")); + // listed before g/y, so can't StopWalk here + filter.include(fakeWalk("d/e/f/g.y")); + singles.get("d/e/f/g").include(fakeWalk("d/e/f/g.y")); + // non-ascii try { filter.include(fakeWalk("\u00C0")); @@ -191,11 +216,50 @@ } } + private void assertNoMatches(TreeWalk tw) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + assertMatches(Sets. of(), tw); + } + + private void assertMatches(Set expect, TreeWalk tw) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + List actual = new ArrayList<>(); + for (String path : singles.keySet()) { + if (includes(singles.get(path), tw)) { + actual.add(path); + } + } + + String[] e = expect.toArray(new String[expect.size()]); + String[] a = actual.toArray(new String[actual.size()]); + Arrays.sort(e); + Arrays.sort(a); + assertArrayEquals(e, a); + + if (expect.isEmpty()) { + assertFalse(includes(filter, tw)); + } else { + assertTrue(includes(filter, tw)); + } + } + + private static boolean includes(TreeFilter f, TreeWalk tw) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + try { + return f.include(tw); + } catch (StopWalkException e) { + return false; + } + } + TreeWalk fakeWalk(final String path) throws IOException { DirCache dc = DirCache.newInCore(); DirCacheEditor dce = dc.editor(); dce.add(new DirCacheEditor.PathEdit(path) { + @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); } @@ -210,4 +274,26 @@ return ret; } + TreeWalk fakeWalkAtSubtree(final String path) throws IOException { + DirCache dc = DirCache.newInCore(); + DirCacheEditor dce = dc.editor(); + dce.add(new DirCacheEditor.PathEdit(path + "/README") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.REGULAR_FILE); + } + }); + dce.finish(); + + TreeWalk ret = new TreeWalk((ObjectReader) null); + ret.addTree(new DirCacheIterator(dc)); + ret.next(); + while (!path.equals(ret.getPathString())) { + if (ret.isSubtree()) { + ret.enterSubtree(); + } + ret.next(); + } + return ret; + } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2017 Magnus Vigerlöf (magnus.vigerlof@gmail.com) + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * 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.treewalk.filter; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Before; +import org.junit.Test; + +public class PathFilterLogicTest extends RepositoryTestCase { + + private ObjectId treeId; + + @Before + public void setup() throws IOException { + String[] paths = new String[] { + "a.txt", + "sub1.txt", + "sub1/suba/a.txt", + "sub1/subb/b.txt", + "sub2/suba/a.txt" + }; + treeId = createTree(paths); + } + + @Test + public void testSinglePath() throws IOException { + List expected = Arrays.asList("sub1/suba/a.txt", + "sub1/subb/b.txt"); + + TreeFilter tf = PathFilter.create("sub1"); + List paths = getMatchingPaths(treeId, tf); + + assertEquals(expected, paths); + } + + @Test + public void testSingleSubPath() throws IOException { + List expected = Collections.singletonList("sub1/suba/a.txt"); + + TreeFilter tf = PathFilter.create("sub1/suba"); + List paths = getMatchingPaths(treeId, tf); + + assertEquals(expected, paths); + } + + @Test + public void testSinglePathNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", + "sub2/suba/a.txt"); + + TreeFilter tf = PathFilter.create("sub1").negate(); + List paths = getMatchingPaths(treeId, tf); + + assertEquals(expected, paths); + } + + @Test + public void testSingleSubPathNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", + "sub1/subb/b.txt", "sub2/suba/a.txt"); + + TreeFilter tf = PathFilter.create("sub1/suba").negate(); + List paths = getMatchingPaths(treeId, tf); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiTwoPath() throws IOException { + List expected = Arrays.asList("sub1/suba/a.txt", + "sub1/subb/b.txt", "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub2")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiThreePath() throws IOException { + List expected = Arrays.asList("sub1.txt", "sub1/suba/a.txt", + "sub1/subb/b.txt", "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub2"), PathFilter.create("sub1.txt")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiTwoSubPath() throws IOException { + List expected = Arrays.asList("sub1/subb/b.txt", + "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"), + PathFilter.create("sub2/suba")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiTwoMixSubPath() throws IOException { + List expected = Arrays.asList("sub1/subb/b.txt", + "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"), + PathFilter.create("sub2")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiTwoMixSubPathNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", + "sub1/suba/a.txt", "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(), + PathFilter.create("sub1/suba")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testOrMultiThreeMixSubPathNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", + "sub1/suba/a.txt", "sub2/suba/a.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(), + PathFilter.create("sub1/suba"), PathFilter.create("no/path")}; + List paths = getMatchingPaths(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testPatternParentFileMatch() throws IOException { + List expected = Collections.emptyList(); + + TreeFilter tf = PathFilter.create("a.txt/test/path"); + List paths = getMatchingPaths(treeId, tf); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiPath() throws IOException { + List expected = Collections.emptyList(); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub2")}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiPathNegate() throws IOException { + List expected = Arrays.asList("sub1/suba/a.txt", + "sub1/subb/b.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub2").negate()}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiSubPathDualNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", + "sub1/subb/b.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/suba").negate(), + PathFilter.create("sub2").negate()}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiSubPath() throws IOException { + List expected = Collections.emptyList(); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub2/suba")}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiSubPathNegate() throws IOException { + List expected = Collections.singletonList("sub1/subb/b.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"), + PathFilter.create("sub1/suba").negate()}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testAndMultiThreeSubPathNegate() throws IOException { + List expected = Collections.singletonList("sub1/subb/b.txt"); + + TreeFilter[] tf = new TreeFilter[]{PathFilter.create("sub1"), + PathFilter.create("sub1/suba").negate(), + PathFilter.create("no/path").negate()}; + List paths = getMatchingPaths(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testTopAndMultiPathDualNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(), + PathFilter.create("sub2").negate()}; + List paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testTopAndMultiSubPathDualNegate() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", "sub1"); + + // Filter on 'sub1/suba' is kind of silly for a non-recursive walk. + // The result is interesting though as the 'sub1' path should be + // returned, due to the fact that there may be hits once the pattern + // is tested with one of the leaf paths. + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/suba").negate(), + PathFilter.create("sub2").negate()}; + List paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testTopOrMultiPathDual() throws IOException { + List expected = Arrays.asList("sub1.txt", "sub2"); + + TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1.txt"), + PathFilter.create("sub2")}; + List paths = getMatchingPathsFlat(treeId, OrTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + @Test + public void testTopNotPath() throws IOException { + List expected = Arrays.asList("a.txt", "sub1.txt", "sub2"); + + TreeFilter tf = PathFilter.create("sub1"); + List paths = getMatchingPathsFlat(treeId, NotTreeFilter.create(tf)); + + assertEquals(expected, paths); + } + + private List getMatchingPaths(final ObjectId objId, + TreeFilter tf) throws IOException { + return getMatchingPaths(objId, tf, true); + } + + private List getMatchingPathsFlat(final ObjectId objId, + TreeFilter tf) throws IOException { + return getMatchingPaths(objId, tf, false); + } + + private List getMatchingPaths(final ObjectId objId, + TreeFilter tf, boolean recursive) throws IOException { + try (TreeWalk tw = new TreeWalk(db)) { + tw.setFilter(tf); + tw.setRecursive(recursive); + tw.addTree(objId); + + List paths = new ArrayList<>(); + while (tw.next()) { + paths.add(tw.getPathString()); + } + return paths; + } + } + + private ObjectId createTree(String... paths) throws IOException { + final ObjectInserter odi = db.newObjectInserter(); + final DirCache dc = db.readDirCache(); + final DirCacheBuilder builder = dc.builder(); + for (String path : paths) { + DirCacheEntry entry = createEntry(path, FileMode.REGULAR_FILE); + builder.add(entry); + } + builder.finish(); + final ObjectId objId = dc.writeTree(odi); + odi.flush(); + return objId; + } +} + diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -113,15 +113,16 @@ private List getMatchingPaths(String suffixFilter, final ObjectId treeId, boolean recursiveWalk) throws IOException { - final TreeWalk tw = new TreeWalk(db); - tw.setFilter(PathSuffixFilter.create(suffixFilter)); - tw.setRecursive(recursiveWalk); - tw.addTree(treeId); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setFilter(PathSuffixFilter.create(suffixFilter)); + tw.setRecursive(recursiveWalk); + tw.addTree(treeId); - List paths = new ArrayList(); - while (tw.next()) - paths.add(tw.getPathString()); - return paths; + List paths = new ArrayList<>(); + while (tw.next()) + paths.add(tw.getPathString()); + return paths; + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,9 +55,10 @@ public class TreeFilterTest extends RepositoryTestCase { @Test public void testALL_IncludesAnything() throws Exception { - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new EmptyTreeIterator()); - assertTrue(TreeFilter.ALL.include(tw)); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new EmptyTreeIterator()); + assertTrue(TreeFilter.ALL.include(tw)); + } } @Test @@ -72,16 +73,18 @@ @Test public void testNotALL_IncludesNothing() throws Exception { - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new EmptyTreeIterator()); - assertFalse(TreeFilter.ALL.negate().include(tw)); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new EmptyTreeIterator()); + assertFalse(TreeFilter.ALL.negate().include(tw)); + } } @Test public void testANY_DIFF_IncludesSingleTreeCase() throws Exception { - final TreeWalk tw = new TreeWalk(db); - tw.addTree(new EmptyTreeIterator()); - assertTrue(TreeFilter.ANY_DIFF.include(tw)); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.addTree(new EmptyTreeIterator()); + assertTrue(TreeFilter.ANY_DIFF.include(tw)); + } } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -84,21 +84,22 @@ ObjectId tree = tree0.writeTree(oi); // Find the directories that were implicitly created above. - TreeWalk tw = new TreeWalk(or); - tw.addTree(tree); ObjectId a = null; ObjectId aSlashC = null; - while (tw.next()) { - if (tw.getPathString().equals("a")) { - a = tw.getObjectId(0); - tw.enterSubtree(); - while (tw.next()) { - if (tw.getPathString().equals("a/c")) { - aSlashC = tw.getObjectId(0); - break; + try (TreeWalk tw = new TreeWalk(or)) { + tw.addTree(tree); + while (tw.next()) { + if (tw.getPathString().equals("a")) { + a = tw.getObjectId(0); + tw.enterSubtree(); + while (tw.next()) { + if (tw.getPathString().equals("a/c")) { + aSlashC = tw.getObjectId(0); + break; + } } + break; } - break; } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -60,124 +60,124 @@ public class PostOrderTreeWalkTest extends RepositoryTestCase { @Test public void testInitialize_NoPostOrder() throws Exception { - final TreeWalk tw = new TreeWalk(db); - assertFalse(tw.isPostOrderTraversal()); + try (final TreeWalk tw = new TreeWalk(db)) { + assertFalse(tw.isPostOrderTraversal()); + } } @Test public void testInitialize_TogglePostOrder() throws Exception { - final TreeWalk tw = new TreeWalk(db); - assertFalse(tw.isPostOrderTraversal()); - tw.setPostOrderTraversal(true); - assertTrue(tw.isPostOrderTraversal()); - tw.setPostOrderTraversal(false); - assertFalse(tw.isPostOrderTraversal()); + try (final TreeWalk tw = new TreeWalk(db)) { + assertFalse(tw.isPostOrderTraversal()); + tw.setPostOrderTraversal(true); + assertTrue(tw.isPostOrderTraversal()); + tw.setPostOrderTraversal(false); + assertFalse(tw.isPostOrderTraversal()); + } } @Test public void testResetDoesNotAffectPostOrder() throws Exception { - final TreeWalk tw = new TreeWalk(db); - tw.setPostOrderTraversal(true); - assertTrue(tw.isPostOrderTraversal()); - tw.reset(); - assertTrue(tw.isPostOrderTraversal()); - - tw.setPostOrderTraversal(false); - assertFalse(tw.isPostOrderTraversal()); - tw.reset(); - assertFalse(tw.isPostOrderTraversal()); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setPostOrderTraversal(true); + assertTrue(tw.isPostOrderTraversal()); + tw.reset(); + assertTrue(tw.isPostOrderTraversal()); + + tw.setPostOrderTraversal(false); + assertFalse(tw.isPostOrderTraversal()); + tw.reset(); + assertFalse(tw.isPostOrderTraversal()); + } } @Test public void testNoPostOrder() throws Exception { final DirCache tree = db.readDirCache(); - { - final DirCacheBuilder b = tree.builder(); + final DirCacheBuilder b = tree.builder(); - b.add(makeFile("a")); - b.add(makeFile("b/c")); - b.add(makeFile("b/d")); - b.add(makeFile("q")); - - b.finish(); - assertEquals(4, tree.getEntryCount()); - } - - final TreeWalk tw = new TreeWalk(db); - tw.setPostOrderTraversal(false); - tw.addTree(new DirCacheIterator(tree)); - - assertModes("a", REGULAR_FILE, tw); - assertModes("b", TREE, tw); - assertTrue(tw.isSubtree()); - assertFalse(tw.isPostChildren()); - tw.enterSubtree(); - assertModes("b/c", REGULAR_FILE, tw); - assertModes("b/d", REGULAR_FILE, tw); - assertModes("q", REGULAR_FILE, tw); + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); + + b.finish(); + assertEquals(4, tree.getEntryCount()); + + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setPostOrderTraversal(false); + tw.addTree(new DirCacheIterator(tree)); + + assertModes("a", REGULAR_FILE, tw); + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); + tw.enterSubtree(); + assertModes("b/c", REGULAR_FILE, tw); + assertModes("b/d", REGULAR_FILE, tw); + assertModes("q", REGULAR_FILE, tw); + } } @Test public void testWithPostOrder_EnterSubtree() throws Exception { final DirCache tree = db.readDirCache(); - { - final DirCacheBuilder b = tree.builder(); + final DirCacheBuilder b = tree.builder(); - b.add(makeFile("a")); - b.add(makeFile("b/c")); - b.add(makeFile("b/d")); - b.add(makeFile("q")); + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); + + b.finish(); + assertEquals(4, tree.getEntryCount()); + + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setPostOrderTraversal(true); + tw.addTree(new DirCacheIterator(tree)); + + assertModes("a", REGULAR_FILE, tw); + + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); + tw.enterSubtree(); + assertModes("b/c", REGULAR_FILE, tw); + assertModes("b/d", REGULAR_FILE, tw); + + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertTrue(tw.isPostChildren()); - b.finish(); - assertEquals(4, tree.getEntryCount()); + assertModes("q", REGULAR_FILE, tw); } - - final TreeWalk tw = new TreeWalk(db); - tw.setPostOrderTraversal(true); - tw.addTree(new DirCacheIterator(tree)); - - assertModes("a", REGULAR_FILE, tw); - - assertModes("b", TREE, tw); - assertTrue(tw.isSubtree()); - assertFalse(tw.isPostChildren()); - tw.enterSubtree(); - assertModes("b/c", REGULAR_FILE, tw); - assertModes("b/d", REGULAR_FILE, tw); - - assertModes("b", TREE, tw); - assertTrue(tw.isSubtree()); - assertTrue(tw.isPostChildren()); - - assertModes("q", REGULAR_FILE, tw); } @Test public void testWithPostOrder_NoEnterSubtree() throws Exception { final DirCache tree = db.readDirCache(); - { - final DirCacheBuilder b = tree.builder(); + final DirCacheBuilder b = tree.builder(); - b.add(makeFile("a")); - b.add(makeFile("b/c")); - b.add(makeFile("b/d")); - b.add(makeFile("q")); + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); - b.finish(); - assertEquals(4, tree.getEntryCount()); - } + b.finish(); + assertEquals(4, tree.getEntryCount()); - final TreeWalk tw = new TreeWalk(db); - tw.setPostOrderTraversal(true); - tw.addTree(new DirCacheIterator(tree)); + try (final TreeWalk tw = new TreeWalk(db)) { + tw.setPostOrderTraversal(true); + tw.addTree(new DirCacheIterator(tree)); - assertModes("a", REGULAR_FILE, tw); + assertModes("a", REGULAR_FILE, tw); - assertModes("b", TREE, tw); - assertTrue(tw.isSubtree()); - assertFalse(tw.isPostChildren()); + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); - assertModes("q", REGULAR_FILE, tw); + assertModes("q", REGULAR_FILE, tw); + } } private DirCacheEntry makeFile(final String path) throws Exception { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,7 +44,6 @@ package org.eclipse.jgit.treewalk; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; -import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.lib.Constants.encode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -54,11 +53,10 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.junit.Test; -@SuppressWarnings("deprecation") public class TreeWalkBasicDiffTest extends RepositoryTestCase { @Test public void testMissingSubtree_DetectFileAdded_FileModified() @@ -72,62 +70,63 @@ // Create sub-a/empty, sub-c/empty = hello. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId1); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId1); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - oldTree = inserter.insert(OBJ_TREE, root.format()); + oldTree = inserter.insert(root); } // Create sub-a/empty, sub-b/empty, sub-c/empty. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subB = root.addTree("sub-b"); - subB.addFile("empty").setId(bFileId); - subB.setId(inserter.insert(OBJ_TREE, subB.format())); + TreeFormatter subB = new TreeFormatter(); + subB.append("empty", FileMode.REGULAR_FILE, bFileId); + root.append("sub-b", FileMode.TREE, inserter.insert(subB)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId2); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId2); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - newTree = inserter.insert(OBJ_TREE, root.format()); + newTree = inserter.insert(root); } inserter.flush(); } - final TreeWalk tw = new TreeWalk(db); - tw.reset(oldTree, newTree); - tw.setRecursive(true); - tw.setFilter(TreeFilter.ANY_DIFF); - - assertTrue(tw.next()); - assertEquals("sub-b/empty", tw.getPathString()); - assertEquals(FileMode.MISSING, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); - assertEquals(bFileId, tw.getObjectId(1)); - - assertTrue(tw.next()); - assertEquals("sub-c/empty", tw.getPathString()); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(cFileId1, tw.getObjectId(0)); - assertEquals(cFileId2, tw.getObjectId(1)); + try (TreeWalk tw = new TreeWalk(db)) { + tw.reset(oldTree, newTree); + tw.setRecursive(true); + tw.setFilter(TreeFilter.ANY_DIFF); + + assertTrue(tw.next()); + assertEquals("sub-b/empty", tw.getPathString()); + assertEquals(FileMode.MISSING, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); + assertEquals(bFileId, tw.getObjectId(1)); + + assertTrue(tw.next()); + assertEquals("sub-c/empty", tw.getPathString()); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(cFileId1, tw.getObjectId(0)); + assertEquals(cFileId2, tw.getObjectId(1)); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java 2019-09-03 12:37:49.000000000 +0000 @@ -55,17 +55,19 @@ public class TreeWalkJava7Test extends RepositoryTestCase { @Test public void testSymlinkToDirNotRecursingViaSymlink() throws Exception { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); FS fs = db.getFS(); assertTrue(fs.supportsSymlinks()); writeTrashFile("target/data", "targetdata"); fs.createSymLink(new File(trash, "link"), "target"); - TreeWalk tw = new TreeWalk(db); - tw.setRecursive(true); - tw.addTree(new FileTreeIterator(db)); - assertTrue(tw.next()); - assertEquals("link", tw.getPathString()); - assertTrue(tw.next()); - assertEquals("target/data", tw.getPathString()); - assertFalse(tw.next()); + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.addTree(new FileTreeIterator(db)); + assertTrue(tw.next()); + assertEquals("link", tw.getPathString()); + assertTrue(tw.next()); + assertEquals("target/data", tw.getPathString()); + assertFalse(tw.next()); + } } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -57,22 +57,22 @@ public void testEmptyList() { BlockList empty; - empty = new BlockList(); + empty = new BlockList<>(); assertEquals(0, empty.size()); assertTrue(empty.isEmpty()); assertFalse(empty.iterator().hasNext()); - empty = new BlockList(0); + empty = new BlockList<>(0); assertEquals(0, empty.size()); assertTrue(empty.isEmpty()); assertFalse(empty.iterator().hasNext()); - empty = new BlockList(1); + empty = new BlockList<>(1); assertEquals(0, empty.size()); assertTrue(empty.isEmpty()); assertFalse(empty.iterator().hasNext()); - empty = new BlockList(64); + empty = new BlockList<>(64); assertEquals(0, empty.size()); assertTrue(empty.isEmpty()); assertFalse(empty.iterator().hasNext()); @@ -80,7 +80,7 @@ @Test public void testGet() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); try { list.get(-1); @@ -121,7 +121,7 @@ @Test public void testSet() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); try { list.set(-1, "foo"); @@ -168,7 +168,7 @@ @Test public void testAddToEnd() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); int cnt = BlockList.BLOCK_SIZE * 3; for (int i = 0; i < cnt; i++) @@ -192,7 +192,7 @@ @Test public void testAddSlowPath() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); String fooStr = "foo"; String barStr = "bar"; @@ -223,7 +223,7 @@ @Test public void testRemoveFromEnd() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); String fooStr = "foo"; String barStr = "bar"; @@ -245,7 +245,7 @@ @Test public void testRemoveSlowPath() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); String fooStr = "foo"; String barStr = "bar"; @@ -270,7 +270,7 @@ @Test public void testAddRemoveAdd() { - BlockList list = new BlockList(); + BlockList list = new BlockList<>(); for (int i = 0; i < BlockList.BLOCK_SIZE + 1; i++) list.add(Integer.valueOf(i)); assertEquals(Integer.valueOf(BlockList.BLOCK_SIZE), @@ -283,14 +283,14 @@ @Test public void testAddAllFromOtherList() { - BlockList src = new BlockList(4); + BlockList src = new BlockList<>(4); int cnt = BlockList.BLOCK_SIZE * 2; for (int i = 0; i < cnt; i++) src.add(Integer.valueOf(42 + i)); src.add(Integer.valueOf(1)); - BlockList dst = new BlockList(4); + BlockList dst = new BlockList<>(4); dst.add(Integer.valueOf(255)); dst.addAll(src); assertEquals(cnt + 2, dst.size()); @@ -301,7 +301,7 @@ @Test public void testFastIterator() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); int cnt = BlockList.BLOCK_SIZE * 3; for (int i = 0; i < cnt; i++) @@ -318,7 +318,7 @@ @Test public void testAddRejectsBadIndexes() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); list.add(Integer.valueOf(41)); try { @@ -336,7 +336,7 @@ @Test public void testRemoveRejectsBadIndexes() { - BlockList list = new BlockList(4); + BlockList list = new BlockList<>(4); list.add(Integer.valueOf(41)); try { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,6 @@ import static org.junit.Assert.assertEquals; -import java.io.IOException; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.junit.MockSystemReader; @@ -113,7 +112,7 @@ } @Test - public void testId() throws IOException { + public void testId() { String msg = "A\nMessage\n"; ObjectId id = ChangeIdUtil.computeChangeId(treeId, parentId, p, q, msg); assertEquals("73f3751208ac92cbb76f9a26ac4a0d9d472e381b", ObjectId @@ -584,23 +583,6 @@ SOB1)); } - public void notestCommitDashV() throws Exception { - assertEquals("a\n" + // - "\n" + // - "Change-Id: I7fc3876fee63c766a2063df97fbe04a2dddd8d7c\n" + // - SOB1 + // - SOB2, // - call("a\n" + // - "\n" + // - SOB1 + // - SOB2 + // - "\n" + // - "# on branch master\n" + // - "diff --git a/src b/src\n" + // - "new file mode 100644\n" + // - "index 0000000..c78b7f0\n")); - } - @Test public void testWithEndingURL() throws Exception { assertEquals("a\n" + // diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2013, Robin Rosenberg - * 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.assertFalse; -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; -import org.junit.Test; - -public class FileUtils7Test { - - private final File trash = new File(new File("target"), "trash"); - - @Before - public void setUp() throws Exception { - FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY | FileUtils.SKIP_MISSING); - assertTrue(trash.mkdirs()); - } - - @After - public void tearDown() throws Exception { - FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY); - } - - @Test - public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget() - throws IOException { - FS fs = FS.DETECTED; - File dir = new File(trash, "dir"); - File file = new File(dir, "file"); - File link = new File(trash, "link"); - FileUtils.mkdirs(dir); - FileUtils.createNewFile(file); - fs.createSymLink(link, "dir"); - FileUtils.delete(link, FileUtils.RECURSIVE); - assertFalse(link.exists()); - 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 -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2010, 2013 Matthias Sohn + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.rmi.RemoteException; +import java.util.regex.Matcher; + +import javax.management.remote.JMXProviderException; + +import org.eclipse.jgit.junit.JGitTestUtil; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +public class FileUtilsTest { + private static final String MSG = "Stale file handle"; + + private static final String SOME_ERROR_MSG = "some error message"; + + private static final IOException IO_EXCEPTION = new UnsupportedEncodingException( + MSG); + + private static final IOException IO_EXCEPTION_WITH_CAUSE = new RemoteException( + SOME_ERROR_MSG, + new JMXProviderException(SOME_ERROR_MSG, IO_EXCEPTION)); + + private File trash; + + @Before + public void setUp() throws Exception { + trash = File.createTempFile("tmp_", ""); + trash.delete(); + assertTrue("mkdir " + trash, trash.mkdir()); + } + + @After + public void tearDown() throws Exception { + FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY); + } + + @Test + public void testDeleteFile() throws IOException { + File f = new File(trash, "test"); + FileUtils.createNewFile(f); + FileUtils.delete(f); + assertFalse(f.exists()); + + try { + FileUtils.delete(f); + fail("deletion of non-existing file must fail"); + } catch (IOException e) { + // expected + } + + try { + FileUtils.delete(f, FileUtils.SKIP_MISSING); + } catch (IOException e) { + fail("deletion of non-existing file must not fail with option SKIP_MISSING"); + } + } + + @Test + public void testDeleteRecursive() throws IOException { + File f1 = new File(trash, "test/test/a"); + FileUtils.mkdirs(f1.getParentFile()); + FileUtils.createNewFile(f1); + File f2 = new File(trash, "test/test/b"); + FileUtils.createNewFile(f2); + File d = new File(trash, "test"); + FileUtils.delete(d, FileUtils.RECURSIVE); + assertFalse(d.exists()); + + try { + FileUtils.delete(d, FileUtils.RECURSIVE); + fail("recursive deletion of non-existing directory must fail"); + } catch (IOException e) { + // expected + } + + try { + FileUtils.delete(d, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + } catch (IOException e) { + fail("recursive deletion of non-existing directory must not fail with option SKIP_MISSING"); + } + } + + @Test + + public void testDeleteRecursiveEmpty() throws IOException { + File f1 = new File(trash, "test/test/a"); + File f2 = new File(trash, "test/a"); + File d1 = new File(trash, "test"); + File d2 = new File(trash, "test/test"); + File d3 = new File(trash, "test/b"); + FileUtils.mkdirs(f1.getParentFile()); + FileUtils.createNewFile(f2); + FileUtils.createNewFile(f1); + FileUtils.mkdirs(d3); + + // Cannot delete hierarchy since files exist + try { + FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY); + fail("delete should fail"); + } catch (IOException e1) { + try { + FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY|FileUtils.RECURSIVE); + fail("delete should fail"); + } catch (IOException e2) { + // Everything still there + assertTrue(f1.exists()); + assertTrue(f2.exists()); + assertTrue(d1.exists()); + assertTrue(d2.exists()); + assertTrue(d3.exists()); + } + } + + // setup: delete files, only directories left + assertTrue(f1.delete()); + assertTrue(f2.delete()); + + // Shall not delete hierarchy without recursive + try { + FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY); + fail("delete should fail"); + } catch (IOException e2) { + // Everything still there + assertTrue(d1.exists()); + assertTrue(d2.exists()); + assertTrue(d3.exists()); + } + + // Now delete the empty hierarchy + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.RECURSIVE); + assertFalse(d2.exists()); + + // Will fail to delete non-existing without SKIP_MISSING + try { + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY); + fail("Cannot delete non-existent entity"); + } catch (IOException e) { + // ok + } + + // ..with SKIP_MISSING there is no exception + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.SKIP_MISSING); + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + + // essentially the same, using IGNORE_ERRORS + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.IGNORE_ERRORS); + FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); + } + + @Test + public void testDeleteRecursiveEmptyNeedsToCheckFilesFirst() + throws IOException { + File d1 = new File(trash, "test"); + File d2 = new File(trash, "test/a"); + File d3 = new File(trash, "test/b"); + File f1 = new File(trash, "test/c"); + File d4 = new File(trash, "test/d"); + FileUtils.mkdirs(d1); + FileUtils.mkdirs(d2); + FileUtils.mkdirs(d3); + FileUtils.mkdirs(d4); + FileUtils.createNewFile(f1); + + // Cannot delete hierarchy since file exists + try { + FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY + | FileUtils.RECURSIVE); + fail("delete should fail"); + } catch (IOException e) { + // Everything still there + assertTrue(f1.exists()); + assertTrue(d1.exists()); + assertTrue(d2.exists()); + assertTrue(d3.exists()); + assertTrue(d4.exists()); + } + } + + @Test + public void testDeleteRecursiveEmptyDirectoriesOnlyButIsFile() + throws IOException { + File f1 = new File(trash, "test/test/a"); + FileUtils.mkdirs(f1.getParentFile()); + FileUtils.createNewFile(f1); + try { + FileUtils.delete(f1, FileUtils.EMPTY_DIRECTORIES_ONLY); + fail("delete should fail"); + } catch (IOException e) { + assertTrue(f1.exists()); + } + } + + @Test + public void testMkdir() throws IOException { + File d = new File(trash, "test"); + FileUtils.mkdir(d); + assertTrue(d.exists() && d.isDirectory()); + + try { + FileUtils.mkdir(d); + fail("creation of existing directory must fail"); + } catch (IOException e) { + // expected + } + + FileUtils.mkdir(d, true); + assertTrue(d.exists() && d.isDirectory()); + + assertTrue(d.delete()); + File f = new File(trash, "test"); + FileUtils.createNewFile(f); + try { + FileUtils.mkdir(d); + fail("creation of directory having same path as existing file must" + + " fail"); + } catch (IOException e) { + // expected + } + assertTrue(f.delete()); + } + + @Test + public void testMkdirs() throws IOException { + File root = new File(trash, "test"); + assertTrue(root.mkdir()); + + File d = new File(root, "test/test"); + FileUtils.mkdirs(d); + assertTrue(d.exists() && d.isDirectory()); + + try { + FileUtils.mkdirs(d); + fail("creation of existing directory hierarchy must fail"); + } catch (IOException e) { + // expected + } + + FileUtils.mkdirs(d, true); + assertTrue(d.exists() && d.isDirectory()); + + FileUtils.delete(root, FileUtils.RECURSIVE); + File f = new File(trash, "test"); + FileUtils.createNewFile(f); + try { + FileUtils.mkdirs(d); + fail("creation of directory having path conflicting with existing" + + " file must fail"); + } catch (IOException e) { + // expected + } + assertTrue(f.delete()); + } + + @Test + public void testCreateNewFile() throws IOException { + File f = new File(trash, "x"); + FileUtils.createNewFile(f); + assertTrue(f.exists()); + + try { + FileUtils.createNewFile(f); + fail("creation of already existing file must fail"); + } catch (IOException e) { + // expected + } + + FileUtils.delete(f); + } + + @Test + public void testDeleteEmptyTreeOk() throws IOException { + File t = new File(trash, "t"); + FileUtils.mkdir(t); + FileUtils.mkdir(new File(t, "d")); + FileUtils.mkdir(new File(new File(t, "d"), "e")); + FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE); + assertFalse(t.exists()); + } + + @Test + public void testDeleteNotEmptyTreeNotOk() throws IOException { + File t = new File(trash, "t"); + FileUtils.mkdir(t); + FileUtils.mkdir(new File(t, "d")); + File f = new File(new File(t, "d"), "f"); + FileUtils.createNewFile(f); + FileUtils.mkdir(new File(new File(t, "d"), "e")); + try { + FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE); + fail("expected failure to delete f"); + } catch (IOException e) { + assertTrue(e.getMessage().endsWith(f.getAbsolutePath())); + } + assertTrue(t.exists()); + } + + @Test + public void testDeleteNotEmptyTreeNotOkButIgnoreFail() throws IOException { + File t = new File(trash, "t"); + FileUtils.mkdir(t); + FileUtils.mkdir(new File(t, "d")); + File f = new File(new File(t, "d"), "f"); + FileUtils.createNewFile(f); + File e = new File(new File(t, "d"), "e"); + FileUtils.mkdir(e); + FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE + | FileUtils.IGNORE_ERRORS); + // Should have deleted as much as possible, but not all + assertTrue(t.exists()); + assertTrue(f.exists()); + assertFalse(e.exists()); + } + + @Test + public void testRenameOverNonExistingFile() throws IOException { + File d = new File(trash, "d"); + FileUtils.mkdirs(d); + File f1 = new File(trash, "d/f"); + File f2 = new File(trash, "d/g"); + JGitTestUtil.write(f1, "f1"); + // test + FileUtils.rename(f1, f2); + assertFalse(f1.exists()); + assertTrue(f2.exists()); + assertEquals("f1", JGitTestUtil.read(f2)); + } + + @Test + public void testRenameOverExistingFile() throws IOException { + File d = new File(trash, "d"); + FileUtils.mkdirs(d); + File f1 = new File(trash, "d/f"); + File f2 = new File(trash, "d/g"); + JGitTestUtil.write(f1, "f1"); + JGitTestUtil.write(f2, "f2"); + // test + FileUtils.rename(f1, f2); + assertFalse(f1.exists()); + assertTrue(f2.exists()); + assertEquals("f1", JGitTestUtil.read(f2)); + } + + @Test + public void testRenameOverExistingNonEmptyDirectory() throws IOException { + File d = new File(trash, "d"); + FileUtils.mkdirs(d); + File f1 = new File(trash, "d/f"); + File f2 = new File(trash, "d/g"); + File d1 = new File(trash, "d/g/h/i"); + File f3 = new File(trash, "d/g/h/f"); + FileUtils.mkdirs(d1); + JGitTestUtil.write(f1, "f1"); + JGitTestUtil.write(f3, "f3"); + // test + try { + FileUtils.rename(f1, f2); + fail("rename to non-empty directory should fail"); + } catch (IOException e) { + assertEquals("f1", JGitTestUtil.read(f1)); // untouched source + assertEquals("f3", JGitTestUtil.read(f3)); // untouched + // empty directories within f2 may or may not have been deleted + } + } + + @Test + public void testRenameOverExistingEmptyDirectory() throws IOException { + File d = new File(trash, "d"); + FileUtils.mkdirs(d); + File f1 = new File(trash, "d/f"); + File f2 = new File(trash, "d/g"); + File d1 = new File(trash, "d/g/h/i"); + FileUtils.mkdirs(d1); + JGitTestUtil.write(f1, "f1"); + // test + FileUtils.rename(f1, f2); + assertFalse(f1.exists()); + assertTrue(f2.exists()); + assertEquals("f1", JGitTestUtil.read(f2)); + } + + @Test + public void testCreateSymlink() throws IOException { + FS fs = FS.DETECTED; + // show test as ignored if the FS doesn't support symlinks + Assume.assumeTrue(fs.supportsSymlinks()); + fs.createSymLink(new File(trash, "x"), "y"); + String target = fs.readSymLink(new File(trash, "x")); + assertEquals("y", target); + } + + @Test + public void testCreateSymlinkOverrideExisting() throws IOException { + FS fs = FS.DETECTED; + // show test as ignored if the FS doesn't support symlinks + Assume.assumeTrue(fs.supportsSymlinks()); + File file = new File(trash, "x"); + fs.createSymLink(file, "y"); + String target = fs.readSymLink(file); + assertEquals("y", target); + fs.createSymLink(file, "z"); + target = fs.readSymLink(file); + assertEquals("z", target); + } + + @Test + public void testRelativize_doc() { + // This is the example from the javadoc + String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project"); + String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"); + String expected = toOSPathString("..\\another_project\\pom.xml"); + + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expected, actual); + } + + @Test + public void testRelativize_mixedCase() { + SystemReader systemReader = SystemReader.getInstance(); + String base = toOSPathString("C:\\git\\jgit"); + String other = toOSPathString("C:\\Git\\test\\d\\f.txt"); + String expectedCaseInsensitive = toOSPathString("..\\test\\d\\f.txt"); + String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt"); + + if (systemReader.isWindows()) { + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expectedCaseInsensitive, actual); + } else if (systemReader.isMacOS()) { + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expectedCaseInsensitive, actual); + } else { + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expectedCaseSensitive, actual); + } + } + + @Test + public void testRelativize_scheme() { + String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1/file.java"); + String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project"); + // 'file.java' is treated as a folder + String expected = toOSPathString("../../project"); + + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expected, actual); + } + + @Test + public void testRelativize_equalPaths() { + String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1"); + String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1"); + String expected = ""; + + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expected, actual); + } + + @Test + public void testRelativize_whitespaces() { + String base = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1"); + String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file"); + String expected = "file"; + + String actual = FileUtils.relativizeNativePath(base, other); + assertEquals(expected, actual); + } + + @Test + public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget() + throws IOException { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + FS fs = FS.DETECTED; + File dir = new File(trash, "dir"); + File file = new File(dir, "file"); + File link = new File(trash, "link"); + FileUtils.mkdirs(dir); + FileUtils.createNewFile(file); + fs.createSymLink(link, "dir"); + FileUtils.delete(link, FileUtils.RECURSIVE); + assertFalse(link.exists()); + 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())); + } + + private String toOSPathString(String path) { + return path.replaceAll("/|\\\\", + Matcher.quoteReplacement(File.separator)); + } + + @Test + public void testIsStaleFileHandleWithDirectCause() throws Exception { + assertTrue(FileUtils.isStaleFileHandle(IO_EXCEPTION)); + } + + @Test + public void testIsStaleFileHandleWithIndirectCause() throws Exception { + assertFalse( + FileUtils.isStaleFileHandle(IO_EXCEPTION_WITH_CAUSE)); + } + + @Test + public void testIsStaleFileHandleInCausalChainWithDirectCause() + throws Exception { + assertTrue( + FileUtils.isStaleFileHandleInCausalChain(IO_EXCEPTION)); + } + + @Test + public void testIsStaleFileHandleInCausalChainWithIndirectCause() + throws Exception { + assertTrue(FileUtils + .isStaleFileHandleInCausalChain(IO_EXCEPTION_WITH_CAUSE)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,505 +0,0 @@ -/* - * Copyright (C) 2010, 2013 Matthias Sohn - * 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 static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.util.regex.Matcher; - -import org.eclipse.jgit.junit.JGitTestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class FileUtilTest { - private File trash; - - @Before - public void setUp() throws Exception { - trash = File.createTempFile("tmp_", ""); - trash.delete(); - assertTrue("mkdir " + trash, trash.mkdir()); - } - - @After - public void tearDown() throws Exception { - FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY); - } - - @Test - public void testDeleteFile() throws IOException { - File f = new File(trash, "test"); - FileUtils.createNewFile(f); - FileUtils.delete(f); - assertFalse(f.exists()); - - try { - FileUtils.delete(f); - fail("deletion of non-existing file must fail"); - } catch (IOException e) { - // expected - } - - try { - FileUtils.delete(f, FileUtils.SKIP_MISSING); - } catch (IOException e) { - fail("deletion of non-existing file must not fail with option SKIP_MISSING"); - } - } - - @Test - public void testDeleteRecursive() throws IOException { - File f1 = new File(trash, "test/test/a"); - FileUtils.mkdirs(f1.getParentFile()); - FileUtils.createNewFile(f1); - File f2 = new File(trash, "test/test/b"); - FileUtils.createNewFile(f2); - File d = new File(trash, "test"); - FileUtils.delete(d, FileUtils.RECURSIVE); - assertFalse(d.exists()); - - try { - FileUtils.delete(d, FileUtils.RECURSIVE); - fail("recursive deletion of non-existing directory must fail"); - } catch (IOException e) { - // expected - } - - try { - FileUtils.delete(d, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); - } catch (IOException e) { - fail("recursive deletion of non-existing directory must not fail with option SKIP_MISSING"); - } - } - - @Test - - public void testDeleteRecursiveEmpty() throws IOException { - File f1 = new File(trash, "test/test/a"); - File f2 = new File(trash, "test/a"); - File d1 = new File(trash, "test"); - File d2 = new File(trash, "test/test"); - File d3 = new File(trash, "test/b"); - FileUtils.mkdirs(f1.getParentFile()); - FileUtils.createNewFile(f2); - FileUtils.createNewFile(f1); - FileUtils.mkdirs(d3); - - // Cannot delete hierarchy since files exist - try { - FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY); - fail("delete should fail"); - } catch (IOException e1) { - try { - FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY|FileUtils.RECURSIVE); - fail("delete should fail"); - } catch (IOException e2) { - // Everything still there - assertTrue(f1.exists()); - assertTrue(f2.exists()); - assertTrue(d1.exists()); - assertTrue(d2.exists()); - assertTrue(d3.exists()); - } - } - - // setup: delete files, only directories left - assertTrue(f1.delete()); - assertTrue(f2.delete()); - - // Shall not delete hierarchy without recursive - try { - FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY); - fail("delete should fail"); - } catch (IOException e2) { - // Everything still there - assertTrue(d1.exists()); - assertTrue(d2.exists()); - assertTrue(d3.exists()); - } - - // Now delete the empty hierarchy - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.RECURSIVE); - assertFalse(d2.exists()); - - // Will fail to delete non-existing without SKIP_MISSING - try { - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY); - fail("Cannot delete non-existent entity"); - } catch (IOException e) { - // ok - } - - // ..with SKIP_MISSING there is no exception - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.SKIP_MISSING); - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); - - // essentially the same, using IGNORE_ERRORS - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.IGNORE_ERRORS); - FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS); - } - - @Test - public void testDeleteRecursiveEmptyNeedsToCheckFilesFirst() - throws IOException { - File d1 = new File(trash, "test"); - File d2 = new File(trash, "test/a"); - File d3 = new File(trash, "test/b"); - File f1 = new File(trash, "test/c"); - File d4 = new File(trash, "test/d"); - FileUtils.mkdirs(d1); - FileUtils.mkdirs(d2); - FileUtils.mkdirs(d3); - FileUtils.mkdirs(d4); - FileUtils.createNewFile(f1); - - // Cannot delete hierarchy since file exists - try { - FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY - | FileUtils.RECURSIVE); - fail("delete should fail"); - } catch (IOException e) { - // Everything still there - assertTrue(f1.exists()); - assertTrue(d1.exists()); - assertTrue(d2.exists()); - assertTrue(d3.exists()); - assertTrue(d4.exists()); - } - } - - @Test - public void testDeleteRecursiveEmptyDirectoriesOnlyButIsFile() - throws IOException { - File f1 = new File(trash, "test/test/a"); - FileUtils.mkdirs(f1.getParentFile()); - FileUtils.createNewFile(f1); - try { - FileUtils.delete(f1, FileUtils.EMPTY_DIRECTORIES_ONLY); - fail("delete should fail"); - } catch (IOException e) { - assertTrue(f1.exists()); - } - } - - @Test - public void testMkdir() throws IOException { - File d = new File(trash, "test"); - FileUtils.mkdir(d); - assertTrue(d.exists() && d.isDirectory()); - - try { - FileUtils.mkdir(d); - fail("creation of existing directory must fail"); - } catch (IOException e) { - // expected - } - - FileUtils.mkdir(d, true); - assertTrue(d.exists() && d.isDirectory()); - - assertTrue(d.delete()); - File f = new File(trash, "test"); - FileUtils.createNewFile(f); - try { - FileUtils.mkdir(d); - fail("creation of directory having same path as existing file must" - + " fail"); - } catch (IOException e) { - // expected - } - assertTrue(f.delete()); - } - - @Test - public void testMkdirs() throws IOException { - File root = new File(trash, "test"); - assertTrue(root.mkdir()); - - File d = new File(root, "test/test"); - FileUtils.mkdirs(d); - assertTrue(d.exists() && d.isDirectory()); - - try { - FileUtils.mkdirs(d); - fail("creation of existing directory hierarchy must fail"); - } catch (IOException e) { - // expected - } - - FileUtils.mkdirs(d, true); - assertTrue(d.exists() && d.isDirectory()); - - FileUtils.delete(root, FileUtils.RECURSIVE); - File f = new File(trash, "test"); - FileUtils.createNewFile(f); - try { - FileUtils.mkdirs(d); - fail("creation of directory having path conflicting with existing" - + " file must fail"); - } catch (IOException e) { - // expected - } - assertTrue(f.delete()); - } - - @Test - public void testCreateNewFile() throws IOException { - File f = new File(trash, "x"); - FileUtils.createNewFile(f); - assertTrue(f.exists()); - - try { - FileUtils.createNewFile(f); - fail("creation of already existing file must fail"); - } catch (IOException e) { - // expected - } - - FileUtils.delete(f); - } - - @Test - public void testDeleteEmptyTreeOk() throws IOException { - File t = new File(trash, "t"); - FileUtils.mkdir(t); - FileUtils.mkdir(new File(t, "d")); - FileUtils.mkdir(new File(new File(t, "d"), "e")); - FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE); - assertFalse(t.exists()); - } - - @Test - public void testDeleteNotEmptyTreeNotOk() throws IOException { - File t = new File(trash, "t"); - FileUtils.mkdir(t); - FileUtils.mkdir(new File(t, "d")); - File f = new File(new File(t, "d"), "f"); - FileUtils.createNewFile(f); - FileUtils.mkdir(new File(new File(t, "d"), "e")); - try { - FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE); - fail("expected failure to delete f"); - } catch (IOException e) { - assertTrue(e.getMessage().endsWith(f.getAbsolutePath())); - } - assertTrue(t.exists()); - } - - @Test - public void testDeleteNotEmptyTreeNotOkButIgnoreFail() throws IOException { - File t = new File(trash, "t"); - FileUtils.mkdir(t); - FileUtils.mkdir(new File(t, "d")); - File f = new File(new File(t, "d"), "f"); - FileUtils.createNewFile(f); - File e = new File(new File(t, "d"), "e"); - FileUtils.mkdir(e); - FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE - | FileUtils.IGNORE_ERRORS); - // Should have deleted as much as possible, but not all - assertTrue(t.exists()); - assertTrue(f.exists()); - assertFalse(e.exists()); - } - - @Test - public void testRenameOverNonExistingFile() throws IOException { - File d = new File(trash, "d"); - FileUtils.mkdirs(d); - File f1 = new File(trash, "d/f"); - File f2 = new File(trash, "d/g"); - JGitTestUtil.write(f1, "f1"); - // test - FileUtils.rename(f1, f2); - assertFalse(f1.exists()); - assertTrue(f2.exists()); - assertEquals("f1", JGitTestUtil.read(f2)); - } - - @Test - public void testRenameOverExistingFile() throws IOException { - File d = new File(trash, "d"); - FileUtils.mkdirs(d); - File f1 = new File(trash, "d/f"); - File f2 = new File(trash, "d/g"); - JGitTestUtil.write(f1, "f1"); - JGitTestUtil.write(f2, "f2"); - // test - FileUtils.rename(f1, f2); - assertFalse(f1.exists()); - assertTrue(f2.exists()); - assertEquals("f1", JGitTestUtil.read(f2)); - } - - @Test - public void testRenameOverExistingNonEmptyDirectory() throws IOException { - File d = new File(trash, "d"); - FileUtils.mkdirs(d); - File f1 = new File(trash, "d/f"); - File f2 = new File(trash, "d/g"); - File d1 = new File(trash, "d/g/h/i"); - File f3 = new File(trash, "d/g/h/f"); - FileUtils.mkdirs(d1); - JGitTestUtil.write(f1, "f1"); - JGitTestUtil.write(f3, "f3"); - // test - try { - FileUtils.rename(f1, f2); - fail("rename to non-empty directory should fail"); - } catch (IOException e) { - assertEquals("f1", JGitTestUtil.read(f1)); // untouched source - assertEquals("f3", JGitTestUtil.read(f3)); // untouched - // empty directories within f2 may or may not have been deleted - } - } - - @Test - public void testRenameOverExistingEmptyDirectory() throws IOException { - File d = new File(trash, "d"); - FileUtils.mkdirs(d); - File f1 = new File(trash, "d/f"); - File f2 = new File(trash, "d/g"); - File d1 = new File(trash, "d/g/h/i"); - FileUtils.mkdirs(d1); - JGitTestUtil.write(f1, "f1"); - // test - FileUtils.rename(f1, f2); - assertFalse(f1.exists()); - assertTrue(f2.exists()); - assertEquals("f1", JGitTestUtil.read(f2)); - } - - @Test - public void testCreateSymlink() throws IOException { - FS fs = FS.DETECTED; - try { - fs.createSymLink(new File(trash, "x"), "y"); - } catch (IOException e) { - if (fs.supportsSymlinks()) - fail("FS claims to support symlinks but attempt to create symlink failed"); - return; - } - assertTrue(fs.supportsSymlinks()); - String target = fs.readSymLink(new File(trash, "x")); - assertEquals("y", target); - } - - @Test - public void testRelativize_doc() { - // This is the javadoc example - String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project"); - String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"); - String expected = toOSPathString("..\\another_project\\pom.xml"); - - String actual = FileUtils.relativize(base, other); - assertEquals(expected, actual); - } - - @Test - public void testRelativize_mixedCase() { - SystemReader systemReader = SystemReader.getInstance(); - String base = toOSPathString("C:\\git\\jgit"); - String other = toOSPathString("C:\\Git\\test\\d\\f.txt"); - String expectedCaseInsensitive = toOSPathString("..\\test\\d\\f.txt"); - String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt"); - - if (systemReader.isWindows()) { - String actual = FileUtils.relativize(base, other); - assertEquals(expectedCaseInsensitive, actual); - } else if (systemReader.isMacOS()) { - String actual = FileUtils.relativize(base, other); - assertEquals(expectedCaseInsensitive, actual); - } else { - String actual = FileUtils.relativize(base, other); - assertEquals(expectedCaseSensitive, actual); - } - } - - @Test - public void testRelativize_scheme() { - String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1/file.java"); - String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project"); - // 'file.java' is treated as a folder - String expected = toOSPathString("../../project"); - - String actual = FileUtils.relativize(base, other); - assertEquals(expected, actual); - } - - @Test - public void testRelativize_equalPaths() { - String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1"); - String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1"); - String expected = ""; - - String actual = FileUtils.relativize(base, other); - assertEquals(expected, actual); - } - - @Test - public void testRelativize_whitespaces() { - String base = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1"); - String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file"); - String expected = "file"; - - String actual = FileUtils.relativize(base, other); - assertEquals(expected, actual); - } - - private String toOSPathString(String path) { - return path.replaceAll("/|\\\\", - Matcher.quoteReplacement(File.separator)); - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2016, Christian Halstrick + * 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.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandFactory; +import org.eclipse.jgit.attributes.FilterCommandRegistry; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +public class FilterCommandsTest extends RepositoryTestCase { + private Git git; + + RevCommit initialCommit; + + RevCommit secondCommit; + + class TestCommandFactory implements FilterCommandFactory { + private int prefix; + + public TestCommandFactory(int prefix) { + this.prefix = prefix; + } + + @Override + public FilterCommand create(Repository repo, InputStream in, + final OutputStream out) { + FilterCommand cmd = new FilterCommand(in, out) { + + @Override + public int run() throws IOException { + int b = in.read(); + if (b == -1) { + return b; + } + out.write(prefix); + out.write(b); + return 1; + } + }; + return cmd; + } + } + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + // commit something + writeTrashFile("Test.txt", "Hello world"); + git.add().addFilepattern("Test.txt").call(); + initialCommit = git.commit().setMessage("Initial commit").call(); + + // create a master branch and switch to it + git.branchCreate().setName("test").call(); + RefUpdate rup = db.updateRef(Constants.HEAD); + rup.link("refs/heads/test"); + + // commit something on the test branch + writeTrashFile("Test.txt", "Some change"); + git.add().addFilepattern("Test.txt").call(); + secondCommit = git.commit().setMessage("Second commit").call(); + } + + @Test + public void testBuiltinCleanFilter() + throws IOException, GitAPIException { + String builtinCommandName = "jgit://builtin/test/clean"; + FilterCommandRegistry.register(builtinCommandName, + new TestCommandFactory('c')); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "test", "clean", builtinCommandName); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=test"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("Test.txt", "Hello again"); + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + + writeTrashFile("Test.bin", "Hello again"); + git.add().addFilepattern("Test.bin").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:Hello again]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + } + + @Test + public void testBuiltinSmudgeFilter() throws IOException, GitAPIException { + String builtinCommandName = "jgit://builtin/test/smudge"; + FilterCommandRegistry.register(builtinCommandName, + new TestCommandFactory('s')); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "test", "smudge", builtinCommandName); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=test"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("Test.txt", "Hello again"); + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.txt, mode:100644, content:Hello again]", + indexState(CONTENT)); + assertEquals("Hello again", read("Test.txt")); + deleteTrashFile("Test.txt"); + git.checkout().addPath("Test.txt").call(); + assertEquals("sHseslslsos sasgsasisn", read("Test.txt")); + + writeTrashFile("Test.bin", "Hello again"); + git.add().addFilepattern("Test.bin").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:Hello again]", + indexState(CONTENT)); + deleteTrashFile("Test.bin"); + git.checkout().addPath("Test.bin").call(); + assertEquals("Hello again", read("Test.bin")); + + config.setString("filter", "test", "clean", null); + config.save(); + + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:sHseslslsos sasgsasisn]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + } + + @Test + public void testBuiltinCleanAndSmudgeFilter() throws IOException, GitAPIException { + String builtinCommandPrefix = "jgit://builtin/test/"; + FilterCommandRegistry.register(builtinCommandPrefix + "smudge", + new TestCommandFactory('s')); + FilterCommandRegistry.register(builtinCommandPrefix + "clean", + new TestCommandFactory('c')); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "test", "smudge", builtinCommandPrefix+"smudge"); + config.setString("filter", "test", "clean", + builtinCommandPrefix + "clean"); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=test"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("Test.txt", "Hello again"); + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + assertEquals("Hello again", read("Test.txt")); + deleteTrashFile("Test.txt"); + git.checkout().addPath("Test.txt").call(); + assertEquals("scsHscsescslscslscsoscs scsascsgscsascsiscsn", + read("Test.txt")); + + writeTrashFile("Test.bin", "Hello again"); + git.add().addFilepattern("Test.bin").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:cHceclclcoc cacgcacicn]", + indexState(CONTENT)); + deleteTrashFile("Test.bin"); + git.checkout().addPath("Test.bin").call(); + assertEquals("Hello again", read("Test.bin")); + + config.setString("filter", "test", "clean", null); + config.save(); + + git.add().addFilepattern("Test.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=test][Test.bin, mode:100644, content:Hello again][Test.txt, mode:100644, content:scsHscsescslscslscsoscs scsascsgscsascsiscsn]", + indexState(CONTENT)); + + config.setString("filter", "test", "clean", null); + config.save(); + } + +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2012-2013, Robin Rosenberg - * 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 static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.attribute.PosixFileAttributeView; -import java.nio.file.attribute.PosixFilePermission; -import java.util.Set; - -import org.eclipse.jgit.junit.RepositoryTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class FSJava7Test { - private File trash; - - @Before - public void setUp() throws Exception { - trash = File.createTempFile("tmp_", ""); - trash.delete(); - assertTrue("mkdir " + trash, trash.mkdir()); - } - - @After - public void tearDown() throws Exception { - FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY); - } - - /** - * The old File methods traverse symbolic links and look at the targets. - * With symbolic links we usually want to modify/look at the link. For some - * reason the executable attribute seems to always look at the target, but - * for the other attributes like lastModified, hidden and exists we must - * differ between the link and the target. - * - * @throws IOException - * @throws InterruptedException - */ - @Test - public void testSymlinkAttributes() throws IOException, InterruptedException { - FS fs = FS.DETECTED; - File link = new File(trash, "ä"); - File target = new File(trash, "å"); - fs.createSymLink(link, "å"); - assertTrue(fs.exists(link)); - String targetName = fs.readSymLink(link); - assertEquals("å", targetName); - assertTrue(fs.lastModified(link) > 0); - assertTrue(fs.exists(link)); - assertFalse(fs.canExecute(link)); - assertEquals(2, fs.length(link)); - assertFalse(fs.exists(target)); - assertFalse(fs.isFile(target)); - assertFalse(fs.isDirectory(target)); - assertFalse(fs.canExecute(target)); - - RepositoryTestCase.fsTick(link); - // Now create the link target - FileUtils.createNewFile(target); - assertTrue(fs.exists(link)); - assertTrue(fs.lastModified(link) > 0); - assertTrue(fs.lastModified(target) > fs.lastModified(link)); - assertFalse(fs.canExecute(link)); - fs.setExecute(target, true); - assertFalse(fs.canExecute(link)); - assumeTrue(fs.supportsExecute()); - assertTrue(fs.canExecute(target)); - } - - @Test - public void testExecutableAttributes() throws Exception { - FS fs = FS.DETECTED.newInstance(); - // If this assumption fails the test is halted and ignored. - assumeTrue(fs instanceof FS_POSIX); - ((FS_POSIX) fs).setUmask(0022); - - File f = new File(trash, "bla"); - assertTrue(f.createNewFile()); - assertFalse(fs.canExecute(f)); - - Set permissions = readPermissions(f); - assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); - assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE)); - assertTrue(!permissions.contains(PosixFilePermission.OWNER_EXECUTE)); - - fs.setExecute(f, true); - - permissions = readPermissions(f); - assertTrue("'owner' execute permission not set", - permissions.contains(PosixFilePermission.OWNER_EXECUTE)); - assertTrue("'group' execute permission not set", - permissions.contains(PosixFilePermission.GROUP_EXECUTE)); - assertTrue("'others' execute permission not set", - permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); - - ((FS_POSIX) fs).setUmask(0033); - fs.setExecute(f, false); - assertFalse(fs.canExecute(f)); - fs.setExecute(f, true); - - permissions = readPermissions(f); - assertTrue("'owner' execute permission not set", - permissions.contains(PosixFilePermission.OWNER_EXECUTE)); - assertFalse("'group' execute permission set", - permissions.contains(PosixFilePermission.GROUP_EXECUTE)); - assertFalse("'others' execute permission set", - permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); - } - - private Set readPermissions(File f) throws IOException { - return Files - .getFileAttributeView(f.toPath(), PosixFileAttributeView.class) - .readAttributes().permissions(); - } - -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012-2013, Robin Rosenberg + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +import org.eclipse.jgit.errors.CommandFailedException; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +public class FSTest { + private File trash; + + @Before + public void setUp() throws Exception { + trash = File.createTempFile("tmp_", ""); + trash.delete(); + assertTrue("mkdir " + trash, trash.mkdir()); + } + + @After + public void tearDown() throws Exception { + FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY); + } + + /** + * The old File methods traverse symbolic links and look at the targets. + * With symbolic links we usually want to modify/look at the link. For some + * reason the executable attribute seems to always look at the target, but + * for the other attributes like lastModified, hidden and exists we must + * differ between the link and the target. + * + * @throws IOException + * @throws InterruptedException + */ + @Test + public void testSymlinkAttributes() throws IOException, InterruptedException { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + FS fs = FS.DETECTED; + File link = new File(trash, "ä"); + File target = new File(trash, "å"); + fs.createSymLink(link, "å"); + assertTrue(fs.exists(link)); + String targetName = fs.readSymLink(link); + assertEquals("å", targetName); + assertTrue(fs.lastModified(link) > 0); + assertTrue(fs.exists(link)); + assertFalse(fs.canExecute(link)); + assertEquals(2, fs.length(link)); + assertFalse(fs.exists(target)); + assertFalse(fs.isFile(target)); + assertFalse(fs.isDirectory(target)); + assertFalse(fs.canExecute(target)); + + RepositoryTestCase.fsTick(link); + // Now create the link target + FileUtils.createNewFile(target); + assertTrue(fs.exists(link)); + assertTrue(fs.lastModified(link) > 0); + assertTrue(fs.lastModified(target) > fs.lastModified(link)); + assertFalse(fs.canExecute(link)); + fs.setExecute(target, true); + assertFalse(fs.canExecute(link)); + assumeTrue(fs.supportsExecute()); + assertTrue(fs.canExecute(target)); + } + + @Test + public void testExecutableAttributes() throws Exception { + FS fs = FS.DETECTED.newInstance(); + // If this assumption fails the test is halted and ignored. + assumeTrue(fs instanceof FS_POSIX); + ((FS_POSIX) fs).setUmask(0022); + + File f = new File(trash, "bla"); + assertTrue(f.createNewFile()); + assertFalse(fs.canExecute(f)); + + Set permissions = readPermissions(f); + assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); + assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE)); + assertTrue(!permissions.contains(PosixFilePermission.OWNER_EXECUTE)); + + fs.setExecute(f, true); + + permissions = readPermissions(f); + assertTrue("'owner' execute permission not set", + permissions.contains(PosixFilePermission.OWNER_EXECUTE)); + assertTrue("'group' execute permission not set", + permissions.contains(PosixFilePermission.GROUP_EXECUTE)); + assertTrue("'others' execute permission not set", + permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); + + ((FS_POSIX) fs).setUmask(0033); + fs.setExecute(f, false); + assertFalse(fs.canExecute(f)); + fs.setExecute(f, true); + + permissions = readPermissions(f); + assertTrue("'owner' execute permission not set", + permissions.contains(PosixFilePermission.OWNER_EXECUTE)); + assertFalse("'group' execute permission set", + permissions.contains(PosixFilePermission.GROUP_EXECUTE)); + assertFalse("'others' execute permission set", + permissions.contains(PosixFilePermission.OTHERS_EXECUTE)); + } + + private Set readPermissions(File f) throws IOException { + return Files + .getFileAttributeView(f.toPath(), PosixFileAttributeView.class) + .readAttributes().permissions(); + } + + @Test(expected = CommandFailedException.class) + public void testReadPipePosixCommandFailure() + throws CommandFailedException { + FS fs = FS.DETECTED.newInstance(); + assumeTrue(fs instanceof FS_POSIX); + + FS.readPipe(fs.userHome(), + new String[] { "/bin/sh", "-c", "exit 1" }, + Charset.defaultCharset().name()); + } + + @Test(expected = CommandFailedException.class) + public void testReadPipeCommandStartFailure() + throws CommandFailedException { + FS fs = FS.DETECTED.newInstance(); + + FS.readPipe(fs.userHome(), + new String[] { "this-command-does-not-exist" }, + Charset.defaultCharset().name()); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -54,6 +54,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.hooks.CommitMsgHook; +import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -76,6 +77,18 @@ } @Test + public void testFindPostCommitHook() throws Exception { + assumeSupportedPlatform(); + + assertNull("no hook should be installed", + FS.DETECTED.findHook(db, PostCommitHook.NAME)); + File hookFile = writeHookFile(PostCommitHook.NAME, + "#!/bin/bash\necho \"test $1 $2\""); + assertEquals("expected to find post-commit hook", hookFile, + FS.DETECTED.findHook(db, PostCommitHook.NAME)); + } + + @Test public void testFailedCommitMsgHookBlocksCommit() throws Exception { assumeSupportedPlatform(); @@ -112,7 +125,8 @@ ByteArrayOutputStream out = new ByteArrayOutputStream(); git.commit().setMessage("commit") .setHookOutputStream(new PrintStream(out)).call(); - assertEquals(".git/COMMIT_EDITMSG\n", out.toString("UTF-8")); + assertEquals(".git/COMMIT_EDITMSG\n", + out.toString("UTF-8")); } @Test @@ -132,6 +146,55 @@ } @Test + public void testPostCommitRunHook() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PostCommitHook.NAME, + "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\""); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + ProcessResult res = FS.DETECTED.runHookIfPresent(db, + PostCommitHook.NAME, + new String[] { + "arg1", "arg2" }, + new PrintStream(out), new PrintStream(err), "stdin"); + + assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n", + out.toString("UTF-8")); + assertEquals("unexpected output on stderr stream", "stderr\n", + err.toString("UTF-8")); + assertEquals("unexpected exit code", 0, res.getExitCode()); + assertEquals("unexpected process status", ProcessResult.Status.OK, + res.getStatus()); + } + + @Test + public void testAllCommitHooks() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PreCommitHook.NAME, + "#!/bin/sh\necho \"test pre-commit\"\n\necho 1>&2 \"stderr pre-commit\"\nexit 0"); + writeHookFile(CommitMsgHook.NAME, + "#!/bin/sh\necho \"test commit-msg $1\"\n\necho 1>&2 \"stderr commit-msg\"\nexit 0"); + writeHookFile(PostCommitHook.NAME, + "#!/bin/sh\necho \"test post-commit\"\necho 1>&2 \"stderr post-commit\"\nexit 0"); + Git git = Git.wrap(db); + String path = "a.txt"; + writeTrashFile(path, "content"); + git.add().addFilepattern(path).call(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + git.commit().setMessage("commit") + .setHookOutputStream(new PrintStream(out)).call(); + } catch (AbortedByHookException e) { + fail("unexpected hook failure"); + } + assertEquals("unexpected hook output", + "test pre-commit\ntest commit-msg .git/COMMIT_EDITMSG\ntest post-commit\n", + out.toString("UTF-8")); + } + + @Test public void testRunHook() throws Exception { assumeSupportedPlatform(); @@ -144,6 +207,7 @@ new String[] { "arg1", "arg2" }, new PrintStream(out), new PrintStream(err), "stdin"); + assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n", out.toString("UTF-8")); assertEquals("unexpected output on stderr stream", "stderr\n", diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -44,6 +44,7 @@ package org.eclipse.jgit.util; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -186,6 +187,16 @@ } @Test + public void testContains() { + IntList i = new IntList(); + i.add(1); + i.add(4); + assertTrue(i.contains(1)); + assertTrue(i.contains(4)); + assertFalse(i.contains(2)); + } + + @Test public void testToString() { final IntList i = new IntList(); i.add(1); diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -93,25 +93,24 @@ byte[] expectBytes = expect.getBytes(); for (int i = 0; i < 5; ++i) { byte[] buf = new byte[i]; - ByteArrayInputStream bis = new ByteArrayInputStream(inbytes); - InputStream in = new AutoCRLFInputStream(bis, true); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (i > 0) { - int n; - while ((n = in.read(buf)) >= 0) { - out.write(buf, 0, n); + try (ByteArrayInputStream bis = new ByteArrayInputStream(inbytes); + InputStream in = new AutoCRLFInputStream(bis, true); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + if (i > 0) { + int n; + while ((n = in.read(buf)) >= 0) { + out.write(buf, 0, n); + } + } else { + int c; + while ((c = in.read()) != -1) + out.write(c); } - } else { - int c; - while ((c = in.read()) != -1) - out.write(c); + out.flush(); + byte[] actualBytes = out.toByteArray(); + Assert.assertEquals("bufsize=" + i, encode(expectBytes), + encode(actualBytes)); } - out.flush(); - in.close(); - out.close(); - byte[] actualBytes = out.toByteArray(); - Assert.assertEquals("bufsize=" + i, encode(expectBytes), - encode(actualBytes)); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2010, Marc Strapetz + * Copyright (C) 2015, Ivan Motsch + * 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.io; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Test; + +public class AutoLFInputStreamTest { + + @Test + public void testLF() throws IOException { + final byte[] bytes = asBytes("1\n2\n3"); + test(bytes, bytes, false); + } + + @Test + public void testCR() throws IOException { + final byte[] bytes = asBytes("1\r2\r3"); + test(bytes, bytes, false); + } + + @Test + public void testCRLF() throws IOException { + test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false); + } + + @Test + public void testLFCR() throws IOException { + final byte[] bytes = asBytes("1\n\r2\n\r3"); + test(bytes, bytes, false); + } + + @Test + public void testEmpty() throws IOException { + final byte[] bytes = asBytes(""); + test(bytes, bytes, false); + } + + @Test + public void testBinaryDetect() throws IOException { + final byte[] bytes = asBytes("1\r\n2\r\n3\0"); + test(bytes, bytes, true); + } + + @Test + public void testBinaryDontDetect() throws IOException { + test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false); + } + + private static void test(byte[] input, byte[] expected, + boolean detectBinary) throws IOException { + final InputStream bis1 = new ByteArrayInputStream(input); + final InputStream cis1 = new AutoLFInputStream(bis1, detectBinary); + int index1 = 0; + for (int b = cis1.read(); b != -1; b = cis1.read()) { + assertEquals(expected[index1], (byte) b); + index1++; + } + + assertEquals(expected.length, index1); + + for (int bufferSize = 1; bufferSize < 10; bufferSize++) { + final byte[] buffer = new byte[bufferSize]; + final InputStream bis2 = new ByteArrayInputStream(input); + final InputStream cis2 = new AutoLFInputStream(bis2, detectBinary); + + int read = 0; + for (int readNow = cis2.read(buffer, 0, buffer.length); readNow != -1 + && read < expected.length; readNow = cis2.read(buffer, 0, + buffer.length)) { + for (int index2 = 0; index2 < readNow; index2++) { + assertEquals(expected[read + index2], buffer[index2]); + } + read += readNow; + } + + assertEquals(expected.length, read); + cis2.close(); + } + cis1.close(); + } + + private static byte[] asBytes(String in) { + return in.getBytes(UTF_8); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/EolCanonicalizingInputStreamTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2010, Marc Strapetz - * 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.io; - -import static org.junit.Assert.assertEquals; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; - -import org.junit.Test; - -public class EolCanonicalizingInputStreamTest { - - @Test - public void testLF() throws IOException { - final byte[] bytes = asBytes("1\n2\n3"); - test(bytes, bytes, false); - } - - @Test - public void testCR() throws IOException { - final byte[] bytes = asBytes("1\r2\r3"); - test(bytes, bytes, false); - } - - @Test - public void testCRLF() throws IOException { - test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false); - } - - @Test - public void testLFCR() throws IOException { - final byte[] bytes = asBytes("1\n\r2\n\r3"); - test(bytes, bytes, false); - } - - @Test - public void testEmpty() throws IOException { - final byte[] bytes = asBytes(""); - test(bytes, bytes, false); - } - - @Test - public void testBinaryDetect() throws IOException { - final byte[] bytes = asBytes("1\r\n2\r\n3\0"); - test(bytes, bytes, true); - } - - @Test - public void testBinaryDontDetect() throws IOException { - test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false); - } - - private static void test(byte[] input, byte[] expected, - boolean detectBinary) throws IOException { - final InputStream bis1 = new ByteArrayInputStream(input); - final InputStream cis1 = new EolCanonicalizingInputStream(bis1, detectBinary); - int index1 = 0; - for (int b = cis1.read(); b != -1; b = cis1.read()) { - assertEquals(expected[index1], (byte) b); - index1++; - } - - assertEquals(expected.length, index1); - - for (int bufferSize = 1; bufferSize < 10; bufferSize++) { - final byte[] buffer = new byte[bufferSize]; - final InputStream bis2 = new ByteArrayInputStream(input); - final InputStream cis2 = new EolCanonicalizingInputStream(bis2, detectBinary); - - int read = 0; - for (int readNow = cis2.read(buffer, 0, buffer.length); readNow != -1 - && read < expected.length; readNow = cis2.read(buffer, 0, - buffer.length)) { - for (int index2 = 0; index2 < readNow; index2++) { - assertEquals(expected[read + index2], buffer[index2]); - } - read += readNow; - } - - assertEquals(expected.length, read); - cis2.close(); - } - cis1.close(); - } - - private static byte[] asBytes(String in) { - try { - return in.getBytes("UTF-8"); - } catch (UnsupportedEncodingException ex) { - throw new AssertionError(); - } - } -} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -166,6 +166,7 @@ assertEquals(-1, u.read()); u.add(new ByteArrayInputStream(new byte[] { 20, 30 }) { + @Override public long skip(long n) { return 0; } @@ -180,11 +181,13 @@ final UnionInputStream u = new UnionInputStream(); final boolean closed[] = new boolean[2]; u.add(new ByteArrayInputStream(new byte[] { 1 }) { + @Override public void close() { closed[0] = true; } }); u.add(new ByteArrayInputStream(new byte[] { 2 }) { + @Override public void close() { closed[1] = true; } @@ -211,11 +214,13 @@ final UnionInputStream u = new UnionInputStream(); final boolean closed[] = new boolean[2]; u.add(new ByteArrayInputStream(new byte[] { 1 }) { + @Override public void close() { closed[0] = true; } }); u.add(new ByteArrayInputStream(new byte[] { 2 }) { + @Override public void close() { closed[1] = true; } @@ -234,6 +239,7 @@ public void testExceptionDuringClose() { final UnionInputStream u = new UnionInputStream(); u.add(new ByteArrayInputStream(new byte[] { 1 }) { + @Override public void close() throws IOException { throw new IOException("I AM A TEST"); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009, 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +public class LongMapTest { + private LongMap map; + + @Before + public void setUp() throws Exception { + map = new LongMap<>(); + } + + @Test + public void testEmptyMap() { + assertFalse(map.containsKey(0)); + assertFalse(map.containsKey(1)); + + assertNull(map.get(0)); + assertNull(map.get(1)); + + assertNull(map.remove(0)); + assertNull(map.remove(1)); + } + + @Test + public void testInsertMinValue() { + final Long min = Long.valueOf(Long.MIN_VALUE); + assertNull(map.put(Long.MIN_VALUE, min)); + assertTrue(map.containsKey(Long.MIN_VALUE)); + assertSame(min, map.get(Long.MIN_VALUE)); + assertFalse(map.containsKey(Integer.MIN_VALUE)); + } + + @Test + public void testReplaceMaxValue() { + final Long min = Long.valueOf(Long.MAX_VALUE); + final Long one = Long.valueOf(1); + assertNull(map.put(Long.MAX_VALUE, min)); + assertSame(min, map.get(Long.MAX_VALUE)); + assertSame(min, map.put(Long.MAX_VALUE, one)); + assertSame(one, map.get(Long.MAX_VALUE)); + } + + @Test + public void testRemoveOne() { + final long start = 1; + assertNull(map.put(start, Long.valueOf(start))); + assertEquals(Long.valueOf(start), map.remove(start)); + assertFalse(map.containsKey(start)); + } + + @Test + public void testRemoveCollision1() { + // This test relies upon the fact that we always >>> 1 the value + // to derive an unsigned hash code. Thus, 0 and 1 fall into the + // same hash bucket. Further it relies on the fact that we add + // the 2nd put at the top of the chain, so removing the 1st will + // cause a different code path. + // + assertNull(map.put(0, Long.valueOf(0))); + assertNull(map.put(1, Long.valueOf(1))); + assertEquals(Long.valueOf(0), map.remove(0)); + + assertFalse(map.containsKey(0)); + assertTrue(map.containsKey(1)); + } + + @Test + public void testRemoveCollision2() { + // This test relies upon the fact that we always >>> 1 the value + // to derive an unsigned hash code. Thus, 0 and 1 fall into the + // same hash bucket. Further it relies on the fact that we add + // the 2nd put at the top of the chain, so removing the 2nd will + // cause a different code path. + // + assertNull(map.put(0, Long.valueOf(0))); + assertNull(map.put(1, Long.valueOf(1))); + assertEquals(Long.valueOf(1), map.remove(1)); + + assertTrue(map.containsKey(0)); + assertFalse(map.containsKey(1)); + } + + @Test + public void testSmallMap() { + final long start = 12; + final long n = 8; + for (long i = start; i < start + n; i++) + assertNull(map.put(i, Long.valueOf(i))); + for (long i = start; i < start + n; i++) + assertEquals(Long.valueOf(i), map.get(i)); + } + + @Test + public void testLargeMap() { + final long start = Integer.MAX_VALUE; + final long n = 100000; + for (long i = start; i < start + n; i++) + assertNull(map.put(i, Long.valueOf(i))); + for (long i = start; i < start + n; i++) + assertEquals(Long.valueOf(i), map.get(i)); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, 2015 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -61,6 +61,17 @@ } @Test + public void testCompareUInt64() { + assertTrue(NB.compareUInt64(0, 0) == 0); + assertTrue(NB.compareUInt64(1, 0) > 0); + assertTrue(NB.compareUInt64(0, 1) < 0); + assertTrue(NB.compareUInt64(-1, 0) > 0); + assertTrue(NB.compareUInt64(0, -1) < 0); + assertTrue(NB.compareUInt64(-1, 1) > 0); + assertTrue(NB.compareUInt64(1, -1) < 0); + } + + @Test public void testDecodeUInt16() { assertEquals(0, NB.decodeUInt16(b(0, 0), 0)); assertEquals(0, NB.decodeUInt16(padb(3, 0, 0), 3)); @@ -79,6 +90,24 @@ } @Test + public void testDecodeUInt24() { + assertEquals(0, NB.decodeUInt24(b(0, 0, 0), 0)); + assertEquals(0, NB.decodeUInt24(padb(3, 0, 0, 0), 3)); + + assertEquals(3, NB.decodeUInt24(b(0, 0, 3), 0)); + assertEquals(3, NB.decodeUInt24(padb(3, 0, 0, 3), 3)); + + assertEquals(0xcede03, NB.decodeUInt24(b(0xce, 0xde, 3), 0)); + assertEquals(0xbade03, NB.decodeUInt24(padb(3, 0xba, 0xde, 3), 3)); + + assertEquals(0x03bade, NB.decodeUInt24(b(3, 0xba, 0xde), 0)); + assertEquals(0x03bade, NB.decodeUInt24(padb(3, 3, 0xba, 0xde), 3)); + + assertEquals(0xffffff, NB.decodeUInt24(b(0xff, 0xff, 0xff), 0)); + assertEquals(0xffffff, NB.decodeUInt24(padb(3, 0xff, 0xff, 0xff), 3)); + } + + @Test public void testDecodeInt32() { assertEquals(0, NB.decodeInt32(b(0, 0, 0, 0), 0)); assertEquals(0, NB.decodeInt32(padb(3, 0, 0, 0, 0), 3)); @@ -187,6 +216,39 @@ } @Test + public void testEncodeInt24() { + byte[] out = new byte[16]; + + prepareOutput(out); + NB.encodeInt24(out, 0, 0); + assertOutput(b(0, 0, 0), out, 0); + + prepareOutput(out); + NB.encodeInt24(out, 3, 0); + assertOutput(b(0, 0, 0), out, 3); + + prepareOutput(out); + NB.encodeInt24(out, 0, 3); + assertOutput(b(0, 0, 3), out, 0); + + prepareOutput(out); + NB.encodeInt24(out, 3, 3); + assertOutput(b(0, 0, 3), out, 3); + + prepareOutput(out); + NB.encodeInt24(out, 0, 0xc0deac); + assertOutput(b(0xc0, 0xde, 0xac), out, 0); + + prepareOutput(out); + NB.encodeInt24(out, 3, 0xbadeac); + assertOutput(b(0xba, 0xde, 0xac), out, 3); + + prepareOutput(out); + NB.encodeInt24(out, 3, -1); + assertOutput(b(0xff, 0xff, 0xff), out, 3); + } + + @Test public void testEncodeInt32() { final byte[] out = new byte[16]; @@ -304,10 +366,24 @@ return r; } + private static byte[] b(int a, int b, int c) { + return new byte[] { (byte) a, (byte) b, (byte) c }; + } + private static byte[] b(final int a, final int b, final int c, final int d) { return new byte[] { (byte) a, (byte) b, (byte) c, (byte) d }; } + private static byte[] padb(int len, int a, int b, int c) { + final byte[] r = new byte[len + 4]; + for (int i = 0; i < len; i++) + r[i] = (byte) 0xaf; + r[len] = (byte) a; + r[len + 1] = (byte) b; + r[len + 2] = (byte) c; + return r; + } + private static byte[] padb(final int len, final int a, final int b, final int c, final int d) { final byte[] r = new byte[len + 4]; diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016, 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.eclipse.jgit.util.Paths.compare; +import static org.eclipse.jgit.util.Paths.compareSameName; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.junit.Test; + +public class PathsTest { + @Test + public void testStripTrailingSeparator() { + assertNull(Paths.stripTrailingSeparator(null)); + assertEquals("", Paths.stripTrailingSeparator("")); + assertEquals("a", Paths.stripTrailingSeparator("a")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo/")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo//")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo///")); + } + + @Test + public void testPathCompare() { + byte[] a = Constants.encode("afoo/bar.c"); + byte[] b = Constants.encode("bfoo/bar.c"); + + assertEquals(0, compare(a, 1, a.length, 0, b, 1, b.length, 0)); + assertEquals(-1, compare(a, 0, a.length, 0, b, 0, b.length, 0)); + assertEquals(1, compare(b, 0, b.length, 0, a, 0, a.length, 0)); + + a = Constants.encode("a"); + b = Constants.encode("aa"); + assertEquals(-97, compare(a, 0, a.length, 0, b, 0, b.length, 0)); + assertEquals(0, compare(a, 0, a.length, 0, b, 0, 1, 0)); + assertEquals(0, compare(a, 0, a.length, 0, b, 1, 2, 0)); + assertEquals(0, compareSameName(a, 0, a.length, b, 1, b.length, 0)); + assertEquals(0, compareSameName(a, 0, a.length, b, 0, 1, 0)); + assertEquals(-50, compareSameName(a, 0, a.length, b, 0, b.length, 0)); + assertEquals(97, compareSameName(b, 0, b.length, a, 0, a.length, 0)); + + a = Constants.encode("a"); + b = Constants.encode("a"); + assertEquals(0, compare( + a, 0, a.length, FileMode.TREE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(0, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + assertEquals(-47, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(47, compare( + a, 0, a.length, FileMode.TREE.getBits(), + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + + assertEquals(0, compareSameName( + a, 0, a.length, + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(0, compareSameName( + a, 0, a.length, + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + + a = Constants.encode("a.c"); + b = Constants.encode("a"); + byte[] c = Constants.encode("a0c"); + assertEquals(-1, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(-1, compare( + b, 0, b.length, FileMode.TREE.getBits(), + c, 0, c.length, FileMode.REGULAR_FILE.getBits())); + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,14 +43,13 @@ package org.eclipse.jgit.util; +import static java.nio.charset.StandardCharsets.ISO_8859_1; import static org.eclipse.jgit.util.QuotedString.GIT_PATH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import java.io.UnsupportedEncodingException; - import org.eclipse.jgit.lib.Constants; import org.junit.Test; @@ -63,12 +62,7 @@ } private static void assertDequote(final String exp, final String in) { - final byte[] b; - try { - b = ('"' + in + '"').getBytes("ISO-8859-1"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + final byte[] b = ('"' + in + '"').getBytes(ISO_8859_1); final String r = GIT_PATH.dequote(b, 0, b.length); assertEquals(exp, r); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawCharUtilTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawCharUtilTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawCharUtilTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawCharUtilTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,6 +43,7 @@ package org.eclipse.jgit.util; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.eclipse.jgit.util.RawCharUtil.isWhitespace; import static org.eclipse.jgit.util.RawCharUtil.trimLeadingWhitespace; import static org.eclipse.jgit.util.RawCharUtil.trimTrailingWhitespace; @@ -50,8 +51,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.UnsupportedEncodingException; - import org.junit.Test; public class RawCharUtilTest { @@ -78,37 +77,31 @@ /** * Test method for * {@link RawCharUtil#trimTrailingWhitespace(byte[], int, int)}. - * - * @throws UnsupportedEncodingException */ @Test - public void testTrimTrailingWhitespace() - throws UnsupportedEncodingException { - assertEquals(0, trimTrailingWhitespace("".getBytes("US-ASCII"), 0, 0)); - assertEquals(0, trimTrailingWhitespace(" ".getBytes("US-ASCII"), 0, 1)); - assertEquals(1, trimTrailingWhitespace("a ".getBytes("US-ASCII"), 0, 2)); - assertEquals(2, - trimTrailingWhitespace(" a ".getBytes("US-ASCII"), 0, 3)); - assertEquals(3, - trimTrailingWhitespace(" a".getBytes("US-ASCII"), 0, 3)); - assertEquals(6, trimTrailingWhitespace( - " test ".getBytes("US-ASCII"), 2, 9)); + public void testTrimTrailingWhitespace() { + assertEquals(0, trimTrailingWhitespace("".getBytes(US_ASCII), 0, 0)); + assertEquals(0, trimTrailingWhitespace(" ".getBytes(US_ASCII), 0, 1)); + assertEquals(1, trimTrailingWhitespace("a ".getBytes(US_ASCII), 0, 2)); + assertEquals(2, trimTrailingWhitespace(" a ".getBytes(US_ASCII), 0, 3)); + assertEquals(3, trimTrailingWhitespace(" a".getBytes(US_ASCII), 0, 3)); + assertEquals(6, + trimTrailingWhitespace(" test ".getBytes(US_ASCII), 2, 9)); } /** * Test method for * {@link RawCharUtil#trimLeadingWhitespace(byte[], int, int)}. - * - * @throws UnsupportedEncodingException */ @Test - public void testTrimLeadingWhitespace() throws UnsupportedEncodingException { - assertEquals(0, trimLeadingWhitespace("".getBytes("US-ASCII"), 0, 0)); - assertEquals(1, trimLeadingWhitespace(" ".getBytes("US-ASCII"), 0, 1)); - assertEquals(0, trimLeadingWhitespace("a ".getBytes("US-ASCII"), 0, 2)); - assertEquals(1, trimLeadingWhitespace(" a ".getBytes("US-ASCII"), 0, 3)); - assertEquals(2, trimLeadingWhitespace(" a".getBytes("US-ASCII"), 0, 3)); - assertEquals(2, trimLeadingWhitespace(" test ".getBytes("US-ASCII"), + public void testTrimLeadingWhitespace() { + assertEquals(0, trimLeadingWhitespace("".getBytes(US_ASCII), 0, 0)); + assertEquals(1, trimLeadingWhitespace(" ".getBytes(US_ASCII), 0, 1)); + assertEquals(0, trimLeadingWhitespace("a ".getBytes(US_ASCII), 0, 2)); + assertEquals(1, trimLeadingWhitespace(" a ".getBytes(US_ASCII), 0, 3)); + assertEquals(2, trimLeadingWhitespace(" a".getBytes(US_ASCII), 0, 3)); + assertEquals(2, + trimLeadingWhitespace(" test ".getBytes(US_ASCII), 2, 9)); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -43,11 +43,10 @@ package org.eclipse.jgit.util; -import static org.junit.Assert.assertEquals; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotNull; -import java.io.UnsupportedEncodingException; - import org.junit.Test; public class RawParseUtils_LineMapTest { @@ -55,52 +54,51 @@ public void testEmpty() { final IntList map = RawParseUtils.lineMap(new byte[] {}, 0, 0); assertNotNull(map); - assertEquals(2, map.size()); - assertEquals(Integer.MIN_VALUE, map.get(0)); - assertEquals(0, map.get(1)); + assertArrayEquals(new int[]{Integer.MIN_VALUE, 0}, asInts(map)); } @Test public void testOneBlankLine() { final IntList map = RawParseUtils.lineMap(new byte[] { '\n' }, 0, 1); - assertEquals(3, map.size()); - assertEquals(Integer.MIN_VALUE, map.get(0)); - assertEquals(0, map.get(1)); - assertEquals(1, map.get(2)); + assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 1}, asInts(map)); } @Test - public void testTwoLineFooBar() throws UnsupportedEncodingException { - final byte[] buf = "foo\nbar\n".getBytes("ISO-8859-1"); + public void testTwoLineFooBar() { + final byte[] buf = "foo\nbar\n".getBytes(ISO_8859_1); final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); - assertEquals(4, map.size()); - assertEquals(Integer.MIN_VALUE, map.get(0)); - assertEquals(0, map.get(1)); - assertEquals(4, map.get(2)); - assertEquals(buf.length, map.get(3)); + assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map)); } @Test - public void testTwoLineNoLF() throws UnsupportedEncodingException { - final byte[] buf = "foo\nbar".getBytes("ISO-8859-1"); + public void testTwoLineNoLF() { + final byte[] buf = "foo\nbar".getBytes(ISO_8859_1); final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); - assertEquals(4, map.size()); - assertEquals(Integer.MIN_VALUE, map.get(0)); - assertEquals(0, map.get(1)); - assertEquals(4, map.get(2)); - assertEquals(buf.length, map.get(3)); + assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map)); + } + + @Test + public void testBinary() { + final byte[] buf = "xxxfoo\nb\0ar".getBytes(ISO_8859_1); + final IntList map = RawParseUtils.lineMap(buf, 3, buf.length); + assertArrayEquals(new int[]{Integer.MIN_VALUE, 3, buf.length}, asInts(map)); } @Test - public void testFourLineBlanks() throws UnsupportedEncodingException { - final byte[] buf = "foo\n\n\nbar\n".getBytes("ISO-8859-1"); + public void testFourLineBlanks() { + final byte[] buf = "foo\n\n\nbar\n".getBytes(ISO_8859_1); final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); - assertEquals(6, map.size()); - assertEquals(Integer.MIN_VALUE, map.get(0)); - assertEquals(0, map.get(1)); - assertEquals(4, map.get(2)); - assertEquals(5, map.get(3)); - assertEquals(6, map.get(4)); - assertEquals(buf.length, map.get(5)); + + assertArrayEquals(new int[]{ + Integer.MIN_VALUE, 0, 4, 5, 6, buf.length + }, asInts(map)); + } + + private int[] asInts(IntList l) { + int[] result = new int[l.size()]; + for (int i = 0; i < l.size(); i++) { + result[i] = l.get(i); + } + return result; } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,11 +42,10 @@ */ package org.eclipse.jgit.util; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.io.UnsupportedEncodingException; - import org.eclipse.jgit.junit.RepositoryTestCase; import org.junit.Test; @@ -94,11 +93,7 @@ } private static RawCharSequence raw(String text) { - try { - byte[] bytes = text.getBytes("UTF-8"); - return new RawCharSequence(bytes, 0, bytes.length); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + byte[] bytes = text.getBytes(UTF_8); + return new RawCharSequence(bytes, 0, bytes.length); } } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,7 @@ import org.junit.Test; public class ReadLinesTest { - List l = new ArrayList(); + List l = new ArrayList<>(); @Before public void clearList() { diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -89,7 +89,7 @@ @Test public void testEmptyBuilder() { - RefList list = new RefList.Builder().toRefList(); + RefList list = new RefList.Builder<>().toRefList(); assertEquals(0, list.size()); assertFalse(list.iterator().hasNext()); assertEquals(-1, list.find("a")); @@ -111,7 +111,7 @@ @Test public void testBuilder_AddThenSort() { - RefList.Builder builder = new RefList.Builder(1); + RefList.Builder builder = new RefList.Builder<>(1); builder.add(REF_B); builder.add(REF_A); @@ -129,7 +129,7 @@ @Test public void testBuilder_AddAll() { - RefList.Builder builder = new RefList.Builder(1); + RefList.Builder builder = new RefList.Builder<>(1); Ref[] src = { REF_A, REF_B, REF_c, REF_A }; builder.addAll(src, 1, 2); @@ -141,7 +141,7 @@ @Test public void testBuilder_Set() { - RefList.Builder builder = new RefList.Builder(); + RefList.Builder builder = new RefList.Builder<>(); builder.add(REF_A); builder.add(REF_A); @@ -163,7 +163,7 @@ @Test public void testBuilder_Remove() { - RefList.Builder builder = new RefList.Builder(); + RefList.Builder builder = new RefList.Builder<>(); builder.add(REF_A); builder.add(REF_B); builder.remove(0); @@ -364,7 +364,7 @@ exp.append(REF_B); exp.append("]"); - RefList.Builder list = new RefList.Builder(); + RefList.Builder list = new RefList.Builder<>(); list.add(REF_A); list.add(REF_B); assertEquals(exp.toString(), list.toString()); @@ -442,16 +442,16 @@ @Test public void testCopyConstructorReusesArray() { - RefList.Builder one = new RefList.Builder(); + RefList.Builder one = new RefList.Builder<>(); one.add(REF_A); - RefList two = new RefList(one.toRefList()); + RefList two = new RefList<>(one.toRefList()); one.set(0, REF_B); assertSame(REF_B, two.get(0)); } private static RefList toList(Ref... refs) { - RefList.Builder b = new RefList.Builder(refs.length); + RefList.Builder b = new RefList.Builder<>(refs.length); b.addAll(refs, 0, refs.length); return b.toRefList(); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -481,7 +481,7 @@ } private static RefList toList(Ref... refs) { - RefList.Builder b = new RefList.Builder(refs.length); + RefList.Builder b = new RefList.Builder<>(refs.length); b.addAll(refs, 0, refs.length); return b.toRefList(); } diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -42,17 +42,16 @@ */ package org.eclipse.jgit.util; -import static org.junit.Assert.assertEquals; -import static org.eclipse.jgit.util.RelativeDateFormatter.YEAR_IN_MILLIS; -import static org.eclipse.jgit.util.RelativeDateFormatter.SECOND_IN_MILLIS; -import static org.eclipse.jgit.util.RelativeDateFormatter.MINUTE_IN_MILLIS; -import static org.eclipse.jgit.util.RelativeDateFormatter.HOUR_IN_MILLIS; import static org.eclipse.jgit.util.RelativeDateFormatter.DAY_IN_MILLIS; +import static org.eclipse.jgit.util.RelativeDateFormatter.HOUR_IN_MILLIS; +import static org.eclipse.jgit.util.RelativeDateFormatter.MINUTE_IN_MILLIS; +import static org.eclipse.jgit.util.RelativeDateFormatter.SECOND_IN_MILLIS; +import static org.eclipse.jgit.util.RelativeDateFormatter.YEAR_IN_MILLIS; +import static org.junit.Assert.assertEquals; import java.util.Date; import org.eclipse.jgit.junit.MockSystemReader; -import org.eclipse.jgit.util.RelativeDateFormatter; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -129,7 +128,14 @@ assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago"); assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago"); assertFormat(2, YEAR_IN_MILLIS, "2 years ago"); - assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago"); + } + + @Test + public void testFullYearMissingSomeDays() { + // avoid "x year(s), 12 months", as humans would always round this up to + // "x+1 years" + assertFormat(5 * 365 + 1, DAY_IN_MILLIS, "5 years ago"); + assertFormat(2 * 365 - 10, DAY_IN_MILLIS, "2 years ago"); } @Test diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.util.FS.ExecutionResult; +import org.junit.Before; +import org.junit.Test; + +public class RunExternalScriptTest { + private static final String LF = "\n"; + + private ByteArrayOutputStream out; + + private ByteArrayOutputStream err; + + @Before + public void setUp() throws Exception { + out = new ByteArrayOutputStream(); + err = new ByteArrayOutputStream(); + } + + @Test + public void testCopyStdIn() throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("cat -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath()), out, err, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(0, rc); + assertEquals(inputStr, new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testCopyNullStdIn() throws IOException, InterruptedException { + File script = writeTempFile("cat -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath()), out, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testArguments() throws IOException, InterruptedException { + File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", + script.getPath(), "a", "b", "c"), out, err, (InputStream) null); + assertEquals(0, rc); + assertEquals("3,a,b,c,,,\n", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testRc() throws IOException, InterruptedException { + File script = writeTempFile("exit 3"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), + out, err, (InputStream) null); + assertEquals(3, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testNullStdout() throws IOException, InterruptedException { + File script = writeTempFile("echo hi"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath()), null, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testStdErr() throws IOException, InterruptedException { + File script = writeTempFile("echo hi >&2"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath()), null, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("hi" + LF, new String(err.toByteArray())); + } + + @Test + public void testAllTogetherBin() throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), + out, err, new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(5, rc); + assertEquals(inputStr, new String(out.toByteArray())); + assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray())); + } + + @Test(expected = IOException.class) + public void testWrongSh() throws IOException, InterruptedException { + File script = writeTempFile("cat -"); + FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh-foo", script.getPath(), "a", "b", + "c"), out, err, (InputStream) null); + } + + @Test + public void testWrongScript() throws IOException, InterruptedException { + File script = writeTempFile("cat-foo -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), + out, err, (InputStream) null); + assertEquals(127, rc); + } + + @Test + public void testCopyStdInExecute() + throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("cat -"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath()); + ExecutionResult res = FS.DETECTED.execute(pb, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(0, res.getRc()); + assertEquals(inputStr, new String(res.getStdout().toByteArray())); + assertEquals("", new String(res.getStderr().toByteArray())); + } + + @Test + public void testStdErrExecute() throws IOException, InterruptedException { + File script = writeTempFile("echo hi >&2"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath()); + ExecutionResult res = FS.DETECTED.execute(pb, null); + assertEquals(0, res.getRc()); + assertEquals("", new String(res.getStdout().toByteArray())); + assertEquals("hi" + LF, new String(res.getStderr().toByteArray())); + } + + @Test + public void testAllTogetherBinExecute() + throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile( + "echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath(), "a", + "b", "c"); + ExecutionResult res = FS.DETECTED.execute(pb, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(5, res.getRc()); + assertEquals(inputStr, new String(res.getStdout().toByteArray())); + assertEquals("3,a,b,c,,," + LF, + new String(res.getStderr().toByteArray())); + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("RunProcessTestScript_", ""); + JGitTestUtil.write(f, body); + return f; + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java --- jgit-4.1.2/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2017, 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.sha1; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.junit.Test; + +public class SHA1Test { + private static final String TEST1 = "abc"; + + private static final String TEST2a = "abcdbcdecdefdefgefghfghighijhi"; + private static final String TEST2b = "jkijkljklmklmnlmnomnopnopq"; + private static final String TEST2 = TEST2a + TEST2b; + + @Test + public void test0() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(new byte[] {}); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(new byte[] {}); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + s.reset(); + s.update(new byte[] {}); + ObjectId s2 = s.toObjectId(); + + assertEquals(m1, s1); + assertEquals(exp, s1); + assertEquals(exp, s2); + } + + @Test + public void test1() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("a9993e364706816aba3e25717850c26c9cd0d89d"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(TEST1.getBytes(UTF_8)); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(TEST1.getBytes(UTF_8)); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + s.reset(); + s.update(TEST1.getBytes(UTF_8)); + ObjectId s2 = s.toObjectId(); + + assertEquals(m1, s1); + assertEquals(exp, s1); + assertEquals(exp, s2); + } + + @Test + public void test2() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(TEST2.getBytes(UTF_8)); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(TEST2.getBytes(UTF_8)); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + s.reset(); + s.update(TEST2.getBytes(UTF_8)); + ObjectId s2 = s.toObjectId(); + + assertEquals(m1, s1); + assertEquals(exp, s1); + assertEquals(exp, s2); + } + + @Test + public void shatteredCollision() + throws IOException, NoSuchAlgorithmException { + byte[] pdf1 = read("shattered-1.pdf", 422435); + byte[] pdf2 = read("shattered-2.pdf", 422435); + MessageDigest md; + SHA1 s; + + // SHAttered attack generated these PDFs to have identical SHA-1. + ObjectId bad = ObjectId + .fromString("38762cf7f55934b34d179ae6a4c80cadccbb7f0a"); + md = MessageDigest.getInstance("SHA-1"); + md.update(pdf1); + assertEquals("shattered-1 collides", bad, + ObjectId.fromRaw(md.digest())); + s = SHA1.newInstance().setDetectCollision(false); + s.update(pdf1); + assertEquals("shattered-1 collides", bad, s.toObjectId()); + + md = MessageDigest.getInstance("SHA-1"); + md.update(pdf2); + assertEquals("shattered-2 collides", bad, + ObjectId.fromRaw(md.digest())); + s = SHA1.newInstance().setDetectCollision(false); + s.update(pdf2); + assertEquals("shattered-2 collides", bad, s.toObjectId()); + + // SHA1 with detectCollision shouldn't be fooled. + s = SHA1.newInstance().setDetectCollision(true); + s.update(pdf1); + try { + s.digest(); + fail("expected " + Sha1CollisionException.class.getSimpleName()); + } catch (Sha1CollisionException e) { + assertEquals(e.getMessage(), + "SHA-1 collision detected on " + bad.name()); + } + + s = SHA1.newInstance().setDetectCollision(true); + s.update(pdf2); + try { + s.digest(); + fail("expected " + Sha1CollisionException.class.getSimpleName()); + } catch (Sha1CollisionException e) { + assertEquals(e.getMessage(), + "SHA-1 collision detected on " + bad.name()); + } + } + + @Test + public void shatteredStoredInGitBlob() throws IOException { + byte[] pdf1 = read("shattered-1.pdf", 422435); + byte[] pdf2 = read("shattered-2.pdf", 422435); + + // Although the prior test detects the chance of a collision, adding + // the Git blob header permutes the data enough for this specific + // attack example to not be detected as a collision. (A different file + // pair that takes the Git header into account however, would.) + ObjectId id1 = blob(pdf1, SHA1.newInstance().setDetectCollision(true)); + ObjectId id2 = blob(pdf2, SHA1.newInstance().setDetectCollision(true)); + + assertEquals( + ObjectId.fromString("ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0"), + id1); + assertEquals( + ObjectId.fromString("b621eeccd5c7edac9b7dcba35a8d5afd075e24f2"), + id2); + } + + @Test + public void detectsShatteredByDefault() throws IOException { + assumeTrue(System.getProperty("org.eclipse.jgit.util.sha1.detectCollision") == null); + assumeTrue(System.getProperty("org.eclipse.jgit.util.sha1.safeHash") == null); + + byte[] pdf1 = read("shattered-1.pdf", 422435); + SHA1 s = SHA1.newInstance(); + s.update(pdf1); + try { + s.digest(); + fail("expected " + Sha1CollisionException.class.getSimpleName()); + } catch (Sha1CollisionException e) { + assertTrue("shattered-1 detected", true); + } + } + + private static ObjectId blob(byte[] pdf1, SHA1 s) { + s.update(Constants.encodedTypeString(Constants.OBJ_BLOB)); + s.update((byte) ' '); + s.update(Constants.encodeASCII(pdf1.length)); + s.update((byte) 0); + s.update(pdf1); + return s.toObjectId(); + } + + private byte[] read(String name, int sizeHint) throws IOException { + try (InputStream in = getClass().getResourceAsStream(name)) { + ByteBuffer buf = IO.readWholeStream(in, sizeHint); + byte[] r = new byte[buf.remaining()]; + buf.get(r); + return r; + } + } +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,48 @@ +# +# See WalkEncryptionTest.java +# +# This file is a template for test configuration file used by WalkEncryptionTest. +# To be active, this file must have the following hard coded name: jgit-s3-config.properties +# To be active, this file must be discovered by WalkEncryptionTest from one of these locations: +# * ${user.home}/jgit-s3-config.properties +# * ${user.dir}/jgit-s3-config.properties +# * ${user.dir}/tst-rsrc/jgit-s3-config.properties +# When this file is missing, tests in WalkEncryptionTest will not run, only report a warning. +# + +# +# WalkEncryptionTest requires amazon s3 test bucket setup. +# +# Test bucket setup instructions: +# +# Create IAM user: +# http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html +# * user name: jgit.eclipse.org +# +# Configure IAM user S3 bucket access +# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html +# * attach S3 user policy to user account: jgit-s3-config.policy.user.json +# +# Create S3 bucket: +# http://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html +# * bucket name: jgit.eclipse.org +# +# Configure S3 bucket source address/mask access: +# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html +# * attach bucket policy to the test bucket: jgit-s3-config.policy.bucket.json +# * verify that any required source address/mask is included in the bucket policy: +# * see https://wiki.eclipse.org/Hudson +# * see http://www.tcpiputils.com/browse/ip-address/198.41.30.200 +# * proxy.eclipse.org 198.41.30.0/24 +# * Andrei Pozolotin 67.175.188.187/32 +# +# Configure bucket 1 day expiration in object life cycle management: +# * https://docs.aws.amazon.com/AmazonS3/latest/dev/manage-lifecycle-using-console.html +# + +# Test bucket name +test.bucket=jgit.eclipse.org + +# IAM credentials for user jgit.eclipse.org +accesskey=AKIAIYWXB4ETREBRMZDQ +secretkey=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 diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,20 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyAllButKnownSourceAddressWithMask", + "Effect": "Deny", + "Principal": "*", + "Action": "s3:*", + "Resource": "arn:aws:s3:::jgit.eclipse.org/*", + "Condition": { + "NotIpAddress": { + "aws:SourceIp": [ + "198.41.30.0/24", + "67.175.188.187/32" + ] + } + } + } + ] +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,24 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "BucketList", + "Effect": "Allow", + "Action": "s3:ListAllMyBuckets", + "Resource": [ + "arn:aws:s3:::jgit.eclipse.org" + ] + }, + { + "Sid": "BucketFullControl", + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::jgit.eclipse.org", + "arn:aws:s3:::jgit.eclipse.org/*" + ] + } + ] +} diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,11 @@ +# +# Sample Amazon S3 connection configuration file, Version 0. +# Version 0 (or lack of version) will produce JetS3tV2 compatible encryption. +# JetS3tV2 supports only PBE algorithms, with partially compromised AES mode. +# + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +crypto.algorithm = PBEWithMD5AndDES +password = secret diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,14 @@ +# +# Sample Amazon S3 connection configuration file, Version 1. +# Version 1 will produce JGitV1 compatible encryption. +# It is JetS3tV2-like mode with proper AES support. +# JGitV1 uses hard coded encryption parameters. +# JGitV1 supports only PBE algorithms. +# + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +crypto.algorithm = PBEWithHmacSHA1AndAES_128 +crypto.version = 1 +password = secret diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,48 @@ +# +# Sample Amazon S3 connection configuration file, Version 2. +# Version 2 will produce JGitV2 compatible encryption. +# JGitV2 introduces more flexible control over cipher and key factory parameters. +# JGitV2 hides actual cipher/key algorithms inside the encryption profile. +# JGitV2 does not use any hard coded encryption parameters. +# JGitV2 supports both PBE and Non-PBE algorithms. + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +# In Version 2 "crypto.algorithm" is a reference to the encryption "profile". +crypto.algorithm = custom +crypto.version = 2 +password = secret + +# +# Encryption profile is a collection of related properties, +# all having common property root name, or prefix: +# +# Cipher algorithm. +custom.algo = AES/CBC/PKCS5Padding +# Key factory algorithm. +custom.key.algo = PBKDF2WithHmacSHA512 +# Key size, bits. +custom.key.size = 256 +# Number of key generation iterations. +custom.key.iter = 50000 +# Salt used in key generation (hex value, white space OK). +custom.key.salt = e2 55 89 67 8e 8d e8 4c + +# Same file can store multiple profiles. +# Only one profile can be active at a time. +# Active profile is selected via "crypto.algorithm" + +# +# Here is how to create V1 encryption in V2 format: +# +# Cipher algorithm. +legacy.algo = PBEWithHmacSHA1AndAES_128 +# Key factory algorithm. +legacy.key.algo = PBEWithHmacSHA1AndAES_128 +# Key size, bits. +legacy.key.size = 32 +# Number of key generation iterations. +legacy.key.iter = 5000 +# Salt used in key generation (hex value, white space OK). +legacy.key.salt = A40BC834D695F313 diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/log4j.properties jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/log4j.properties --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/log4j.properties 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/log4j.properties 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ + +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n Binary files /tmp/tmpQGZVOL/sibfYI5CmC/jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif and /tmp/tmpQGZVOL/jcv3oOJ6sW/jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif differ Binary files /tmp/tmpQGZVOL/sibfYI5CmC/jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif and /tmp/tmpQGZVOL/jcv3oOJ6sW/jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif differ diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ +diff --git a/M1 b/M1 +new file mode 100755 +index 0000000..de98044 +--- /dev/null ++++ b/M1 +@@ -0,0 +1,3 @@ ++a ++b ++c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M1_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +a +b +c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,10 @@ +diff --git a/M2 b/M2 +old mode 100644 +new mode 100755 +index 0000000..de98044 +--- a/M2 ++++ b/M2 +@@ -1,3 +1,1 @@ + a +-b +-c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +a diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M2_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +a +b +c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,10 @@ +diff --git a/M3 b/M3 +old mode 100755 +new mode 100644 +index 0000000..de98044 +--- a/M3 ++++ b/M3 +@@ -1,1 +1,3 @@ + a ++b ++c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,3 @@ +a +b +c diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/M3_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +a diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ +diff --git a/NonASCII2 b/NonASCII2 +index 2e65efe..7898192 100644 +--- a/NonASCII2 ++++ b/NonASCII2 +@@ -1 +1 @@ +-你好, 世界! +\ No newline at end of file ++再见 +\ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +再见 \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII2_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +你好, 世界! \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ +diff --git a/NonASCIIAdd2 b/NonASCIIAdd2 +new file mode 100644 +index 2e65efe..7898192 100644 +--- /dev/null ++++ b/NonASCIIAdd2 +@@ -0,0 +1 @@ ++あ +\ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd2_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +あ \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,7 @@ +diff --git a/NonASCIIAdd b/NonASCIIAdd +index 2e65efe..7898192 100644 +--- a/NonASCIIAdd ++++ b/NonASCIIAdd +@@ -0,0 +1 @@ ++あ +\ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIAdd_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +あ \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ +diff --git a/NonASCIIDel b/NonASCIIDel +deleted file mode 100644 +new file mode 100644 +index 2e65efe..7898192 100644 +--- a/NonASCIIDel ++++ /dev/null +@@ -1 +0,0 @@ +-あ +\ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCIIDel_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +あ \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,8 @@ +diff --git a/NonASCII b/NonASCII +index 2e65efe..7898192 100644 +--- a/NonASCII ++++ b/NonASCII +@@ -1 +1 @@ +-あ +\ No newline at end of file ++あ diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PostImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +あ diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/NonASCII_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +あ \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W.patch jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W.patch --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W.patch 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W.patch 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,7 @@ +diff --git a/W b/W +index a3648a1..2d44096 100644 +--- a/W ++++ b/W +@@ -1 +0,0 @@ +-a +\ No newline at end of file \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W_PreImage jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W_PreImage --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W_PreImage 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/W_PreImage 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1 @@ +a \ No newline at end of file diff -Nru jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/indexdiff/filerepo.txt jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/indexdiff/filerepo.txt --- jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/indexdiff/filerepo.txt 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/indexdiff/filerepo.txt 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,30 @@ +blob +mark :1 +data 10 +äéü.txt +blob +mark :2 +data 8 +test.txt +blob +mark :3 +data 5 +Test + +blob +mark :4 +data 7 +äéü + +reset refs/heads/master +commit refs/heads/master +mark :5 +author A U Thor 1450727513 +0100 +committer A U Thor 1450727513 +0100 +data 26 +Initial commit with links +M 120000 :1 testfolder/aeu.txt +M 120000 :2 testfolder/link.txt +M 100644 :3 testfolder/test.txt +M 100644 :4 testfolder/äéü.txt + Binary files /tmp/tmpQGZVOL/sibfYI5CmC/jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-1.pdf and /tmp/tmpQGZVOL/jcv3oOJ6sW/jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-1.pdf differ Binary files /tmp/tmpQGZVOL/sibfYI5CmC/jgit-4.1.2/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-2.pdf and /tmp/tmpQGZVOL/jcv3oOJ6sW/jgit-4.11.9/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-2.pdf differ diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/BUILD jgit-4.11.9/org.eclipse.jgit.ui/BUILD --- jgit-4.1.2/org.eclipse.jgit.ui/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/BUILD 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "ui", + srcs = glob(["src/**/*.java"]), + resource_strip_prefix = "org.eclipse.jgit.ui/resources", + resources = glob(["resources/**"]), + deps = ["//org.eclipse.jgit:jgit"], +) diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/.classpath jgit-4.11.9/org.eclipse.jgit.ui/.classpath --- jgit-4.1.2/org.eclipse.jgit.ui/.classpath 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/.classpath 2019-09-03 12:37:49.000000000 +0000 @@ -1,6 +1,6 @@ - + diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/META-INF/MANIFEST.MF jgit-4.11.9/org.eclipse.jgit.ui/META-INF/MANIFEST.MF --- jgit-4.1.2/org.eclipse.jgit.ui/META-INF/MANIFEST.MF 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/META-INF/MANIFEST.MF 2019-09-03 12:37:49.000000000 +0000 @@ -2,15 +2,16 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name +Automatic-Module-Name: org.eclipse.jgit.ui Bundle-SymbolicName: org.eclipse.jgit.ui -Bundle-Version: 4.1.2.201602141800-r +Bundle-Version: 4.11.9.201909030838-r Bundle-Vendor: %provider_name -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -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)" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Export-Package: org.eclipse.jgit.awtui;version="4.11.9" +Import-Package: org.eclipse.jgit.errors;version="[4.11.9,4.12.0)", + org.eclipse.jgit.lib;version="[4.11.9,4.12.0)", + org.eclipse.jgit.nls;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revplot;version="[4.11.9,4.12.0)", + org.eclipse.jgit.revwalk;version="[4.11.9,4.12.0)", + org.eclipse.jgit.transport;version="[4.11.9,4.12.0)", + org.eclipse.jgit.util;version="[4.11.9,4.12.0)" diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/pom.xml jgit-4.11.9/org.eclipse.jgit.ui/pom.xml --- jgit-4.1.2/org.eclipse.jgit.ui/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -52,7 +52,7 @@ org.eclipse.jgit org.eclipse.jgit-parent - 4.1.2.201602141800-r + 4.11.9.201909030838-r org.eclipse.jgit.ui diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs --- jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 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 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -25,7 +25,7 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=warning @@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore @@ -76,7 +76,7 @@ org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore @@ -99,6 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=error org.eclipse.jdt.core.compiler.problem.unusedLabel=error org.eclipse.jdt.core.compiler.problem.unusedLocal=error @@ -111,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs --- jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -9,21 +9,23 @@ org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=true @@ -39,11 +41,12 @@ sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true sp_cleanup.remove_unused_imports=false sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true @@ -52,8 +55,10 @@ sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs --- jgit-4.1.2/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs 2019-09-03 12:37:49.000000000 +0000 @@ -1,4 +1,4 @@ -#Tue Oct 18 00:52:01 CEST 2011 +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error @@ -8,6 +8,10 @@ API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error CLASS_ELEMENT_TYPE_ADDED_METHOD=Error CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error @@ -47,6 +51,7 @@ ILLEGAL_INSTANTIATE=Warning ILLEGAL_OVERRIDE=Warning ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error @@ -58,6 +63,7 @@ INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore INVALID_JAVADOC_TAG=Ignore INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error LEAK_EXTEND=Warning @@ -75,6 +81,7 @@ METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error @@ -83,10 +90,13 @@ TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error UNUSED_PROBLEM_FILTERS=Warning automatically_removed_unused_problem_filters=false +changed_execution_env=Error eclipse.preferences.version=1 incompatible_api_component_version=Error incompatible_api_component_version_include_major_without_breaking_change=Disabled incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore invalid_since_tag_version=Error malformed_since_tag=Error missing_since_tag=Error diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java 2019-09-03 12:37:49.000000000 +0000 @@ -58,13 +58,18 @@ import org.eclipse.jgit.util.CachedAuthenticator; -/** Basic network prompt for username/password when using AWT. */ +/** + * Basic network prompt for username/password when using AWT. + */ public class AwtAuthenticator extends CachedAuthenticator { - /** Install this authenticator implementation into the JVM. */ + /** + * Install this authenticator implementation into the JVM. + */ public static void install() { setDefault(new AwtAuthenticator()); } + /** {@inheritDoc} */ @Override protected PasswordAuthentication promptPasswordAuthentication() { final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1, diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java 2019-09-03 12:37:49.000000000 +0000 @@ -56,22 +56,33 @@ import javax.swing.JTextField; import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.ChainingCredentialsProvider; import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.NetRCCredentialsProvider; import org.eclipse.jgit.transport.URIish; -/** Interacts with the user during authentication by using AWT/Swing dialogs. */ +/** + * Interacts with the user during authentication by using AWT/Swing dialogs. + */ public class AwtCredentialsProvider extends CredentialsProvider { - /** Install this implementation as the default. */ + /** + * Install this implementation as the default. + */ public static void install() { - CredentialsProvider.setDefault(new AwtCredentialsProvider()); + final AwtCredentialsProvider c = new AwtCredentialsProvider(); + CredentialsProvider cp = new ChainingCredentialsProvider( + new NetRCCredentialsProvider(), c); + CredentialsProvider.setDefault(cp); } + /** {@inheritDoc} */ @Override public boolean isInteractive() { return true; } + /** {@inheritDoc} */ @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { @@ -93,6 +104,7 @@ return true; } + /** {@inheritDoc} */ @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java 2019-09-03 12:37:49.000000000 +0000 @@ -84,6 +84,7 @@ } } + /** {@inheritDoc} */ @Override protected void drawLine(final Color color, int x1, int y1, int x2, int y2, int width) { @@ -100,6 +101,7 @@ g.drawLine(x1, y1, x2, y2); } + /** {@inheritDoc} */ @Override protected void drawCommitDot(final int x, final int y, final int w, final int h) { @@ -110,6 +112,7 @@ g.drawOval(x, y, w, h); } + /** {@inheritDoc} */ @Override protected void drawBoundaryDot(final int x, final int y, final int w, final int h) { @@ -120,6 +123,7 @@ g.drawOval(x, y, w, h); } + /** {@inheritDoc} */ @Override protected void drawText(final String msg, final int x, final int y) { final int texth = g.getFontMetrics().getHeight(); @@ -128,6 +132,7 @@ g.drawString(msg, x, y0 + texth - g.getFontMetrics().getDescent()); } + /** {@inheritDoc} */ @Override protected Color laneColor(final SwingLane myLane) { return myLane != null ? myLane.color : Color.black; @@ -147,6 +152,7 @@ g.drawPolygon(triangle); } + /** {@inheritDoc} */ @Override protected int drawLabel(int x, int y, Ref ref) { String txt; diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java 2019-09-03 12:37:49.000000000 +0000 @@ -81,7 +81,9 @@ private final SwingCommitList allCommits; - /** Create a new empty panel. */ + /** + * Create a new empty panel. + */ public CommitGraphPane() { allCommits = new SwingCommitList(); configureHeader(); @@ -110,6 +112,7 @@ return allCommits; } + /** {@inheritDoc} */ @Override public void setModel(final TableModel dataModel) { if (dataModel != null && !(dataModel instanceof CommitTableModel)) @@ -117,6 +120,7 @@ super.setModel(dataModel); } + /** {@inheritDoc} */ @Override protected TableModel createDefaultDataModel() { return new CommitTableModel(); @@ -146,14 +150,17 @@ PersonIdent lastAuthor; + @Override public int getColumnCount() { return 3; } + @Override public int getRowCount() { return allCommits != null ? allCommits.size() : 0; } + @Override public Object getValueAt(final int rowIndex, final int columnIndex) { final PlotCommit c = allCommits.get(rowIndex); switch (columnIndex) { @@ -180,6 +187,7 @@ static class NameCellRender extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; + @Override public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { @@ -201,6 +209,7 @@ private final DateFormat fmt = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$ + @Override public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { @@ -223,6 +232,7 @@ PlotCommit commit; + @Override @SuppressWarnings("unchecked") public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java 2019-09-03 12:37:49.000000000 +0000 @@ -53,7 +53,7 @@ final LinkedList colors; SwingCommitList() { - colors = new LinkedList(); + colors = new LinkedList<>(); repackColors(); } @@ -67,6 +67,7 @@ colors.add(Color.orange); } + /** {@inheritDoc} */ @Override protected SwingLane createLane() { final SwingLane lane = new SwingLane(); @@ -76,6 +77,7 @@ return lane; } + /** {@inheritDoc} */ @Override protected void recycleLane(final SwingLane lane) { colors.add(lane.color); diff -Nru jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/UIText.java jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/UIText.java --- jgit-4.1.2/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/UIText.java 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/UIText.java 2019-09-03 12:37:49.000000000 +0000 @@ -52,6 +52,8 @@ public class UIText extends TranslationBundle { /** + * Get an instance of this translation bundle. + * * @return an instance of this translation bundle */ public static UIText get() { diff -Nru jgit-4.1.2/pom.xml jgit-4.11.9/pom.xml --- jgit-4.1.2/pom.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/pom.xml 2019-09-03 12:37:49.000000000 +0000 @@ -51,7 +51,7 @@ org.eclipse.jgit org.eclipse.jgit-parent pom - 4.1.2.201602141800-r + 4.11.9.201909030838-r JGit - Parent ${jgit-url} @@ -89,6 +89,9 @@ Dave Borowitz + David Pursehouse + + Gunnar Wagenknecht @@ -118,6 +121,9 @@ Stefan Lay + + Thomas Wolf + @@ -188,26 +194,30 @@ UTF-8 UTF-8 - yyyyMMddHHmm ${project.build.directory}/META-INF/MANIFEST.MF - 4.0.0.201506090130-r - 0.1.53 - 0.7.9 - 4.11 + 4.11.0.201803080745-r + 0.1.54 + 1.1.1 + 1.1.6 + 4.12 1C - 2.0.15 - 1.6 + 2.33 + 1.15 4.3.1 3.1.0 - 9.2.13.v20150730 - 2.6.1 - 4.3.6 + 9.4.8.v20171121 + 0.11.0 + 4.5.2 + 4.4.6 1.7.2 1.2.15 - 2.10.1 - 0.23.0 + 3.0.0 + 1.1.0 + 2.8.2 + 3.1.2 + 2.20.1 jacoco @@ -239,7 +249,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.6 + 3.0.2 @@ -258,23 +268,70 @@ maven-compiler-plugin - 3.2 + 3.7.0 UTF-8 - 1.7 - 1.7 + 1.8 + 1.8 + + + default-compile + compile + + compile + + + + org/eclipse/jgit/transport/InsecureCipherFactory.java + + + + + compile-with-errorprone + compile + + compile + + + javac-with-errorprone + true + + org/eclipse/jgit/transport/InsecureCipherFactory.java + + + + + + + org.codehaus.plexus + plexus-compiler-javac + 2.8.2 + + + org.codehaus.plexus + plexus-compiler-javac-errorprone + 2.8.2 + + + + com.google.errorprone + error_prone_core + 2.2.0 + + maven-clean-plugin - 2.6.1 + 3.0.0 org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.1.0 @@ -286,13 +343,13 @@ org.apache.maven.plugins maven-dependency-plugin - 2.10 + 3.0.2 org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.1 @@ -304,7 +361,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.18.1 + 2.20.1 ${test-fork-count} true @@ -314,13 +371,13 @@ org.codehaus.mojo build-helper-maven-plugin - 1.9 + 3.0.0 - org.codehaus.mojo - findbugs-maven-plugin - 3.0.0 + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs-maven-plugin-version} true false @@ -337,13 +394,16 @@ org.apache.maven.plugins maven-pmd-plugin - 3.4 + 3.8 utf-8 100 - 1.7 + 1.8 xml false + + **/UbcCheck.java + @@ -355,19 +415,9 @@ - org.codehaus.mojo - clirr-maven-plugin - ${clirr-version} - - ${jgit-last-release-version} - info - - - - org.eclipse.cbi.maven.plugins eclipse-jarsigner-plugin - 1.1.2 + 1.1.4 org.eclipse.tycho.extras @@ -382,24 +432,24 @@ org.jacoco jacoco-maven-plugin - 0.7.1.201405082137 + 0.7.9 org.apache.maven.plugins maven-site-plugin - 3.4 + 3.6 org.apache.maven.wagon wagon-ssh - 2.7 + 2.12 org.apache.maven.plugins maven-surefire-report-plugin - 2.18.1 + ${maven-surefire-report-plugin-version} org.apache.maven.plugins @@ -409,13 +459,34 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 2.8 + 2.9 + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M1 + + + enforce-maven + + enforce + + + + + 3.5.2 + + + + + + + + maven-compiler-plugin @@ -460,10 +531,12 @@ org.apache.maven.plugins maven-javadoc-plugin + -Xdoclint:-missing ${project.build.sourceEncoding} true + org.eclipse.jgit.http.test - http://docs.oracle.com/javase/7/docs/api + http://docs.oracle.com/javase/8/docs/api @@ -523,24 +596,21 @@ org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin-version} - - org.apache.maven.plugins maven-jxr-plugin + 2.5 - org.codehaus.mojo - clirr-maven-plugin - - - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs-maven-plugin-version} org.apache.maven.plugins maven-surefire-report-plugin + ${maven-surefire-report-plugin-version} true false @@ -561,6 +631,12 @@ + com.jcraft + jzlib + ${jzlib-version} + + + com.googlecode.javaewah JavaEWAH ${javaewah-version} @@ -591,6 +667,13 @@ + org.tukaani + xz + 1.6 + true + + + org.eclipse.jetty jetty-servlet ${jetty-version} @@ -609,6 +692,12 @@ + org.apache.httpcomponents + httpcore + ${httpcore-version} + + + org.slf4j slf4j-api ${slf4j-version} @@ -643,6 +732,12 @@ + + + com.google.code.gson + gson + ${gson-version} + @@ -667,35 +762,12 @@ - jgit.java7 - - [1.7,) - - - - jgit.java8 - - [1.8,) - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - -Xdoclint:-missing - - - - - - static-checks - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin org.apache.maven.plugins @@ -778,6 +850,8 @@ org.eclipse.jgit.http.apache org.eclipse.jgit.http.server org.eclipse.jgit.pgm + org.eclipse.jgit.lfs + org.eclipse.jgit.lfs.server org.eclipse.jgit.junit org.eclipse.jgit.junit.http @@ -785,6 +859,8 @@ org.eclipse.jgit.ant.test org.eclipse.jgit.http.test org.eclipse.jgit.pgm.test + org.eclipse.jgit.lfs.test + org.eclipse.jgit.lfs.server.test diff -Nru jgit-4.1.2/README.md jgit-4.11.9/README.md --- jgit-4.1.2/README.md 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/README.md 2019-09-03 12:37:49.000000000 +0000 @@ -18,10 +18,6 @@ All portions of JGit are covered by the EDL. Absolutely no GPL, LGPL or EPL contributions are accepted within this package. -- org.eclipse.jgit.java7 - - Extensions for users of Java 7. - - org.eclipse.jgit.ant Ant tasks based on JGit. @@ -59,10 +55,6 @@ Unit tests for org.eclipse.jgit -- org.eclipse.jgit.java7.test - - Unit tests for Java 7 specific features - - org.eclipse.jgit.ant.test - org.eclipse.jgit.pgm.test - org.eclipse.jgit.http.test @@ -73,16 +65,14 @@ Warnings/Caveats ---------------- -- Native smbolic links are supported, but only if you are using Java 7 - or newer and include the org.eclipse.jgit.java7 jar/bundle in the - classpath, provided the file system supports them. For Windows you - must have Windows Vista/Windows 2008 or newer, use a - non-administrator account and have the SeCreateSymbolicLinkPrivilege. +- Native smbolic links are supported, provided the file system supports + them. For Windows you must have Windows Vista/Windows 2008 or newer, + use a non-administrator account and have the SeCreateSymbolicLinkPrivilege. - Only the timestamp of the index is used by jgit if the index is dirty. -- JGit requires at least a Java 7 JDK. +- JGit requires at least a Java 8 JDK. - CRLF conversion is performed depending on the core.autocrlf setting, however Git for Windows by default stores that setting during @@ -153,12 +143,6 @@ * Assorted set of command line utilities. Mostly for ad-hoc testing of jgit log, glog, fetch etc. -- org.eclipse.jgit.java7/ - - * Support for symbolic links. - - * Optimizations for reading file system attributes - - org.eclipse.jgit.ant/ * Ant tasks diff -Nru jgit-4.1.2/tools/bazlets.bzl jgit-4.11.9/tools/bazlets.bzl --- jgit-4.1.2/tools/bazlets.bzl 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/tools/bazlets.bzl 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,18 @@ +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +NAME = "com_googlesource_gerrit_bazlets" + +def load_bazlets( + commit, + local_path = None): + if not local_path: + git_repository( + name = NAME, + remote = "https://gerrit.googlesource.com/bazlets", + commit = commit, + ) + else: + native.local_repository( + name = NAME, + path = local_path, + ) diff -Nru jgit-4.1.2/tools/default.defs jgit-4.11.9/tools/default.defs --- jgit-4.1.2/tools/default.defs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/tools/default.defs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,42 @@ +def java_sources( + name, + srcs, + visibility = ['PUBLIC'] + ): + java_library( + name = name, + resources = srcs, + visibility = visibility, + ) + +def maven_jar( + name, + group, + artifact, + version, + bin_sha1, + src_sha1, + visibility = ['PUBLIC']): + jar_name = '%s__jar' % name + src_name = '%s__src' % name + + remote_file( + name = jar_name, + sha1 = bin_sha1, + url = 'mvn:%s:%s:jar:%s' % (group, artifact, version), + out = '%s.jar' % jar_name, + ) + + remote_file( + name = src_name, + sha1 = src_sha1, + url = 'mvn:%s:%s:src:%s' % (group, artifact, version), + out = '%s.jar' % src_name, + ) + + prebuilt_jar( + name = name, + binary_jar = ':' + jar_name, + source_jar = ':' + src_name, + visibility = visibility) + diff -Nru jgit-4.1.2/tools/eclipse-JGit-Format.xml jgit-4.11.9/tools/eclipse-JGit-Format.xml --- jgit-4.1.2/tools/eclipse-JGit-Format.xml 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/tools/eclipse-JGit-Format.xml 2019-09-03 12:37:49.000000000 +0000 @@ -45,7 +45,7 @@ - + @@ -156,7 +156,7 @@ - + @@ -227,7 +227,7 @@ - + diff -Nru jgit-4.1.2/tools/git.defs jgit-4.11.9/tools/git.defs --- jgit-4.1.2/tools/git.defs 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/tools/git.defs 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,9 @@ +def git_version(): + import subprocess + cmd = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty'] + p = subprocess.Popen(cmd, stdout = subprocess.PIPE) + v = p.communicate()[0].strip() + r = p.returncode + if r != 0: + raise subprocess.CalledProcessError(r, ' '.join(cmd)) + return v diff -Nru jgit-4.1.2/tools/maven-central/deploy.rb jgit-4.11.9/tools/maven-central/deploy.rb --- jgit-4.1.2/tools/maven-central/deploy.rb 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/tools/maven-central/deploy.rb 2019-09-03 12:37:49.000000000 +0000 @@ -53,9 +53,10 @@ group + '.archive', group + '.http.apache', group + '.http.server', - group + '.java7', group + '.junit', group + '.junit.http', + group + '.lfs', + group + '.lfs.server', group + '.pgm', group + '.ui'] diff -Nru jgit-4.1.2/tools/maven-central/download.rb jgit-4.11.9/tools/maven-central/download.rb --- jgit-4.1.2/tools/maven-central/download.rb 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/tools/maven-central/download.rb 2019-09-03 12:37:49.000000000 +0000 @@ -13,9 +13,10 @@ group + '.archive', group + '.http.apache', group + '.http.server', - group + '.java7', group + '.junit', group + '.junit.http', + group + '.lfs', + group + '.lfs.server', group + '.pgm', group + '.ui'] diff -Nru jgit-4.1.2/tools/release.sh jgit-4.11.9/tools/release.sh --- jgit-4.1.2/tools/release.sh 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/tools/release.sh 2019-09-03 12:37:49.000000000 +0000 @@ -47,6 +47,5 @@ git tag -sf -m "$MSG" $1 # run the build -mvn clean install +mvn clean install -T 1C mvn clean install -f org.eclipse.jgit.packaging/pom.xml - diff -Nru jgit-4.1.2/tools/version.sh jgit-4.11.9/tools/version.sh --- jgit-4.1.2/tools/version.sh 2016-02-14 22:58:51.000000000 +0000 +++ jgit-4.11.9/tools/version.sh 2019-09-03 12:37:49.000000000 +0000 @@ -131,6 +131,7 @@ $seen_version = 1 if (!/<\?xml/ && s/(version=")[^"]*(")/${1}'"$OSGI_V"'${2}/); } + s/(import feature="org\.eclipse\.jgit.*" version=")[^"]*(")/${1}'"$API_V"'${2}/; ' org.eclipse.jgit.packaging/org.*.feature/feature.xml perl -pi~ -e ' @@ -146,17 +147,6 @@ perl -pi~ -e ' if ($ARGV ne $old_argv) { - $seen_version = 0; - $old_argv = $ARGV; - } - if ($seen_version < 5) { - $seen_version++ if - s{<(version)>.*}{<${1}>'"$POM_V"'}; - } - ' org.eclipse.jgit.packaging/org.eclipse.jgit.updatesite/pom.xml - -perl -pi~ -e ' - if ($ARGV ne $old_argv) { $seen_version = 0; $old_argv = $ARGV; } diff -Nru jgit-4.1.2/WORKSPACE jgit-4.11.9/WORKSPACE --- jgit-4.1.2/WORKSPACE 1970-01-01 00:00:00.000000000 +0000 +++ jgit-4.11.9/WORKSPACE 2019-09-03 12:37:49.000000000 +0000 @@ -0,0 +1,156 @@ +workspace(name = "jgit") + +load("//tools:bazlets.bzl", "load_bazlets") + +load_bazlets(commit = "09a035e98077dce549d5f6a7472d06c4b8f792d2") + +load( + "@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", + "maven_jar", +) + +maven_jar( + name = "jsch", + artifact = "com.jcraft:jsch:0.1.53", + sha1 = "658b682d5c817b27ae795637dfec047c63d29935", +) + +maven_jar( + name = "jzlib", + artifact = "com.jcraft:jzlib:1.1.1", + sha1 = "a1551373315ffc2f96130a0e5704f74e151777ba", +) + +maven_jar( + name = "javaewah", + artifact = "com.googlecode.javaewah:JavaEWAH:1.1.6", + sha1 = "94ad16d728b374d65bd897625f3fbb3da223a2b6", +) + +maven_jar( + name = "httpclient", + artifact = "org.apache.httpcomponents:httpclient:4.5.2", + sha1 = "733db77aa8d9b2d68015189df76ab06304406e50", +) + +maven_jar( + name = "httpcore", + artifact = "org.apache.httpcomponents:httpcore:4.4.6", + sha1 = "e3fd8ced1f52c7574af952e2e6da0df8df08eb82", +) + +maven_jar( + name = "commons-codec", + artifact = "commons-codec:commons-codec:1.4", + sha1 = "4216af16d38465bbab0f3dff8efa14204f7a399a", +) + +maven_jar( + name = "commons-logging", + artifact = "commons-logging:commons-logging:1.1.3", + sha1 = "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f", +) + +maven_jar( + name = "log-api", + artifact = "org.slf4j:slf4j-api:1.7.2", + sha1 = "0081d61b7f33ebeab314e07de0cc596f8e858d97", +) + +maven_jar( + name = "slf4j-simple", + artifact = "org.slf4j:slf4j-simple:1.7.2", + sha1 = "760055906d7353ba4f7ce1b8908bc6b2e91f39fa", +) + +maven_jar( + name = "servlet-api-3_1", + artifact = "javax.servlet:javax.servlet-api:3.1.0", + sha1 = "3cd63d075497751784b2fa84be59432f4905bf7c", +) + +maven_jar( + name = "commons-compress", + artifact = "org.apache.commons:commons-compress:1.15", + sha1 = "b686cd04abaef1ea7bc5e143c080563668eec17e", +) + +maven_jar( + name = "tukaani-xz", + artifact = "org.tukaani:xz:1.6", + sha1 = "05b6f921f1810bdf90e25471968f741f87168b64", +) + +maven_jar( + name = "args4j", + artifact = "args4j:args4j:2.33", + sha1 = "bd87a75374a6d6523de82fef51fc3cfe9baf9fc9", +) + +maven_jar( + name = "junit", + artifact = "junit:junit:4.11", + sha1 = "4e031bb61df09069aeb2bffb4019e7a5034a4ee0", +) + +maven_jar( + name = "hamcrest-library", + artifact = "org.hamcrest:hamcrest-library:1.3", + sha1 = "4785a3c21320980282f9f33d0d1264a69040538f", +) + +maven_jar( + name = "hamcrest-core", + artifact = "org.hamcrest:hamcrest-core:1.3", + sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0", +) + +maven_jar( + name = "gson", + artifact = "com.google.code.gson:gson:2.8.2", + sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf", +) + +JETTY_VER = "9.4.8.v20171121" + +maven_jar( + name = "jetty-servlet", + artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER, + sha1 = "bbbb9b5de08f468c7b9b3de6aea0b098d2c679b6", + src_sha1 = "6ef1e65a5af7ab2d79ba6043923affdaeaafb1e5", +) + +maven_jar( + name = "jetty-security", + artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER, + sha1 = "e8350eec683b55494287f06740543e4be6f75425", + src_sha1 = "e3a879d8675fa10bc305e7a59006f1d09db04a68", +) + +maven_jar( + name = "jetty-server", + artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER, + sha1 = "34614bd9a29de57ef28ca31f1f2b49a412af196d", + src_sha1 = "fef49ac6b2bbc6d142dc0be34f68f0fb0792d52b", +) + +maven_jar( + name = "jetty-http", + artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER, + sha1 = "9879d6c4e37400bf43f0cd4b3c6e34a3ba409864", + src_sha1 = "5e746cd0ccb732eef0427c8c4b9dcb034e26c61b", +) + +maven_jar( + name = "jetty-io", + artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER, + sha1 = "d3fe2dfa62f52ee91ff07cb359f63387e0e30b40", + src_sha1 = "41f25e1e1bba14ab0d3415488fa189f09c27a1cf", +) + +maven_jar( + name = "jetty-util", + artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER, + sha1 = "d6ec1a1613c7fa72aa6bf5d8c204750afbc3df3b", + src_sha1 = "a74ecb43f96b2e21852f6908604316d7348a16ad", +)