Merge branch 'stable-4.6' into stable-4.7

* stable-4.6:
  Prepare 4.5.7-SNAPSHOT builds
  JGit v4.5.6.201903121547-r
  Check for packfile validity and fd before reading
  Move throw of PackInvalidException outside the catch
  Use FileSnapshot to get lastModified on PackFile
  Include size when comparing FileSnapshot
  Do not reuse packfiles when changed on filesystem
  Silence API warnings for new API introduced for fixes

Change-Id: I3d1544d034783fe0fa1385dfe9b03ad8e9247c63
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/.buckconfig b/.buckconfig
deleted file mode 100644
index 7986494..0000000
--- a/.buckconfig
+++ /dev/null
@@ -1,26 +0,0 @@
-[alias]
-  all = //:all
-  jgit = //org.eclipse.jgit:jgit
-  jgit-archive = //org.eclipse.jgit.archive:jgit-archive
-  jgit_bin = //:jgit_bin
-  jgit-lfs = //org.eclipse.jgit.lfs:jgit-lfs
-  jgit-lfs-server = //org.eclipse.jgit.lfs.server:jgit-lfs-server
-  jgit-servlet = //org.eclipse.jgit.http.server:jgit-servlet
-
-[buildfile]
-  includes = //tools/default.defs
-
-[java]
-  src_roots = src, resources, tst
-  source_level = 8
-  target_level = 8
-
-[project]
-  ignore = .git
-
-[cache]
-  mode = dir
-
-[download]
-  maven_repo = http://repo1.maven.org/maven2
-  in_build = true
diff --git a/.buckversion b/.buckversion
deleted file mode 100644
index 7eb591f..0000000
--- a/.buckversion
+++ /dev/null
@@ -1 +0,0 @@
-e27df656657f93f8d57a7aaac69a5ae0e298a292
diff --git a/.gitignore b/.gitignore
index e78f8fd..963b8a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
-/.buckd
 /.project
-/buck-cache
-/buck-out
 /target
 infer-out
+bazel-bin
+bazel-genfiles
+bazel-jgit
+bazel-out
+bazel-testlogs
+*~
diff --git a/.mailmap b/.mailmap
index ab4c963..560baaa 100644
--- a/.mailmap
+++ b/.mailmap
@@ -2,6 +2,7 @@
 Roberto Tyley <roberto.tyley@guardian.co.uk> roberto <roberto.tyley@guardian.co.uk>
 Saša Živkov <sasa.zivkov@sap.com>            Sasa Zivkov <sasa.zivkov@sap.com>
 Saša Živkov <sasa.zivkov@sap.com>            Saša Živkov <zivkov@gmail.com>
+Saša Živkov <sasa.zivkov@sap.com>            Sasa Zivkov <zivkov@gmail.com>
 Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>           Shawn Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <spearce@spearce.org>
diff --git a/BUCK b/BUCK
deleted file mode 100644
index 4578e17..0000000
--- a/BUCK
+++ /dev/null
@@ -1,18 +0,0 @@
-DEPS = [
-  '//org.eclipse.jgit:jgit',
-  '//org.eclipse.jgit.archive:jgit-archive',
-  '//org.eclipse.jgit.http.server:jgit-servlet',
-  '//org.eclipse.jgit.lfs:jgit-lfs',
-  '//org.eclipse.jgit.lfs.server:jgit-lfs-server',
-  '//org.eclipse.jgit.pgm:jgit',
-]
-
-zip_file(
-  name = 'all',
-  srcs = DEPS,
-)
-
-sh_binary(
-  name = 'jgit_bin',
-  main = '//org.eclipse.jgit.pgm:jgit',
-)
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..be6dd76
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,25 @@
+package(default_visibility = ["//visibility:public"])
+
+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 --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..c5eae1d
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,150 @@
+workspace(name = "jgit")
+
+load("//tools:bazlets.bzl", "load_bazlets")
+
+load_bazlets(commit = "3afbeab55ece585dbfc7a980bf7214b24ddbbe86")
+
+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 = "javaewah",
+    artifact = "com.googlecode.javaewah:JavaEWAH:1.1.6",
+    sha1 = "94ad16d728b374d65bd897625f3fbb3da223a2b6",
+)
+
+maven_jar(
+    name = "httpclient",
+    artifact = "org.apache.httpcomponents:httpclient:4.3.6",
+    sha1 = "4c47155e3e6c9a41a28db36680b828ced53b8af4",
+)
+
+maven_jar(
+    name = "httpcore",
+    artifact = "org.apache.httpcomponents:httpcore:4.3.3",
+    sha1 = "f91b7a4aadc5cf486df6e4634748d7dd7a73f06d",
+)
+
+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.6",
+    sha1 = "c7d9b580aff9e9f1998361f16578e63e5c064699",
+)
+
+maven_jar(
+    name = "tukaani-xz",
+    artifact = "org.tukaani:xz:1.3",
+    sha1 = "66db21c8484120cb6a51b5b3ea47b6f383942bec",
+)
+
+maven_jar(
+    name = "args4j",
+    artifact = "args4j:args4j:2.0.15",
+    sha1 = "139441471327b9cc6d56436cb2a31e60eb6ed2ba",
+)
+
+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.2.4",
+    sha1 = "a60a5e993c98c864010053cb901b7eab25306568",
+)
+
+JETTY_VER = "9.3.17.v20170317"
+
+maven_jar(
+    name = "jetty-servlet",
+    artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER,
+    sha1 = "ed6986b0d0ca7b9b0f9015c9efb80442e3043a8e",
+    src_sha1 = "ee6b4784a00a92e5c1b6111033b7ae41ac6052a3",
+)
+
+maven_jar(
+    name = "jetty-security",
+    artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
+    sha1 = "ca52535569445682d42aaa97c7039442719a0507",
+    src_sha1 = "2ff9f4fb18b320fd5a0272a427bacc4d5fe7bc86",
+)
+
+maven_jar(
+    name = "jetty-server",
+    artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
+    sha1 = "194e9a02e6ba249ef4a3f4bd56b4993087992299",
+    src_sha1 = "0c9bd572f530c411592aefb71781ecca0b3719a9",
+)
+
+maven_jar(
+    name = "jetty-http",
+    artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
+    sha1 = "6c02d728e15d4868486254039c867a1ac3e4a52e",
+    src_sha1 = "3c0a2a82792f268631b4fefd77be9f126ec974b1",
+)
+
+maven_jar(
+    name = "jetty-io",
+    artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
+    sha1 = "756a8cd2a1cbfb84a94973b6332dd3eccd47c0cd",
+    src_sha1 = "a9afa99cccb19b441364fa805d027f457cbbb136",
+)
+
+maven_jar(
+    name = "jetty-util",
+    artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
+    sha1 = "b8512ab02819de01f0f5a5c6026163041f579beb",
+    src_sha1 = "96f8e3dcdc3660a5c91f19c46695daa70ac95625",
+)
diff --git a/lib/BUCK b/lib/BUCK
deleted file mode 100644
index d89af5d..0000000
--- a/lib/BUCK
+++ /dev/null
@@ -1,134 +0,0 @@
-maven_jar(
-  name = 'jsch',
-  bin_sha1 = '658b682d5c817b27ae795637dfec047c63d29935',
-  src_sha1 = '791359d94d6edcace686a56d0727ee093a2f7c33',
-  group = 'com.jcraft',
-  artifact = 'jsch',
-  version = '0.1.53',
-)
-
-maven_jar(
-  name = 'javaewah',
-  bin_sha1 = '94ad16d728b374d65bd897625f3fbb3da223a2b6',
-  src_sha1 = 'ca2745e91c6a51f8e6809d1579bda36ad83f1f58',
-  group = 'com.googlecode.javaewah',
-  artifact = 'JavaEWAH',
-  version = '1.1.6',
-)
-
-maven_jar(
-  name = 'httpcomponents',
-  bin_sha1 = '4c47155e3e6c9a41a28db36680b828ced53b8af4',
-  src_sha1 = 'af4d76be0c46ee26b0d9d1d4a34d244a633cac84',
-  group = 'org.apache.httpcomponents',
-  artifact = 'httpclient',
-  version = '4.3.6',
-)
-
-maven_jar(
-  name = 'httpcore',
-  bin_sha1 = 'f91b7a4aadc5cf486df6e4634748d7dd7a73f06d',
-  src_sha1 = '1b0aa62a6a91e9fa00c16f0a4a2c874804ed3b1e',
-  group = 'org.apache.httpcomponents',
-  artifact = 'httpcore',
-  version = '4.3.3',
-)
-
-maven_jar(
-  name = 'commons-logging',
-  bin_sha1 = 'f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f',
-  src_sha1 = '28bb0405fddaf04f15058fbfbe01fe2780d7d3b6',
-  group = 'commons-logging',
-  artifact = 'commons-logging',
-  version = '1.1.3',
-)
-
-maven_jar(
-  name = 'slf4j-api',
-  bin_sha1 = '0081d61b7f33ebeab314e07de0cc596f8e858d97',
-  src_sha1 = '58d38f68d4a867d4552ae27960bb348d7eaa1297',
-  group = 'org.slf4j',
-  artifact = 'slf4j-api',
-  version = '1.7.2',
-)
-
-maven_jar(
-  name = 'slf4j-simple',
-  bin_sha1 = '760055906d7353ba4f7ce1b8908bc6b2e91f39fa',
-  src_sha1 = '09474919128b3a7fcf21a5f9c907f5251f234544',
-  group = 'org.slf4j',
-  artifact = 'slf4j-simple',
-  version = '1.7.2',
-)
-
-maven_jar(
-  name = 'servlet-api',
-  bin_sha1 = '3cd63d075497751784b2fa84be59432f4905bf7c',
-  src_sha1 = 'ab3976d4574c48d22dc1abf6a9e8bd0fdf928223',
-  group = 'javax.servlet',
-  artifact = 'javax.servlet-api',
-  version = '3.1.0',
-)
-
-maven_jar(
-  name = 'commons-compress',
-  bin_sha1 = 'c7d9b580aff9e9f1998361f16578e63e5c064699',
-  src_sha1 = '396b81bdfd0fb617178e1707ef64832215307c78',
-  group = 'org.apache.commons',
-  artifact = 'commons-compress',
-  version = '1.6',
-)
-
-maven_jar(
-  name = 'tukaani-xz',
-  bin_sha1 = '66db21c8484120cb6a51b5b3ea47b6f383942bec',
-  src_sha1 = '6396220725701d767c553902c41120d7bf38e9f5',
-  group = 'org.tukaani',
-  artifact = 'xz',
-  version = '1.3',
-)
-
-maven_jar(
-  name = 'args4j',
-  bin_sha1 = '139441471327b9cc6d56436cb2a31e60eb6ed2ba',
-  src_sha1 = '22631b78cc8f60a6918557e8cbdb33e90f63a77f',
-  group = 'args4j',
-  artifact = 'args4j',
-  version = '2.0.15',
-)
-
-maven_jar(
-  name = 'junit',
-  bin_sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
-  src_sha1 = '28e0ad201304e4a4abf999ca0570b7cffc352c3c',
-  group = 'junit',
-  artifact = 'junit',
-  version = '4.11',
-)
-
-maven_jar(
-  name = 'hamcrest-library',
-  bin_sha1 = '4785a3c21320980282f9f33d0d1264a69040538f',
-  src_sha1 = '047a7ee46628ab7133129cd7cef1e92657bc275e',
-  group = 'org.hamcrest',
-  artifact = 'hamcrest-library',
-  version = '1.3',
-)
-
-maven_jar(
-  name = 'hamcrest-core',
-  bin_sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0',
-  src_sha1 = '1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b',
-  group = 'org.hamcrest',
-  artifact = 'hamcrest-core',
-  version = '1.3',
-)
-
-maven_jar(
-  name = 'gson',
-  bin_sha1 = 'a60a5e993c98c864010053cb901b7eab25306568',
-  src_sha1 = 'a6dc5db8a12928e583bd3f23e72d3ab611ecd58f',
-  group = 'com.google.code.gson',
-  artifact = 'gson',
-  version = '2.2.4',
-)
diff --git a/lib/BUILD b/lib/BUILD
new file mode 100644
index 0000000..346e1fd
--- /dev/null
+++ b/lib/BUILD
@@ -0,0 +1,156 @@
+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.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__"],
+    exports = ["@jsch//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 --git a/lib/jetty/BUCK b/lib/jetty/BUCK
deleted file mode 100644
index fbb37c1..0000000
--- a/lib/jetty/BUCK
+++ /dev/null
@@ -1,56 +0,0 @@
-VERSION = '9.3.17.v20170317'
-GROUP = 'org.eclipse.jetty'
-
-maven_jar(
-  name = 'servlet',
-  bin_sha1 = 'ed6986b0d0ca7b9b0f9015c9efb80442e3043a8e',
-  src_sha1 = 'ee6b4784a00a92e5c1b6111033b7ae41ac6052a3',
-  group = GROUP,
-  artifact = 'jetty-servlet',
-  version = VERSION,
-)
-
-maven_jar(
-  name = 'security',
-  bin_sha1 = 'ca52535569445682d42aaa97c7039442719a0507',
-  src_sha1 = '2ff9f4fb18b320fd5a0272a427bacc4d5fe7bc86',
-  group = GROUP,
-  artifact = 'jetty-security',
-  version = VERSION,
-)
-
-maven_jar(
-  name = 'server',
-  bin_sha1 = '194e9a02e6ba249ef4a3f4bd56b4993087992299',
-  src_sha1 = '0c9bd572f530c411592aefb71781ecca0b3719a9',
-  group = GROUP,
-  artifact = 'jetty-server',
-  version = VERSION,
-)
-
-maven_jar(
-  name = 'http',
-  bin_sha1 = '6c02d728e15d4868486254039c867a1ac3e4a52e',
-  src_sha1 = '3c0a2a82792f268631b4fefd77be9f126ec974b1',
-  group = GROUP,
-  artifact = 'jetty-http',
-  version = VERSION,
-)
-
-maven_jar(
-  name = 'io',
-  bin_sha1 = '756a8cd2a1cbfb84a94973b6332dd3eccd47c0cd',
-  src_sha1 = 'a9afa99cccb19b441364fa805d027f457cbbb136',
-  group = GROUP,
-  artifact = 'jetty-io',
-  version = VERSION,
-)
-
-maven_jar(
-  name = 'util',
-  bin_sha1 = 'b8512ab02819de01f0f5a5c6026163041f579beb',
-  src_sha1 = '96f8e3dcdc3660a5c91f19c46695daa70ac95625',
-  group = GROUP,
-  artifact = 'jetty-util',
-  version = VERSION,
-)
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 3bf69c3..681a0fa 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -3,14 +3,13 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
- org.hamcrest;version="[1.1.0,2.0.0)",
+ org.eclipse.jgit.ant.tasks;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index 9592841..f74bafb 100644
--- a/org.eclipse.jgit.ant.test/pom.xml
+++ b/org.eclipse.jgit.ant.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
index 80cfbbb..4d260cf 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 6b008e1..131fb09 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -2,11 +2,11 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)"
+  org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="4.6.2";
+Export-Package: org.eclipse.jgit.ant.tasks;version="4.7.8";
  uses:="org.apache.tools.ant.types,org.apache.tools.ant"
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index 194c9dd..535f56c 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>4.6.2-SNAPSHOT</version>
+		<version>4.7.8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
index 4f1759f..06ddbab 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.archive/BUCK b/org.eclipse.jgit.archive/BUCK
deleted file mode 100644
index ae17032..0000000
--- a/org.eclipse.jgit.archive/BUCK
+++ /dev/null
@@ -1,13 +0,0 @@
-java_library(
-  name = 'jgit-archive',
-  srcs = glob(
-    ['src/**'],
-    excludes = ['src/org/eclipse/jgit/archive/FormatActivator.java'],
-  ),
-  resources = glob(['resources/**']),
-  provided_deps = [
-    '//org.eclipse.jgit:jgit',
-    '//lib:commons-compress',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.archive/BUILD b/org.eclipse.jgit.archive/BUILD
new file mode 100644
index 0000000..dfdbfdc
--- /dev/null
+++ b/org.eclipse.jgit.archive/BUILD
@@ -0,0 +1,16 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "jgit-archive",
+    srcs = glob(
+        ["src/**"],
+        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 --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index b2fe6b2..f9a55f8 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -12,14 +12,15 @@
  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.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.api;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.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.6.2";
+Export-Package: org.eclipse.jgit.archive;version="4.7.8";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index 7d7477c..39be331 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.archive - Sources
 Bundle-SymbolicName: org.eclipse.jgit.archive.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.6.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.6.2.qualifier";roots="."
+Bundle-Version: 4.7.8.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.7.8.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 9c38817..01259d4 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java
index 1be126a..9d3decd 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java
@@ -55,7 +55,7 @@
  * that performs the same registration automatically.
  */
 public class ArchiveFormats {
-	private static final List<String> myFormats = new ArrayList<String>();
+	private static final List<String> myFormats = new ArrayList<>();
 
 	private static final void register(String name, ArchiveCommand.Format<?> fmt) {
 		myFormats.add(name);
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java
index a6a954f..aa4e4f5 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/FormatActivator.java
@@ -64,6 +64,7 @@ public class FormatActivator implements BundleActivator {
 	 * @param context
 	 *            unused
 	 */
+	@Override
 	public void start(BundleContext context) {
 		ArchiveFormats.registerAll();
 	}
@@ -75,6 +76,7 @@ public void start(BundleContext context) {
 	 * @param context
 	 *            unused
 	 */
+	@Override
 	public void stop(BundleContext context) {
 		ArchiveFormats.unregisterAll();
 	}
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
index d56cb35..7b7fbcd 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
@@ -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,6 +70,7 @@ public final class TarFormat extends BaseFormat implements
 	private static final List<String> SUFFIXES = Collections
 			.unmodifiableList(Arrays.asList(".tar")); //$NON-NLS-1$
 
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 			throws IOException {
 		return createArchiveOutputStream(s,
@@ -76,6 +80,7 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	/**
 	 * @since 4.0
 	 */
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		TarArchiveOutputStream out = new TarArchiveOutputStream(s, "UTF-8"); //$NON-NLS-1$
@@ -84,9 +89,21 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 		return applyFormatOptions(out, o);
 	}
 
+	@Deprecated
+	@Override
 	public void putEntry(ArchiveOutputStream out,
 			String path, FileMode mode, ObjectLoader loader)
 			throws IOException {
+		putEntry(out, null, path, mode,loader);
+	}
+
+	/**
+	 * @since 4.7
+	 */
+	@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 +123,12 @@ public void putEntry(ArchiveOutputStream out,
 			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,6 +150,7 @@ public void putEntry(ArchiveOutputStream out,
 		out.closeArchiveEntry();
 	}
 
+	@Override
 	public Iterable<String> suffixes() {
 		return SUFFIXES;
 	}
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java
index f3ab4da..5f194ec 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/Tbz2Format.java
@@ -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,6 +66,7 @@ public final class Tbz2Format extends BaseFormat implements
 
 	private final ArchiveCommand.Format<ArchiveOutputStream> tarFormat = new TarFormat();
 
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 			throws IOException {
 		return createArchiveOutputStream(s,
@@ -74,18 +76,32 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	/**
 	 * @since 4.0
 	 */
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(s);
 		return tarFormat.createArchiveOutputStream(out, o);
 	}
 
+	@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);
 	}
 
+	/**
+	 * @since 4.7
+	 */
+	@Override
+	public void putEntry(ArchiveOutputStream out,
+			ObjectId tree, String path, FileMode mode, ObjectLoader loader)
+			throws IOException {
+		tarFormat.putEntry(out, tree, path, mode, loader);
+	}
+
+	@Override
 	public Iterable<String> suffixes() {
 		return SUFFIXES;
 	}
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java
index 06f09a4..a6d053e 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TgzFormat.java
@@ -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,6 +66,7 @@ public final class TgzFormat extends BaseFormat implements
 
 	private final ArchiveCommand.Format<ArchiveOutputStream> tarFormat = new TarFormat();
 
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 			throws IOException {
 		return createArchiveOutputStream(s,
@@ -74,18 +76,32 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	/**
 	 * @since 4.0
 	 */
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		GzipCompressorOutputStream out = new GzipCompressorOutputStream(s);
 		return tarFormat.createArchiveOutputStream(out, o);
 	}
 
+	@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);
 	}
 
+	/**
+	 * @since 4.7
+	 */
+	@Override
+	public void putEntry(ArchiveOutputStream out,
+			ObjectId tree, String path, FileMode mode, ObjectLoader loader)
+			throws IOException {
+		tarFormat.putEntry(out, tree, path, mode, loader);
+	}
+
+	@Override
 	public Iterable<String> suffixes() {
 		return SUFFIXES;
 	}
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java
index 1483935..b6742ac 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TxzFormat.java
@@ -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,6 +66,7 @@ public final class TxzFormat extends BaseFormat implements
 
 	private final ArchiveCommand.Format<ArchiveOutputStream> tarFormat = new TarFormat();
 
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 			throws IOException {
 		return createArchiveOutputStream(s,
@@ -74,18 +76,32 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	/**
 	 * @since 4.0
 	 */
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		XZCompressorOutputStream out = new XZCompressorOutputStream(s);
 		return tarFormat.createArchiveOutputStream(out, o);
 	}
 
+	@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);
 	}
 
+	/**
+	 * @since 4.7
+	 */
+	@Override
+	public void putEntry(ArchiveOutputStream out,
+			ObjectId tree, String path, FileMode mode, ObjectLoader loader)
+			throws IOException {
+		tarFormat.putEntry(out, tree, path, mode, loader);
+	}
+
+	@Override
 	public Iterable<String> suffixes() {
 		return SUFFIXES;
 	}
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java
index 0e1b253..46d918e 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ZipFormat.java
@@ -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,6 +68,7 @@ public final class ZipFormat extends BaseFormat implements
 	private static final List<String> SUFFIXES = Collections
 			.unmodifiableList(Arrays.asList(".zip")); //$NON-NLS-1$
 
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 			throws IOException {
 		return createArchiveOutputStream(s,
@@ -75,14 +78,27 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	/**
 	 * @since 4.0
 	 */
+	@Override
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		return applyFormatOptions(new ZipArchiveOutputStream(s), o);
 	}
 
+	@Deprecated
+	@Override
 	public void putEntry(ArchiveOutputStream out,
 			String path, FileMode mode, ObjectLoader loader)
 			throws IOException {
+		putEntry(out, null, path, mode,loader);
+	}
+
+	/**
+	 * @since 4.7
+	 */
+	@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 +108,12 @@ public void putEntry(ArchiveOutputStream out,
 			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,6 +136,7 @@ public void putEntry(ArchiveOutputStream out,
 		out.closeArchiveEntry();
 	}
 
+	@Override
 	public Iterable<String> suffixes() {
 		return SUFFIXES;
 	}
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
index 80cfbbb..4d260cf 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.http.apache/BUCK b/org.eclipse.jgit.http.apache/BUCK
deleted file mode 100644
index 3b7a823..0000000
--- a/org.eclipse.jgit.http.apache/BUCK
+++ /dev/null
@@ -1,11 +0,0 @@
-java_library(
-  name = 'http-apache',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  deps = [
-    '//org.eclipse.jgit:jgit',
-    '//lib:httpcomponents',
-    '//lib:httpcore',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.http.apache/BUILD b/org.eclipse.jgit.http.apache/BUILD
new file mode 100644
index 0000000..c1538ab
--- /dev/null
+++ b/org.eclipse.jgit.http.apache/BUILD
@@ -0,0 +1,13 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "http-apache",
+    srcs = glob(["src/**"]),
+    resource_strip_prefix = "org.eclipse.jgit.http.apache/resources",
+    resources = glob(["resources/**"]),
+    deps = [
+        "//lib:httpclient",
+        "//lib:httpcore",
+        "//org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 978be27..2123021 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -20,13 +20,12 @@
  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.client.cache;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.6.2,4.7.0)",
- org.eclipse.jgit.transport.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="4.6.2";
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="4.7.8";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index dfd8585..5cc886a 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>4.6.2-SNAPSHOT</version>
+		<version>4.7.8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
index 281154f..945ecd5 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
@@ -223,15 +223,18 @@ public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
 		this.proxy = proxy;
 	}
 
+	@Override
 	public int getResponseCode() throws IOException {
 		execute();
 		return resp.getStatusLine().getStatusCode();
 	}
 
+	@Override
 	public URL getURL() {
 		return url;
 	}
 
+	@Override
 	public String getResponseMessage() throws IOException {
 		execute();
 		return resp.getStatusLine().getReasonPhrase();
@@ -259,10 +262,11 @@ private void execute() throws IOException, ClientProtocolException {
 		}
 	}
 
+	@Override
 	public Map<String, List<String>> getHeaderFields() {
-		Map<String, List<String>> ret = new HashMap<String, List<String>>();
+		Map<String, List<String>> ret = new HashMap<>();
 		for (Header hdr : resp.getAllHeaders()) {
-			List<String> list = new LinkedList<String>();
+			List<String> list = new LinkedList<>();
 			for (HeaderElement hdrElem : hdr.getElements())
 				list.add(hdrElem.toString());
 			ret.put(hdr.getName(), list);
@@ -270,10 +274,12 @@ public Map<String, List<String>> getHeaderFields() {
 		return ret;
 	}
 
+	@Override
 	public void setRequestProperty(String name, String value) {
 		req.addHeader(name, value);
 	}
 
+	@Override
 	public void setRequestMethod(String method) throws ProtocolException {
 		this.method = method;
 		if (METHOD_GET.equalsIgnoreCase(method)) {
@@ -290,18 +296,22 @@ public void setRequestMethod(String method) throws ProtocolException {
 		}
 	}
 
+	@Override
 	public void setUseCaches(boolean usecaches) {
 		// not needed
 	}
 
+	@Override
 	public void setConnectTimeout(int timeout) {
 		this.timeout = Integer.valueOf(timeout);
 	}
 
+	@Override
 	public void setReadTimeout(int readTimeout) {
 		this.readTimeout = Integer.valueOf(readTimeout);
 	}
 
+	@Override
 	public String getContentType() {
 		HttpEntity responseEntity = resp.getEntity();
 		if (responseEntity != null) {
@@ -312,16 +322,19 @@ public String getContentType() {
 		return null;
 	}
 
+	@Override
 	public InputStream getInputStream() throws IOException {
 		return resp.getEntity().getContent();
 	}
 
 	// will return only the first field
+	@Override
 	public String getHeaderField(String name) {
 		Header header = resp.getFirstHeader(name);
 		return (header == null) ? null : header.getValue();
 	}
 
+	@Override
 	public int getContentLength() {
 		Header contentLength = resp.getFirstHeader("content-length"); //$NON-NLS-1$
 		if (contentLength == null) {
@@ -336,14 +349,17 @@ public int getContentLength() {
 		}
 	}
 
+	@Override
 	public void setInstanceFollowRedirects(boolean followRedirects) {
 		this.followRedirects = Boolean.valueOf(followRedirects);
 	}
 
+	@Override
 	public void setDoOutput(boolean dooutput) {
 		// TODO: check whether we can really ignore this.
 	}
 
+	@Override
 	public void setFixedLengthStreamingMode(int contentLength) {
 		if (entity != null)
 			throw new IllegalArgumentException();
@@ -351,52 +367,63 @@ public void setFixedLengthStreamingMode(int contentLength) {
 		entity.setContentLength(contentLength);
 	}
 
+	@Override
 	public OutputStream getOutputStream() throws IOException {
 		if (entity == null)
 			entity = new TemporaryBufferEntity(new LocalFile(null));
 		return entity.getBuffer();
 	}
 
+	@Override
 	public void setChunkedStreamingMode(int chunklen) {
 		if (entity == null)
 			entity = new TemporaryBufferEntity(new LocalFile(null));
 		entity.setChunked(true);
 	}
 
+	@Override
 	public String getRequestMethod() {
 		return method;
 	}
 
+	@Override
 	public boolean usingProxy() {
 		return isUsingProxy;
 	}
 
+	@Override
 	public void connect() throws IOException {
 		execute();
 	}
 
+	@Override
 	public void setHostnameVerifier(final HostnameVerifier hostnameverifier) {
 		this.hostnameverifier = new X509HostnameVerifier() {
+			@Override
 			public boolean verify(String hostname, SSLSession session) {
 				return hostnameverifier.verify(hostname, session);
 			}
 
+			@Override
 			public void verify(String host, String[] cns, String[] subjectAlts)
 					throws SSLException {
 				throw new UnsupportedOperationException(); // TODO message
 			}
 
+			@Override
 			public void verify(String host, X509Certificate cert)
 					throws SSLException {
 				throw new UnsupportedOperationException(); // TODO message
 			}
 
+			@Override
 			public void verify(String host, SSLSocket ssl) throws IOException {
 				hostnameverifier.verify(host, ssl.getSession());
 			}
 		};
 	}
 
+	@Override
 	public void configure(KeyManager[] km, TrustManager[] tm,
 			SecureRandom random) throws KeyManagementException {
 		getSSLContext().init(km, tm, random);
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java
index fe1eef4..f97d284 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnectionFactory.java
@@ -55,10 +55,12 @@
  * @since 3.3
  */
 public class HttpClientConnectionFactory implements HttpConnectionFactory {
+	@Override
 	public HttpConnection create(URL url) throws IOException {
 		return new HttpClientConnection(url.toString());
 	}
 
+	@Override
 	public HttpConnection create(URL url, Proxy proxy)
 			throws IOException {
 		return new HttpClientConnection(url.toString(), proxy);
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
index 93328c9..3efff49 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
@@ -78,25 +78,30 @@ public TemporaryBuffer getBuffer() {
 		return buffer;
 	}
 
+	@Override
 	public boolean isRepeatable() {
 		return true;
 	}
 
+	@Override
 	public long getContentLength() {
 		if (contentLength != null)
 			return contentLength.intValue();
 		return buffer.length();
 	}
 
+	@Override
 	public InputStream getContent() throws IOException, IllegalStateException {
 		return buffer.openInputStream();
 	}
 
+	@Override
 	public void writeTo(OutputStream outstream) throws IOException {
 		// TODO: dont we need a progressmonitor
 		buffer.writeTo(outstream, null);
 	}
 
+	@Override
 	public boolean isStreaming() {
 		return false;
 	}
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
index 80cfbbb..4d260cf 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.http.server/BUCK b/org.eclipse.jgit.http.server/BUCK
deleted file mode 100644
index 3743557..0000000
--- a/org.eclipse.jgit.http.server/BUCK
+++ /dev/null
@@ -1,10 +0,0 @@
-java_library(
-  name = 'jgit-servlet',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  provided_deps = [
-    '//org.eclipse.jgit:jgit',
-    '//lib:servlet-api',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.http.server/BUILD b/org.eclipse.jgit.http.server/BUILD
new file mode 100644
index 0000000..876c5fa
--- /dev/null
+++ b/org.eclipse.jgit.http.server/BUILD
@@ -0,0 +1,13 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "jgit-servlet",
+    srcs = glob(["src/**"]),
+    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 --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index be3bb33..26fc9e9 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -2,13 +2,13 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="4.6.2",
- org.eclipse.jgit.http.server.glue;version="4.6.2";
+Export-Package: org.eclipse.jgit.http.server;version="4.7.8",
+ org.eclipse.jgit.http.server.glue;version="4.7.8";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="4.6.2";
+ org.eclipse.jgit.http.server.resolver;version="4.7.8";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -17,12 +17,12 @@
 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.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.resolver;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)"
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 843ec4b..022dc7f 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java
index d33362b..05391eb 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java
@@ -70,14 +70,17 @@ class AsIsFileFilter implements Filter {
 		this.asIs = getAnyFile;
 	}
 
+	@Override
 	public void init(FilterConfig config) throws ServletException {
 		// Do nothing.
 	}
 
+	@Override
 	public void destroy() {
 		// Do nothing.
 	}
 
+	@Override
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		HttpServletRequest req = (HttpServletRequest) request;
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
index 529b839..8070371 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
@@ -95,9 +95,9 @@ public class GitFilter extends MetaFilter {
 
 	private ReceivePackFactory<HttpServletRequest> receivePackFactory = new DefaultReceivePackFactory();
 
-	private final List<Filter> uploadPackFilters = new LinkedList<Filter>();
+	private final List<Filter> uploadPackFilters = new LinkedList<>();
 
-	private final List<Filter> receivePackFilters = new LinkedList<Filter>();
+	private final List<Filter> receivePackFilters = new LinkedList<>();
 
 	/**
 	 * New servlet that will load its base directory from {@code web.xml}.
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
index bca5210..3a5edee 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
@@ -174,18 +174,22 @@ public void addReceivePackFilter(Filter filter) {
 	@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<String> getInitParameterNames() {
 				return config.getInitParameterNames();
 			}
 
+			@Override
 			public ServletContext getServletContext() {
 				return config.getServletContext();
 			}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
index bb6efb8..91c2f9f 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
@@ -60,6 +60,7 @@
 class InfoPacksServlet extends HttpServlet {
 	private static final long serialVersionUID = 1L;
 
+	@Override
 	public void doGet(final HttpServletRequest req,
 			final HttpServletResponse rsp) throws IOException {
 		sendPlainText(packList(req), req, rsp);
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index 446f6a2..72c7136 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -64,6 +64,7 @@
 class InfoRefsServlet extends HttpServlet {
 	private static final long serialVersionUID = 1L;
 
+	@Override
 	public void doGet(final HttpServletRequest req,
 			final HttpServletResponse rsp) throws IOException {
 		// Assume a dumb client and send back the dumb client
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
index 511cdf1..223813f 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
@@ -66,14 +66,17 @@
  * downstream servlet can directly access its contents on disk.
  */
 class IsLocalFilter implements Filter {
+	@Override
 	public void init(FilterConfig config) throws ServletException {
 		// Do nothing.
 	}
 
+	@Override
 	public void destroy() {
 		// Do nothing.
 	}
 
+	@Override
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		if (isLocal(getRepository(request)))
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java
index 6a23cb9..bdc3420 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java
@@ -59,14 +59,17 @@
 
 /** Adds HTTP response headers to prevent caching by proxies/browsers. */
 class NoCacheFilter implements Filter {
+	@Override
 	public void init(FilterConfig config) throws ServletException {
 		// Do nothing.
 	}
 
+	@Override
 	public void destroy() {
 		// Do nothing.
 	}
 
+	@Override
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		HttpServletResponse rsp = (HttpServletResponse) response;
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index c88670e..9d24bf7 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -130,6 +130,7 @@ static class Factory implements Filter {
 			this.receivePackFactory = receivePackFactory;
 		}
 
+		@Override
 		public void doFilter(ServletRequest request, ServletResponse response,
 				FilterChain chain) throws IOException, ServletException {
 			HttpServletRequest req = (HttpServletRequest) request;
@@ -153,10 +154,12 @@ public void doFilter(ServletRequest request, ServletResponse response,
 			}
 		}
 
+		@Override
 		public void init(FilterConfig filterConfig) throws ServletException {
 			// Nothing.
 		}
 
+		@Override
 		public void destroy() {
 			// Nothing.
 		}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
index b3fad3d..de09c54 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
@@ -100,14 +100,17 @@ public RepositoryFilter(final RepositoryResolver<HttpServletRequest> resolver) {
 		this.resolver = resolver;
 	}
 
+	@Override
 	public void init(final FilterConfig config) throws ServletException {
 		context = config.getServletContext();
 	}
 
+	@Override
 	public void destroy() {
 		context = null;
 	}
 
+	@Override
 	public void doFilter(final ServletRequest request,
 			final ServletResponse response, final FilterChain chain)
 			throws IOException, ServletException {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
index 145c63b..08a5eba 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
@@ -95,6 +95,7 @@ protected OutputStream overflow() throws IOException {
 		return out;
 	}
 
+	@Override
 	public void close() throws IOException {
 		super.close();
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
index a06bb1e..fe34f66 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
@@ -80,14 +80,17 @@ abstract class SmartServiceInfoRefs implements Filter {
 		this.filters = filters.toArray(new Filter[filters.size()]);
 	}
 
+	@Override
 	public void init(FilterConfig config) throws ServletException {
 		// Do nothing.
 	}
 
+	@Override
 	public void destroy() {
 		// Do nothing.
 	}
 
+	@Override
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		final HttpServletRequest req = (HttpServletRequest) request;
@@ -154,6 +157,7 @@ protected abstract void advertise(HttpServletRequest req,
 	private class Chain implements FilterChain {
 		private int filterIdx;
 
+		@Override
 		public void doFilter(ServletRequest req, ServletResponse rsp)
 				throws IOException, ServletException {
 			if (filterIdx < filters.length)
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
index 650059b..28ee17d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
@@ -68,6 +68,7 @@ class TextFileServlet extends HttpServlet {
 		this.fileName = name;
 	}
 
+	@Override
 	public void doGet(final HttpServletRequest req,
 			final HttpServletResponse rsp) throws IOException {
 		try {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index a9a0c5b..97d00c1 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -129,6 +129,7 @@ static class Factory implements Filter {
 			this.uploadPackFactory = uploadPackFactory;
 		}
 
+		@Override
 		public void doFilter(ServletRequest request, ServletResponse response,
 				FilterChain chain) throws IOException, ServletException {
 			HttpServletRequest req = (HttpServletRequest) request;
@@ -152,10 +153,12 @@ public void doFilter(ServletRequest request, ServletResponse response,
 			}
 		}
 
+		@Override
 		public void init(FilterConfig filterConfig) throws ServletException {
 			// Nothing.
 		}
 
+		@Override
 		public void destroy() {
 			// Nothing.
 		}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java
index 2187cfa..adb6c42 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java
@@ -89,7 +89,7 @@ public class MetaFilter implements Filter {
 
 	/** Empty filter with no bindings. */
 	public MetaFilter() {
-		this.bindings = new ArrayList<ServletBinderImpl>();
+		this.bindings = new ArrayList<>();
 	}
 
 	/**
@@ -128,10 +128,12 @@ public ServletBinder serveRegex(Pattern pattern) {
 		return register(new RegexPipeline.Binder(pattern));
 	}
 
+	@Override
 	public void init(FilterConfig filterConfig) throws ServletException {
 		servletContext = filterConfig.getServletContext();
 	}
 
+	@Override
 	public void destroy() {
 		if (pipelines != null) {
 			Set<Object> destroyed = newIdentitySet();
@@ -142,7 +144,7 @@ public void destroy() {
 	}
 
 	private static Set<Object> newIdentitySet() {
-		final Map<Object, Object> m = new IdentityHashMap<Object, Object>();
+		final Map<Object, Object> m = new IdentityHashMap<>();
 		return new AbstractSet<Object>() {
 			@Override
 			public boolean add(Object o) {
@@ -166,6 +168,7 @@ public int size() {
 		};
 	}
 
+	@Override
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		HttpServletRequest req = (HttpServletRequest) request;
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
index 0506065..71365c8 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
@@ -123,6 +123,7 @@ public void init(ServletConfig config) throws ServletException {
 		filter.init(new NoParameterFilterConfig(name, ctx));
 	}
 
+	@Override
 	public void destroy() {
 		filter.destroy();
 	}
@@ -131,6 +132,7 @@ public void destroy() {
 	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 --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java
index 8dfcc4d..961f88e 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java
@@ -59,26 +59,32 @@ final class NoParameterFilterConfig implements FilterConfig {
 		this.context = context;
 	}
 
+	@Override
 	public String getInitParameter(String name) {
 		return null;
 	}
 
+	@Override
 	public Enumeration<String> getInitParameterNames() {
 		return new Enumeration<String>() {
+			@Override
 			public boolean hasMoreElements() {
 				return false;
 			}
 
+			@Override
 			public String nextElement() {
 				throw new NoSuchElementException();
 			}
 		};
 	}
 
+	@Override
 	public ServletContext getServletContext() {
 		return context;
 	}
 
+	@Override
 	public String getFilterName() {
 		return filterName;
 	}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
index 2414660..a402977 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
@@ -79,14 +79,17 @@ public RegexGroupFilter(final int groupIdx) {
 		this.groupIdx = groupIdx - 1;
 	}
 
+	@Override
 	public void init(FilterConfig config) throws ServletException {
 		// Do nothing.
 	}
 
+	@Override
 	public void destroy() {
 		// Do nothing.
 	}
 
+	@Override
 	public void doFilter(final ServletRequest request,
 			final ServletResponse rsp, final FilterChain chain)
 			throws IOException, ServletException {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
index 2ef7136..f33243b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
@@ -95,6 +95,7 @@ static class Binder extends ServletBinderImpl {
 			pattern = p;
 		}
 
+		@Override
 		UrlPipeline create() {
 			return new RegexPipeline(pattern, getFilters(), getServlet());
 		}
@@ -108,6 +109,7 @@ UrlPipeline create() {
 		this.pattern = pattern;
 	}
 
+	@Override
 	boolean match(final HttpServletRequest req) {
 		final String pathInfo = req.getPathInfo();
 		return pathInfo != null && pattern.matcher(pathInfo).matches();
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
index e96fc59..4e879a9 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
@@ -58,9 +58,10 @@ abstract class ServletBinderImpl implements ServletBinder {
 	private HttpServlet httpServlet;
 
 	ServletBinderImpl() {
-		this.filters = new ArrayList<Filter>();
+		this.filters = new ArrayList<>();
 	}
 
+	@Override
 	public ServletBinder through(Filter filter) {
 		if (filter == null)
 			throw new NullPointerException(HttpServerText.get().filterMustNotBeNull);
@@ -68,6 +69,7 @@ public ServletBinder through(Filter filter) {
 		return this;
 	}
 
+	@Override
 	public void with(HttpServlet servlet) {
 		if (servlet == null)
 			throw new NullPointerException(HttpServerText.get().servletMustNotBeNull);
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java
index b942016..903de63 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java
@@ -71,6 +71,7 @@ static class Binder extends ServletBinderImpl {
 			this.suffix = suffix;
 		}
 
+		@Override
 		UrlPipeline create() {
 			return new SuffixPipeline(suffix, getFilters(), getServlet());
 		}
@@ -87,6 +88,7 @@ UrlPipeline create() {
 		this.suffixLen = suffix.length();
 	}
 
+	@Override
 	boolean match(final HttpServletRequest req) {
 		final String pathInfo = req.getPathInfo();
 		return pathInfo != null && pathInfo.endsWith(suffix);
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java
index 2d0b844..56e4e22 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java
@@ -121,26 +121,32 @@ private static void initServlet(final HttpServlet ref,
 			throws ServletException {
 		if (!inited.contains(ref)) {
 			ref.init(new ServletConfig() {
+				@Override
 				public String getInitParameter(String name) {
 					return null;
 				}
 
+				@Override
 				public Enumeration<String> getInitParameterNames() {
 					return new Enumeration<String>() {
+						@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 @@ private static class Chain implements FilterChain {
 			this.servlet = servlet;
 		}
 
+		@Override
 		public void doFilter(ServletRequest req, ServletResponse rsp)
 				throws IOException, ServletException {
 			if (filterIdx < filters.length)
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
index 4ef2a7c..88ad472 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
@@ -72,6 +72,7 @@ public void access(HttpServletRequest req, Repository db)
 	};
 
 	private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
+		@Override
 		public ServiceConfig parse(final Config cfg) {
 			return new ServiceConfig(cfg);
 		}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
index 8c39b79..04e192b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
@@ -69,6 +69,7 @@
 public class DefaultReceivePackFactory implements
 		ReceivePackFactory<HttpServletRequest> {
 	private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
+		@Override
 		public ServiceConfig parse(final Config cfg) {
 			return new ServiceConfig(cfg);
 		}
@@ -85,6 +86,7 @@ private static class ServiceConfig {
 		}
 	}
 
+	@Override
 	public ReceivePack create(final HttpServletRequest req, final Repository db)
 			throws ServiceNotEnabledException, ServiceNotAuthorizedException {
 		final ServiceConfig cfg = db.getConfig().get(CONFIG);
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
index 34c069e..d01e2ef 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
@@ -62,6 +62,7 @@
 public class DefaultUploadPackFactory implements
 		UploadPackFactory<HttpServletRequest> {
 	private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
+		@Override
 		public ServiceConfig parse(final Config cfg) {
 			return new ServiceConfig(cfg);
 		}
@@ -75,6 +76,7 @@ private static class ServiceConfig {
 		}
 	}
 
+	@Override
 	public UploadPack create(final HttpServletRequest req, final Repository db)
 			throws ServiceNotEnabledException, ServiceNotAuthorizedException {
 		if (db.getConfig().get(CONFIG).enabled)
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.http.test/BUCK b/org.eclipse.jgit.http.test/BUCK
deleted file mode 100644
index f5cd10e..0000000
--- a/org.eclipse.jgit.http.test/BUCK
+++ /dev/null
@@ -1,41 +0,0 @@
-TESTS = glob(['tst/**/*.java'])
-
-for t in TESTS:
-  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
-  java_test(
-    name = n,
-    labels = ['http'],
-    srcs = [t],
-    deps = [
-      ':helpers',
-      '//org.eclipse.jgit:jgit',
-      '//org.eclipse.jgit.http.apache:http-apache',
-      '//org.eclipse.jgit.http.server:jgit-servlet',
-      '//org.eclipse.jgit.junit:junit',
-      '//org.eclipse.jgit.junit.http:junit-http',
-      '//lib:hamcrest-core',
-      '//lib:hamcrest-library',
-      '//lib:junit',
-      '//lib:servlet-api',
-      '//lib/jetty:http',
-      '//lib/jetty:io',
-      '//lib/jetty:server',
-      '//lib/jetty:servlet',
-      '//lib/jetty:security',
-      '//lib/jetty:util',
-      '//lib:commons-logging',
-    ],
-    vm_args = ['-Djava.io.tmpdir=buck-out'],
-  )
-
-java_library(
-  name = 'helpers',
-  srcs = glob(['src/**/*.java']),
-  deps = [
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.http.server:jgit-servlet',
-    '//org.eclipse.jgit.junit:junit',
-    '//org.eclipse.jgit.junit.http:junit-http',
-    '//lib:junit',
-  ],
-)
diff --git a/org.eclipse.jgit.http.test/BUILD b/org.eclipse.jgit.http.test/BUILD
new file mode 100644
index 0000000..ce2d611
--- /dev/null
+++ b/org.eclipse.jgit.http.test/BUILD
@@ -0,0 +1,42 @@
+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",
+        "//org.eclipse.jgit.http.server:jgit-servlet",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit.http:junit-http",
+        "//org.eclipse.jgit.junit:junit",
+    ],
+)
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 6effed6..82acb2b 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -22,24 +22,24 @@
  org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.http.server;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.http.server.glue;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.http.server.resolver;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.junit.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.resolver;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.http.server;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.http.server.glue;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.http.server.resolver;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index 0366f75..8865741 100644
--- a/org.eclipse.jgit.http.test/pom.xml
+++ b/org.eclipse.jgit.http.test/pom.xml
@@ -51,7 +51,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
index 0285bd1..0e92b14 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
@@ -80,6 +80,7 @@ public class AdvertiseErrorTest extends HttpTestCase {
 
 	private URIish remoteURI;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -90,6 +91,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
@@ -102,6 +104,7 @@ public Repository open(HttpServletRequest req, String name)
 			}
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
+			@Override
 			public ReceivePack create(HttpServletRequest req, Repository db)
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java
index c6b8f09..e94a792 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java
@@ -64,6 +64,7 @@ public class AsIsServiceTest extends LocalDiskRepositoryTestCase {
 
 	private AsIsFileService service;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
index f2879e0..b24e2df 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
@@ -71,6 +71,7 @@ public class DefaultReceivePackFactoryTest extends LocalDiskRepositoryTestCase {
 
 	private ReceivePackFactory<HttpServletRequest> factory;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
index 3bcb057..ce24d64 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
@@ -69,6 +69,7 @@ public class DefaultUploadPackFactoryTest extends LocalDiskRepositoryTestCase {
 
 	private UploadPackFactory<HttpServletRequest> factory;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
index 677132d..ab6dc35 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
@@ -109,6 +109,7 @@ public DumbClientDumbServerTest(HttpConnectionFactory cf) {
 		HttpTransport.setConnectionFactory(cf);
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -212,7 +213,7 @@ public void testInitialClone_Loose() throws Exception {
 
 	@Test
 	public void testInitialClone_Packed() throws Exception {
-		new TestRepository<Repository>(remoteRepository).packAndPrune();
+		new TestRepository<>(remoteRepository).packAndPrune();
 
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
index bce44f9..06bfd79 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
@@ -114,6 +114,7 @@ public DumbClientSmartServerTest(HttpConnectionFactory cf) {
 		HttpTransport.setConnectionFactory(cf);
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -124,6 +125,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
@@ -239,7 +241,7 @@ public void testInitialClone_Small() throws Exception {
 
 	@Test
 	public void testInitialClone_Packed() throws Exception {
-		new TestRepository<Repository>(remoteRepository).packAndPrune();
+		new TestRepository<>(remoteRepository).packAndPrune();
 
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
index 7c6d591..82e79b8 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
@@ -83,7 +83,7 @@ public void testUnreasonableNames() throws ServiceNotEnabledException {
 
 	private static void assertUnreasonable(String name)
 			throws ServiceNotEnabledException {
-		FileResolver<RepositoryResolver> r = new FileResolver<RepositoryResolver>(
+		FileResolver<RepositoryResolver> r = new FileResolver<>(
 				new File("."), false);
 		try {
 			r.open(null, name);
@@ -103,7 +103,7 @@ public void testExportOk() throws IOException {
 		FileResolver<RepositoryResolver> resolver;
 
 		assertFalse("no git-daemon-export-ok", export.exists());
-		resolver = new FileResolver<RepositoryResolver>(base, false /*
+		resolver = new FileResolver<>(base, false /*
 																	 * require
 																	 * flag
 																	 */);
@@ -114,7 +114,7 @@ public void testExportOk() throws IOException {
 			assertEquals("Service not enabled", e.getMessage());
 		}
 
-		resolver = new FileResolver<RepositoryResolver>(base, true /*
+		resolver = new FileResolver<>(base, true /*
 																	 * export
 																	 * all
 																	 */);
@@ -125,7 +125,7 @@ public void testExportOk() throws IOException {
 		}
 
 		FileUtils.createNewFile(export);
-		resolver = new FileResolver<RepositoryResolver>(base, false /*
+		resolver = new FileResolver<>(base, false /*
 																	 * require
 																	 * flag
 																	 */);
@@ -142,7 +142,7 @@ public void testNotAGitRepository() throws IOException,
 		final Repository a = createBareRepository();
 		final String name = a.getDirectory().getName() + "-not-a-git";
 		final File base = a.getDirectory().getParentFile();
-		FileResolver<RepositoryResolver> resolver = new FileResolver<RepositoryResolver>(
+		FileResolver<RepositoryResolver> resolver = new FileResolver<>(
 				base, false);
 
 		try {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
index 4b15d4b..de7891c 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
@@ -107,6 +107,7 @@ public class GitServletResponseTests extends HttpTestCase {
 	 * 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();
@@ -117,6 +118,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
@@ -129,6 +131,7 @@ public Repository open(HttpServletRequest req, String name)
 			}
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
+			@Override
 			public ReceivePack create(HttpServletRequest req, Repository db)
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
@@ -266,10 +269,11 @@ public void testUnpackErrorWithSubsequentExceptionInPostReceiveHook()
 		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<ReceiveCommand> commands) {
 				// the maxPackSize setting caused that the packfile couldn't be
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index d67c817..adb69ec 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -88,6 +88,7 @@ public class HookMessageTest extends HttpTestCase {
 
 	private URIish remoteURI;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -98,6 +99,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
@@ -110,11 +112,13 @@ public Repository open(HttpServletRequest req, String name)
 			}
 		});
 		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<ReceiveCommand> commands) {
 						rp.sendMessage("message line 1");
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index ce78442..6dbe0e3 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -94,6 +94,7 @@ public class HttpClientTests extends HttpTestCase {
 
 	private URIish smartAuthBasicURI;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -132,6 +133,7 @@ private ServletContextHandler dumb(final String path) {
 	private ServletContextHandler smart(final String path) {
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
index 108e7bb..4c08ec2 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
@@ -83,6 +83,7 @@ public class MeasurePackSizeTest extends HttpTestCase {
 
 	long packSize = -1;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -93,6 +94,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
@@ -105,12 +107,14 @@ public Repository open(HttpServletRequest req, String name)
 			}
 		});
 		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<ReceiveCommand> commands) {
 						packSize = rp.getPackSize();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
index 68c5a3a..87d0bad 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
@@ -84,6 +84,7 @@ public class ProtocolErrorTest extends HttpTestCase {
 
 	private RevBlob a_blob;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -94,6 +95,7 @@ public void setUp() throws Exception {
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
index 64fbc01..725a590 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
@@ -93,12 +93,14 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res)
 		}
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		server = new AppServer();
 		ctx = server.addContext("/");
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		server.tearDown();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java
index 5be7834..ef8daec 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SetAdditionalHeadersTest.java
@@ -77,6 +77,7 @@ public class SetAdditionalHeadersTest extends HttpTestCase {
 	private RevCommit A, B;
 
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -111,7 +112,7 @@ public void testSetHeaders() throws IOException {
 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
 
-			HashMap<String, String> headers = new HashMap<String, String>();
+			HashMap<String, String> headers = new HashMap<>();
 			headers.put("Cookie", "someTokenValue=23gBog34");
 			headers.put("AnotherKey", "someValue");
 			((TransportHttp) t).setAdditionalHeaders(headers);
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 3e88271..ed223c9 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -112,6 +112,7 @@
 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.util.HttpSupport;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -128,6 +129,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
 
 	private URIish brokenURI;
 
+	private URIish redirectURI;
+
 	private RevBlob A_txt;
 
 	private RevCommit A, B;
@@ -144,6 +147,7 @@ public SmartClientSmartServerTest(HttpConnectionFactory cf) {
 		HttpTransport.setConnectionFactory(cf);
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -155,13 +159,42 @@ public void setUp() throws Exception {
 				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
 
-		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
+
+		ServletContextHandler app = addNormalContext(gs, src, srcName);
+
+		ServletContextHandler broken = addBrokenContext(gs, src, srcName);
+
+		ServletContextHandler redirect = addRedirectContext(gs, src, srcName);
+
+		server.setUp();
+
+		remoteRepository = src.getRepository();
+		remoteURI = toURIish(app, srcName);
+		brokenURI = toURIish(broken, srcName);
+		redirectURI = toURIish(redirect, 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);
+
+		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
+	}
+
+	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
+		ServletContextHandler app = server.addContext("/git");
 		gs.setRepositoryResolver(new TestRepoResolver(src, srcName));
 		app.addServlet(new ServletHolder(gs), "/*");
+		return app;
+	}
 
+	@SuppressWarnings("unused")
+	private ServletContextHandler addBrokenContext(GitServlet gs, TestRepository<Repository> 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 {
@@ -173,29 +206,58 @@ public void doFilter(ServletRequest request,
 				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();
+	@SuppressWarnings("unused")
+	private ServletContextHandler addRedirectContext(GitServlet gs,
+			TestRepository<Repository> src, String srcName) {
+		ServletContextHandler redirect = server.addContext("/redirect");
+		redirect.addFilter(new FilterHolder(new Filter() {
 
-		remoteRepository = src.getRepository();
-		remoteURI = toURIish(app, srcName);
-		brokenURI = toURIish(broken, srcName);
+			@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());
+				}
+				httpServletResponse
+						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
+						fullUrl.toString().replace("/redirect", "/git"));
+			}
 
-		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
@@ -312,6 +374,52 @@ public void testInitialClone_Small() throws Exception {
 	}
 
 	@Test
+	public void testInitialClone_RedirectSmall() throws Exception {
+		Repository dst = createBareRepository();
+		assertFalse(dst.hasObject(A_txt));
+
+		try (Transport t = Transport.open(dst, redirectURI)) {
+			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+		}
+
+		assertTrue(dst.hasObject(A_txt));
+		assertEquals(B, dst.exactRef(master).getObjectId());
+		fsck(dst, B);
+
+		List<AccessEvent> requests = getRequests();
+		assertEquals(4, requests.size());
+
+		AccessEvent firstRedirect = requests.get(0);
+		assertEquals(301, firstRedirect.getStatus());
+
+		AccessEvent info = requests.get(1);
+		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 secondRedirect = requests.get(2);
+		assertEquals(301, secondRedirect.getStatus());
+
+		AccessEvent service = requests.get(3);
+		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 testFetch_FewLocalCommits() throws Exception {
 		// Bootstrap by doing the clone.
 		//
@@ -329,7 +437,7 @@ public void testFetch_FewLocalCommits() throws Exception {
 
 		// Create a new commit on the remote.
 		//
-		b = new TestRepository<Repository>(remoteRepository).branch(master);
+		b = new TestRepository<>(remoteRepository).branch(master);
 		RevCommit Z = b.commit().message("Z").create();
 
 		// Now incrementally update.
@@ -389,7 +497,7 @@ public void testFetch_TooManyLocalCommits() throws Exception {
 
 		// Create a new commit on the remote.
 		//
-		b = new TestRepository<Repository>(remoteRepository).branch(master);
+		b = new TestRepository<>(remoteRepository).branch(master);
 		RevCommit Z = b.commit().message("Z").create();
 
 		// Now incrementally update.
@@ -506,7 +614,7 @@ public void testFetch_RefsUnreadableOnUpload() throws Exception {
 			final String repoName = "refs-unreadable";
 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
 					new DfsRepositoryDescription(repoName));
-			final TestRepository<Repository> repo = new TestRepository<Repository>(
+			final TestRepository<Repository> repo = new TestRepository<>(
 					badRefsRepo);
 
 			ServletContextHandler app = noRefServer.addContext("/git");
@@ -727,6 +835,7 @@ private TestRepoResolver(TestRepository<Repository> repo,
 			this.repoName = repoName;
 		}
 
+		@Override
 		public Repository open(HttpServletRequest req, String name)
 				throws RepositoryNotFoundException, ServiceNotEnabledException {
 			if (!name.equals(repoName))
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.junit.http/BUCK b/org.eclipse.jgit.junit.http/BUCK
deleted file mode 100644
index 68976a6..0000000
--- a/org.eclipse.jgit.junit.http/BUCK
+++ /dev/null
@@ -1,18 +0,0 @@
-java_library(
-  name = 'junit-http',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  provided_deps = [
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.http.server:jgit-servlet',
-    '//org.eclipse.jgit.junit:junit',
-    '//lib:junit',
-    '//lib:servlet-api',
-    '//lib/jetty:http',
-    '//lib/jetty:server',
-    '//lib/jetty:servlet',
-    '//lib/jetty:security',
-    '//lib/jetty:util',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.junit.http/BUILD b/org.eclipse.jgit.junit.http/BUILD
new file mode 100644
index 0000000..be6e1ae
--- /dev/null
+++ b/org.eclipse.jgit.junit.http/BUILD
@@ -0,0 +1,22 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "junit-http",
+    testonly = 1,
+    srcs = glob(["src/**"]),
+    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 --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index cd42e2c..8371aee 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -20,22 +20,23 @@
  org.eclipse.jetty.util.component;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.http.server;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.resolver;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.http.server;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.7.8,4.8.0)",
  org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="4.6.2";
+Export-Package: org.eclipse.jgit.junit.http;version="4.7.8";
   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 --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index ced9c2c..92b7885 100644
--- a/org.eclipse.jgit.junit.http/pom.xml
+++ b/org.eclipse.jgit.junit.http/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
index aaccc66..6b7853d 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
@@ -76,7 +76,7 @@ public class AccessEvent {
 	}
 
 	private static Map<String, String> cloneHeaders(final Request req) {
-		Map<String, String> r = new TreeMap<String, String>();
+		Map<String, String> r = new TreeMap<>();
 		Enumeration hn = req.getHeaderNames();
 		while (hn.hasMoreElements()) {
 			String key = (String) hn.nextElement();
@@ -88,7 +88,7 @@ private static Map<String, String> cloneHeaders(final Request req) {
 	}
 
 	private static Map<String, String> cloneHeaders(final Response rsp) {
-		Map<String, String> r = new TreeMap<String, String>();
+		Map<String, String> r = new TreeMap<>();
 		Enumeration<String> hn = rsp.getHttpFields().getFieldNames();
 		while (hn.hasMoreElements()) {
 			String key = hn.nextElement();
@@ -153,6 +153,7 @@ public String getResponseHeader(String name) {
 		return responseHeaders.get(name);
 	}
 
+	@Override
 	public String toString() {
 		StringBuilder b = new StringBuilder();
 		b.append(method);
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
index 44c1977..a663484 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
@@ -269,7 +269,7 @@ public int getPort() {
 
 	/** @return all requests since the server was started. */
 	public List<AccessEvent> getRequests() {
-		return new ArrayList<AccessEvent>(log.getEvents());
+		return new ArrayList<>(log.getEvents());
 	}
 
 	/**
@@ -289,7 +289,7 @@ public List<AccessEvent> getRequests(URIish base, String path) {
 	 * @return all requests which match the given path.
 	 */
 	public List<AccessEvent> getRequests(String path) {
-		ArrayList<AccessEvent> r = new ArrayList<AccessEvent>();
+		ArrayList<AccessEvent> r = new ArrayList<>();
 		for (AccessEvent event : log.getEvents()) {
 			if (event.getPath().equals(path)) {
 				r.add(event);
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
index ab5d3e1..1b94e02 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
@@ -74,11 +74,13 @@ public abstract class HttpTestCase extends LocalDiskRepositoryTestCase {
 	/** In-memory application server; subclass must start. */
 	protected AppServer server;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
 		server = new AppServer();
 	}
 
+	@Override
 	public void tearDown() throws Exception {
 		server.tearDown();
 		super.tearDown();
@@ -86,7 +88,7 @@ public void tearDown() throws Exception {
 
 	protected TestRepository<Repository> createTestRepository()
 			throws IOException {
-		return new TestRepository<Repository>(createBareRepository());
+		return new TestRepository<>(createBareRepository());
 	}
 
 	protected URIish toURIish(String path) throws URISyntaxException {
@@ -118,12 +120,12 @@ protected List<AccessEvent> getRequests(String path) {
 	protected static void fsck(Repository db, RevObject... tips)
 			throws Exception {
 		TestRepository<? extends Repository> tr =
-				new TestRepository<Repository>(db);
+				new TestRepository<>(db);
 		tr.fsck(tips);
 	}
 
 	protected static Set<RefSpec> mirror(String... refs) {
-		HashSet<RefSpec> r = new HashSet<RefSpec>();
+		HashSet<RefSpec> r = new HashSet<>();
 		for (String name : refs) {
 			RefSpec rs = new RefSpec(name);
 			rs = rs.setDestination(name);
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
index 5976589..9defcd9 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
@@ -52,33 +52,39 @@
 import javax.servlet.ServletContext;
 
 public class MockServletConfig implements ServletConfig {
-	private final Map<String, String> parameters = new HashMap<String, String>();
+	private final Map<String, String> parameters = new HashMap<>();
 
 	public void setInitParameter(String name, String value) {
 		parameters.put(name, value);
 	}
 
+	@Override
 	public String getInitParameter(String name) {
 		return parameters.get(name);
 	}
 
+	@Override
 	public Enumeration<String> getInitParameterNames() {
 		final Iterator<String> i = parameters.keySet().iterator();
 		return new Enumeration<String>() {
+			@Override
 			public boolean hasMoreElements() {
 				return i.hasNext();
 			}
 
+			@Override
 			public String nextElement() {
 				return i.next();
 			}
 		};
 	}
 
+	@Override
 	public String getServletName() {
 		return "MOCK_SERVLET";
 	}
 
+	@Override
 	public ServletContext getServletContext() {
 		return null;
 	}
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
index 7600843..415398d 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
@@ -52,7 +52,7 @@
 
 /** Logs warnings into an array for later inspection. */
 public class RecordingLogger implements Logger {
-	private static List<Warning> warnings = new ArrayList<Warning>();
+	private static List<Warning> warnings = new ArrayList<>();
 
 	/** Clear the warnings, automatically done by {@link AppServer#setUp()} */
 	public static void clear() {
@@ -64,7 +64,7 @@ public static void clear() {
 	/** @return the warnings (if any) from the last execution */
 	public static List<Warning> getWarnings() {
 		synchronized (warnings) {
-			ArrayList<Warning> copy = new ArrayList<Warning>(warnings);
+			ArrayList<Warning> copy = new ArrayList<>(warnings);
 			return Collections.unmodifiableList(copy);
 		}
 	}
@@ -94,10 +94,12 @@ public RecordingLogger(final String name) {
 		this.name = name;
 	}
 
+	@Override
 	public Logger getLogger(@SuppressWarnings("hiding") String name) {
 		return new RecordingLogger(name);
 	}
 
+	@Override
 	public String getName() {
 		return name;
 	}
@@ -108,6 +110,7 @@ public void warn(String msg, Object arg0, Object arg1) {
 		}
 	}
 
+	@Override
 	public void warn(String msg, Throwable th) {
 		synchronized (warnings) {
 			warnings.add(new Warning(msg, th));
@@ -126,6 +129,7 @@ public void debug(@SuppressWarnings("unused") String msg,
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void debug(String msg, Throwable th) {
 		// Ignore (not relevant to test failures)
 	}
@@ -144,46 +148,56 @@ public void info(@SuppressWarnings("unused") String msg) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public boolean isDebugEnabled() {
 		return false;
 	}
 
+	@Override
 	public void setDebugEnabled(boolean enabled) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void warn(String msg, Object... args) {
 		synchronized (warnings) {
 			warnings.add(new Warning(MessageFormat.format(msg, args)));
 		}
 	}
 
+	@Override
 	public void warn(Throwable thrown) {
 		synchronized (warnings) {
 			warnings.add(new Warning(thrown));
 		}
 	}
 
+	@Override
 	public void info(String msg, Object... args) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void info(Throwable thrown) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void info(String msg, Throwable thrown) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void debug(String msg, Object... args) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void debug(Throwable thrown) {
 		// Ignore (not relevant to test failures)
 	}
 
+	@Override
 	public void ignore(Throwable arg0) {
 		// Ignore (not relevant to test failures)
 	}
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
index e550e6c..605c69a 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
@@ -92,6 +92,7 @@ public URIish getUri() {
 	private ServletContextHandler smart(final String path) {
 		GitServlet gs = new GitServlet();
 		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
+			@Override
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java
index 14ea03a..c218c07 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java
@@ -61,7 +61,7 @@
 class TestRequestLog extends HandlerWrapper {
   private static final int MAX = 16;
 
-  private final List<AccessEvent> events = new ArrayList<AccessEvent>();
+  private final List<AccessEvent> events = new ArrayList<>();
 
   private final Semaphore active = new Semaphore(MAX);
 
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
new file mode 100644
index 0000000..a70ce77
--- /dev/null
+++ b/org.eclipse.jgit.junit/.settings/.api_filters
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.junit" version="2">
+    <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
+        <filter comment="OK to use internal implementation in tests" id="643842064">
+            <message_arguments>
+                <message_argument value="FileRepository"/>
+                <message_argument value="LocalDiskRepositoryTestCase"/>
+                <message_argument value="createBareRepository()"/>
+            </message_arguments>
+        </filter>
+        <filter comment="OK to use internal implementation in tests" id="643842064">
+            <message_arguments>
+                <message_argument value="FileRepository"/>
+                <message_argument value="LocalDiskRepositoryTestCase"/>
+                <message_argument value="createRepository(boolean, boolean)"/>
+            </message_arguments>
+        </filter>
+        <filter comment="OK to use internal implementation in tests" id="643842064">
+            <message_arguments>
+                <message_argument value="FileRepository"/>
+                <message_argument value="LocalDiskRepositoryTestCase"/>
+                <message_argument value="createWorkRepository()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/junit/RepositoryTestCase.java" type="org.eclipse.jgit.junit.RepositoryTestCase">
+        <filter comment="OK to use internal implementation in tests" id="627060751">
+            <message_arguments>
+                <message_argument value="FileRepository"/>
+                <message_argument value="RepositoryTestCase"/>
+                <message_argument value="db"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.junit/BUCK b/org.eclipse.jgit.junit/BUCK
deleted file mode 100644
index 7e25432..0000000
--- a/org.eclipse.jgit.junit/BUCK
+++ /dev/null
@@ -1,10 +0,0 @@
-java_library(
-  name = 'junit',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  provided_deps = [
-    '//org.eclipse.jgit:jgit',
-    '//lib:junit',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.junit/BUILD b/org.eclipse.jgit.junit/BUILD
new file mode 100644
index 0000000..350b25f
--- /dev/null
+++ b/org.eclipse.jgit.junit/BUILD
@@ -0,0 +1,14 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "junit",
+    testonly = 1,
+    srcs = glob(["src/**"]),
+    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 --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 9de6239..4c3f91a 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -2,31 +2,31 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.api.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.dircache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.merge;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util.io;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util.time;version="[4.6.2,4.7.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.api.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.dircache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.merge;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.io;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.time;version="[4.7.8,4.8.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.rules;version="[4.9.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
  org.junit.runners.model;version="[4.5.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="4.6.2";
+Export-Package: org.eclipse.jgit.junit;version="4.7.8";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -35,4 +35,4 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.storage.file,
    org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="4.6.2"
+ org.eclipse.jgit.junit.time;version="4.7.8"
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index ab757af..95af045 100644
--- a/org.eclipse.jgit.junit/pom.xml
+++ b/org.eclipse.jgit.junit/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index dc2e8bf..6ace9fc 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -53,13 +53,16 @@
 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.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -106,7 +109,7 @@ public abstract class LocalDiskRepositoryTestCase {
 	 */
 	protected MockSystemReader mockSystemReader;
 
-	private final List<Repository> toClose = new ArrayList<Repository>();
+	private final Set<Repository> toClose = new HashSet<>();
 	private File tmp;
 
 	@Before
@@ -119,6 +122,12 @@ public void setUp() throws Exception {
 		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);
 
@@ -284,7 +293,7 @@ public static String indexState(Repository repo, int includedOptions)
 			throws IllegalStateException, IOException {
 		DirCache dc = repo.readDirCache();
 		StringBuilder sb = new StringBuilder();
-		TreeSet<Long> timeStamps = new TreeSet<Long>();
+		TreeSet<Long> timeStamps = new TreeSet<>();
 
 		// iterate once over the dircache just to collect all time stamps
 		if (0 != (includedOptions & MOD_TIME)) {
@@ -357,12 +366,32 @@ protected FileRepository createWorkRepository() throws IOException {
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
-	private FileRepository createRepository(boolean bare) throws IOException {
+	private FileRepository createRepository(boolean bare)
+			throws IOException {
+		return createRepository(bare, true /* auto close */);
+	}
+
+	/**
+	 * Creates a new empty repository.
+	 *
+	 * @param bare
+	 *            true to create a bare repository; false to make a repository
+	 *            within its working directory
+	 * @param autoClose
+	 *            auto close the repository in #tearDown
+	 * @return the newly created repository, opened for access
+	 * @throws IOException
+	 *             the repository could not be created in the temporary area
+	 */
+	public FileRepository createRepository(boolean bare, boolean autoClose)
+			throws IOException {
 		File gitdir = createUniqueTestGitDir(bare);
 		FileRepository db = new FileRepository(gitdir);
 		assertFalse(gitdir.exists());
 		db.create(bare);
-		toClose.add(db);
+		if (autoClose) {
+			addRepoToClose(db);
+		}
 		return db;
 	}
 
@@ -530,7 +559,7 @@ protected String read(final File f) throws IOException {
 	}
 
 	private static HashMap<String, String> cloneEnv() {
-		return new HashMap<String, String>(System.getenv());
+		return new HashMap<>(System.getenv());
 	}
 
 	private static final class CleanupThread extends Thread {
@@ -552,7 +581,7 @@ static void removed(File tmp) {
 			}
 		}
 
-		private final List<File> toDelete = new ArrayList<File>();
+		private final List<File> toDelete = new ArrayList<>();
 
 		@Override
 		public void run() {
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 6faa2ec..68482c6 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -88,7 +88,7 @@ public boolean isOutdated() {
 
 	long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
 
-	final Map<String, String> values = new HashMap<String, String>();
+	final Map<String, String> values = new HashMap<>();
 
 	FileBasedConfig userGitConfig;
 
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index a44e999..d1358ee 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -876,7 +876,7 @@ public void packAndPrune() throws Exception {
 
 			final File pack, idx;
 			try (PackWriter pw = new PackWriter(db)) {
-				Set<ObjectId> all = new HashSet<ObjectId>();
+				Set<ObjectId> all = new HashSet<>();
 				for (Ref r : db.getAllRefs().values())
 					all.add(r.getObjectId());
 				pw.preparePack(m, all, PackWriter.NONE);
@@ -992,7 +992,7 @@ public class CommitBuilder {
 
 		private ObjectId topLevelTree;
 
-		private final List<RevCommit> parents = new ArrayList<RevCommit>(2);
+		private final List<RevCommit> parents = new ArrayList<>(2);
 
 		private int tick = 1;
 
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.lfs.server.test/BUCK b/org.eclipse.jgit.lfs.server.test/BUCK
deleted file mode 100644
index 25e9f09..0000000
--- a/org.eclipse.jgit.lfs.server.test/BUCK
+++ /dev/null
@@ -1,34 +0,0 @@
-TEST_BASE = ['tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java']
-TESTS = glob(['tst/**/*.java'],
-    excludes = TEST_BASE
-)
-
-for t in TESTS:
-  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
-  java_test(
-    name = n,
-    labels = ['lfs-server'],
-    srcs = [t] + TEST_BASE,
-    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:hamcrest-core',
-      '//lib:hamcrest-library',
-      '//lib:httpcore',
-      '//lib:httpcomponents',
-      '//lib:junit',
-      '//lib/jetty:http',
-      '//lib/jetty:io',
-      '//lib/jetty:server',
-      '//lib/jetty:servlet',
-      '//lib/jetty:security',
-      '//lib/jetty:util',
-      '//lib:servlet-api',
-      '//lib:commons-logging',
-    ],
-    vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'],
-  )
diff --git a/org.eclipse.jgit.lfs.server.test/BUILD b/org.eclipse.jgit.lfs.server.test/BUILD
new file mode 100644
index 0000000..1341dd6
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server.test/BUILD
@@ -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 --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index 405e07a..0304aaa 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -27,11 +27,11 @@
  org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.junit.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.test;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.junit.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.test;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index d178fef..792a310 100644
--- a/org.eclipse.jgit.lfs.server.test/pom.xml
+++ b/org.eclipse.jgit.lfs.server.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
index 808ec3a..ede0f7d 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.lfs.server/BUCK b/org.eclipse.jgit.lfs.server/BUCK
deleted file mode 100644
index 6b40b7c..0000000
--- a/org.eclipse.jgit.lfs.server/BUCK
+++ /dev/null
@@ -1,22 +0,0 @@
-SRCS = glob(['src/**'])
-RESOURCES = glob(['resources/**'])
-
-java_library(
-  name = 'jgit-lfs-server',
-  srcs = SRCS,
-  resources = RESOURCES,
-  deps = [
-    '//org.eclipse.jgit.http.apache:http-apache',
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.lfs:jgit-lfs',
-    '//lib:gson',
-    '//lib:httpcore',
-    '//lib:servlet-api'
-  ],
-  visibility = ['PUBLIC'],
-)
-
-java_sources(
-  name = 'jgit-lfs-server_src',
-  srcs = SRCS + RESOURCES,
-)
diff --git a/org.eclipse.jgit.lfs.server/BUILD b/org.eclipse.jgit.lfs.server/BUILD
new file mode 100644
index 0000000..fa14e8a
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/BUILD
@@ -0,0 +1,17 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "jgit-lfs-server",
+    srcs = glob(["src/**"]),
+    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 --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 3e8a9d1..826e02f 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -2,19 +2,19 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="4.6.2";
+Export-Package: org.eclipse.jgit.lfs.server;version="4.7.8";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="4.6.2";
+ org.eclipse.jgit.lfs.server.fs;version="4.7.8";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="4.6.2";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="4.6.2";
+ org.eclipse.jgit.lfs.server.internal;version="4.7.8";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="4.7.8";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -24,13 +24,14 @@
  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.6.2,4.7.0)",
- org.eclipse.jgit.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)"
+ org.eclipse.jgit.annotations;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index cc84686..3cdb144 100644
--- a/org.eclipse.jgit.lfs.server/pom.xml
+++ b/org.eclipse.jgit.lfs.server/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
index 841074b..2473dcd 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
@@ -49,7 +49,12 @@
 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;
@@ -58,6 +63,7 @@
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
+import java.text.MessageFormat;
 import java.util.List;
 
 import javax.servlet.ServletException;
@@ -71,8 +77,12 @@
 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
@@ -86,6 +96,8 @@
  * @since 4.3
  */
 public abstract class LfsProtocolServlet extends HttpServlet {
+	private static Logger LOG = LoggerFactory
+			.getLogger(LfsProtocolServlet.class);
 
 	private static final long serialVersionUID = 1L;
 
@@ -132,9 +144,54 @@ public abstract class LfsProtocolServlet extends HttpServlet {
 	 *             <dd>when an unexpected internal server error occurred</dd>
 	 *             </dl>
 	 * @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 LfsException
+	 *             implementations should throw more specific exceptions to
+	 *             signal which type of error occurred:
+	 *             <dl>
+	 *             <dt>{@link LfsValidationError}</dt>
+	 *             <dd>when there is a validation error with one or more of the
+	 *             objects in the request</dd>
+	 *             <dt>{@link LfsRepositoryNotFound}</dt>
+	 *             <dd>when the repository does not exist for the user</dd>
+	 *             <dt>{@link LfsRepositoryReadOnly}</dt>
+	 *             <dd>when the user has read, but not write access. Only
+	 *             applicable when the operation in the request is "upload"</dd>
+	 *             <dt>{@link LfsRateLimitExceeded}</dt>
+	 *             <dd>when the user has hit a rate limit with the server</dd>
+	 *             <dt>{@link LfsBandwidthLimitExceeded}</dt>
+	 *             <dd>when the bandwidth limit for the user or repository has
+	 *             been exceeded</dd>
+	 *             <dt>{@link LfsInsufficientStorage}</dt>
+	 *             <dd>when there is insufficient storage on the server</dd>
+	 *             <dt>{@link LfsUnavailable}</dt>
+	 *             <dd>when LFS is not available</dd>
+	 *             <dt>{@link LfsException}</dt>
+	 *             <dd>when an unexpected internal server error occurred</dd>
+	 *             </dl>
+	 * @since 4.7
 	 */
 	protected abstract LargeFileRepository getLargeFileRepository(
-			LfsRequest request, String path) throws LfsException;
+			LfsRequest request, String path, String auth) throws LfsException;
 
 	/**
 	 * LFS request.
@@ -163,6 +220,30 @@ public String getOperation() {
 		public List<LfsObject> 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);
+		}
 	}
 
 	@Override
@@ -179,9 +260,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse res)
 		res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
 		LargeFileRepository repo = null;
 		try {
-			repo = getLargeFileRepository(request, path);
+			repo = getLargeFileRepository(request, path,
+					req.getHeader(HDR_AUTHORIZATION));
 			if (repo == null) {
-				throw new LfsException("unexpected error"); //$NON-NLS-1$
+				String error = MessageFormat
+						.format(LfsText.get().lfsFailedToGetRepository, path);
+				LOG.error(error);
+				throw new LfsException(error);
 			}
 			res.setStatus(SC_OK);
 			TransferHandler handler = TransferHandler
@@ -201,6 +286,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse res)
 			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 {
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
index bf5b61c..86ca2d3 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
@@ -44,6 +44,9 @@
 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;
@@ -58,18 +61,14 @@
 
 abstract class TransferHandler {
 
-	private static final String DOWNLOAD = "download"; //$NON-NLS-1$
-	private static final String UPLOAD = "upload"; //$NON-NLS-1$
-	private static final String VERIFY = "verify"; //$NON-NLS-1$
-
 	static TransferHandler forOperation(String operation,
 			LargeFileRepository repository, List<LfsObject> objects) {
 		switch (operation) {
-		case TransferHandler.UPLOAD:
+		case UPLOAD:
 			return new Upload(repository, objects);
-		case TransferHandler.DOWNLOAD:
+		case DOWNLOAD:
 			return new Download(repository, objects);
-		case TransferHandler.VERIFY:
+		case VERIFY:
 		default:
 			throw new UnsupportedOperationException(MessageFormat.format(
 					LfsServerText.get().unsupportedOperation, operation));
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
index a8e3c11..15c4448 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
@@ -202,6 +202,11 @@ static class Error {
 	 */
 	protected static void sendError(HttpServletResponse rsp, int status, String message)
 			throws IOException {
+		if (rsp.isCommitted()) {
+			rsp.getOutputStream().close();
+			return;
+		}
+		rsp.reset();
 		rsp.setStatus(status);
 		PrintWriter writer = rsp.getWriter();
 		gson.toJson(new Error(message), writer);
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
index f179b6c..a76f7ef 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
@@ -61,9 +61,11 @@
 import org.eclipse.jgit.util.HttpSupport;
 
 /**
- * Handle asynchronous large object download
+ * Handle asynchronous large object download.
+ *
+ * @since 4.7
  */
-class ObjectDownloadListener implements WriteListener {
+public class ObjectDownloadListener implements WriteListener {
 
 	private static Logger LOG = Logger
 			.getLogger(ObjectDownloadListener.class.getName());
@@ -78,7 +80,7 @@ class ObjectDownloadListener implements WriteListener {
 
 	private final WritableByteChannel outChannel;
 
-	private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+	private ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
 
 	/**
 	 * @param repository
@@ -113,19 +115,35 @@ public ObjectDownloadListener(FileLfsRepository repository,
 	@Override
 	public void onWritePossible() throws IOException {
 		while (out.isReady()) {
-			if (in.read(buffer) != -1) {
-				buffer.flip();
-				outChannel.write(buffer);
-				buffer.compact();
-			} else {
-				in.close();
-				buffer.flip();
-				while (out.isReady()) {
-					if (buffer.hasRemaining()) {
-						outChannel.write(buffer);
-					} else {
+			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;
 				}
 			}
 		}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
index 84e4e6f..da86880 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
@@ -150,7 +150,9 @@ protected void close() throws IOException {
 			channel.close();
 			// TODO check if status 200 is ok for PUT request, HTTP foresees 204
 			// for successful PUT without response body
-			response.setStatus(HttpServletResponse.SC_OK);
+			if (!response.isCommitted()) {
+				response.setStatus(HttpServletResponse.SC_OK);
+			}
 		} finally {
 			context.complete();
 		}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java
index c229758..ed896ad 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/S3Repository.java
@@ -94,10 +94,10 @@ public S3Repository(S3Config config) {
 	@Override
 	public Response.Action getDownloadAction(AnyLongObjectId oid) {
 		URL endpointUrl = getObjectUrl(oid);
-		Map<String, String> queryParams = new HashMap<String, String>();
+		Map<String, String> queryParams = new HashMap<>();
 		queryParams.put(X_AMZ_EXPIRES,
 				Integer.toString(s3Config.getExpirationSeconds()));
-		Map<String, String> headers = new HashMap<String, String>();
+		Map<String, String> headers = new HashMap<>();
 		String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
 				s3Config, endpointUrl, METHOD_GET, headers, queryParams,
 				UNSIGNED_PAYLOAD);
@@ -111,7 +111,7 @@ public Response.Action getDownloadAction(AnyLongObjectId oid) {
 	public Response.Action getUploadAction(AnyLongObjectId oid, long size) {
 		cacheObjectMetaData(oid, size);
 		URL objectUrl = getObjectUrl(oid);
-		Map<String, String> headers = new HashMap<String, String>();
+		Map<String, String> 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());
@@ -134,10 +134,10 @@ public Action getVerifyAction(AnyLongObjectId id) {
 	@Override
 	public long getSize(AnyLongObjectId oid) throws IOException {
 		URL endpointUrl = getObjectUrl(oid);
-		Map<String, String> queryParams = new HashMap<String, String>();
+		Map<String, String> queryParams = new HashMap<>();
 		queryParams.put(X_AMZ_EXPIRES,
 				Integer.toString(s3Config.getExpirationSeconds()));
-		Map<String, String> headers = new HashMap<String, String>();
+		Map<String, String> headers = new HashMap<>();
 
 		String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
 				s3Config, endpointUrl, METHOD_HEAD, headers, queryParams,
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
index f95b605..a9b0ec4 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
@@ -57,6 +57,7 @@
 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;
@@ -239,7 +240,7 @@ private static void addHostHeader(URL url,
 
 	private static String canonicalizeHeaderNames(
 			Map<String, String> headers) {
-		List<String> sortedHeaders = new ArrayList<String>();
+		List<String> sortedHeaders = new ArrayList<>();
 		sortedHeaders.addAll(headers.keySet());
 		Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
 
@@ -247,7 +248,7 @@ private static String canonicalizeHeaderNames(
 		for (String header : sortedHeaders) {
 			if (buffer.length() > 0)
 				buffer.append(";"); //$NON-NLS-1$
-			buffer.append(header.toLowerCase());
+			buffer.append(header.toLowerCase(Locale.ROOT));
 		}
 
 		return buffer.toString();
@@ -259,13 +260,14 @@ private static String canonicalizeHeaderString(
 			return ""; //$NON-NLS-1$
 		}
 
-		List<String> sortedHeaders = new ArrayList<String>();
+		List<String> 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().replaceAll("\\s+", " ") + ":" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			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$
 		}
@@ -303,7 +305,7 @@ private static String canonicalizeQueryString(
 			return ""; //$NON-NLS-1$
 		}
 
-		SortedMap<String, String> sorted = new TreeMap<String, String>();
+		SortedMap<String, String> sorted = new TreeMap<>();
 
 		Iterator<Map.Entry<String, String>> pairs = parameters.entrySet()
 				.iterator();
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.lfs.test/BUCK b/org.eclipse.jgit.lfs.test/BUCK
deleted file mode 100644
index 1298e16..0000000
--- a/org.eclipse.jgit.lfs.test/BUCK
+++ /dev/null
@@ -1,30 +0,0 @@
-TESTS = glob(['tst/**/*.java'])
-
-for t in TESTS:
-  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
-  java_test(
-    name = n,
-    labels = ['lfs'],
-    srcs = [t],
-    deps = [
-      ':helpers',
-      '//org.eclipse.jgit.junit:junit',
-      '//org.eclipse.jgit.lfs:jgit-lfs',
-      '//org.eclipse.jgit:jgit',
-      '//lib:hamcrest-core',
-      '//lib:hamcrest-library',
-      '//lib:junit',
-    ],
-  )
-
-java_library(
-  name = 'helpers',
-  srcs = glob(['src/**/*.java']),
-  deps = [
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.lfs:jgit-lfs',
-    '//org.eclipse.jgit.junit:junit',
-    '//lib:junit',
-  ],
-  visibility = ['PUBLIC']
-)
diff --git a/org.eclipse.jgit.lfs.test/BUILD b/org.eclipse.jgit.lfs.test/BUILD
new file mode 100644
index 0000000..213ba57
--- /dev/null
+++ b/org.eclipse.jgit.lfs.test/BUILD
@@ -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 --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index e859f63..3a01c6e 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -2,19 +2,23 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.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)"
-Export-Package: org.eclipse.jgit.lfs.test;version="4.6.2";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="4.7.8";x-friends:="org.eclipse.jgit.lfs.server.test"
 
diff --git a/org.eclipse.jgit.lfs.test/pom.xml b/org.eclipse.jgit.lfs.test/pom.xml
index a06aefc..814e3ee 100644
--- a/org.eclipse.jgit.lfs.test/pom.xml
+++ b/org.eclipse.jgit.lfs.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java
new file mode 100644
index 0000000..a56f0df
--- /dev/null
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LfsPointerFilterTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015, Dariusz Luksza <dariusz@luksza.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * 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<InMemoryRepository> 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 --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
index 435a2a3..e754d6f 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
@@ -57,6 +57,7 @@
 import java.nio.charset.StandardCharsets;
 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;
@@ -152,7 +153,7 @@ public void test010_toString() {
 	public void test011_toString() {
 		final String x = "0123456789ABCDEFabcdef01234567890123456789ABCDEFabcdef0123456789";
 		final LongObjectId oid = LongObjectId.fromString(x);
-		assertEquals(x.toLowerCase(), oid.name());
+		assertEquals(x.toLowerCase(Locale.ROOT), oid.name());
 	}
 
 	@Test
@@ -290,7 +291,6 @@ public void testCompareTo() {
 				"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
 		assertEquals(0, id1.compareTo(LongObjectId.fromString(
 				"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")));
-		assertEquals(0, id1.compareTo(id1));
 
 		assertEquals(-1, id1.compareTo(LongObjectId.fromString(
 				"1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")));
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
index 808ec3a..ede0f7d 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.lfs/BUCK b/org.eclipse.jgit.lfs/BUCK
deleted file mode 100644
index ddb3a10..0000000
--- a/org.eclipse.jgit.lfs/BUCK
+++ /dev/null
@@ -1,17 +0,0 @@
-SRCS = glob(['src/**'])
-RESOURCES = glob(['resources/**'])
-
-java_library(
-  name = 'jgit-lfs',
-  srcs = SRCS,
-  resources = RESOURCES,
-  deps = [
-    '//org.eclipse.jgit:jgit'
-  ],
-  visibility = ['PUBLIC'],
-)
-
-java_sources(
-  name = 'jgit-lfs_src',
-  srcs = SRCS + RESOURCES,
-)
diff --git a/org.eclipse.jgit.lfs/BUILD b/org.eclipse.jgit.lfs/BUILD
new file mode 100644
index 0000000..c4c9f8a
--- /dev/null
+++ b/org.eclipse.jgit.lfs/BUILD
@@ -0,0 +1,11 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "jgit-lfs",
+    srcs = glob(["src/**"]),
+    resource_strip_prefix = "org.eclipse.jgit.lfs/resources",
+    resources = glob(["resources/**"]),
+    deps = [
+        "//org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index d5d1f7c..0c03da3 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -2,17 +2,20 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="4.6.2",
- org.eclipse.jgit.lfs.errors;version="4.6.2",
- org.eclipse.jgit.lfs.internal;version="4.6.2";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.6.2"
+Export-Package: org.eclipse.jgit.lfs;version="4.7.8",
+ org.eclipse.jgit.lfs.errors;version="4.7.8",
+ org.eclipse.jgit.lfs.internal;version="4.7.8";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.7.8"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.annotations;version="[4.6.2,4.7.0)";resolution:=optional,
- org.eclipse.jgit.attributes;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)"
+Import-Package: org.eclipse.jgit.annotations;version="[4.7.8,4.8.0)";resolution:=optional,
+ org.eclipse.jgit.attributes;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 049b360..3b165fa 100644
--- a/org.eclipse.jgit.lfs/pom.xml
+++ b/org.eclipse.jgit.lfs/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
index 5e52a78..e08e28c 100644
--- a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
+++ b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
@@ -7,3 +7,5 @@
 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}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
index 66feca7..b78ee04 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
@@ -130,6 +130,7 @@ public CleanFilter(Repository db, InputStream in, OutputStream out)
 		this.aOut = new AtomicObjectOutputStream(tmpFile.toAbsolutePath());
 	}
 
+	@Override
 	public int run() throws IOException {
 		try {
 			byte[] buf = new byte[8192];
@@ -151,7 +152,10 @@ public int run() throws IOException {
 						FileUtils.delete(tmpFile.toFile());
 					}
 				} else {
-					FileUtils.mkdirs(mediaFile.getParent().toFile(), true);
+					Path parent = mediaFile.getParent();
+					if (parent != null) {
+						FileUtils.mkdirs(parent.toFile(), true);
+					}
 					FileUtils.rename(tmpFile.toFile(), mediaFile.toFile(),
 							StandardCopyOption.ATOMIC_MOVE);
 				}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
index bbea535..0f62025 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
@@ -51,6 +51,7 @@
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.UnsupportedCharsetException;
+import java.util.Locale;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
@@ -69,11 +70,17 @@ public class LfsPointer {
 	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$
+
+	/**
 	 * 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().replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$
+			.toLowerCase(Locale.ROOT).replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$
 
 	private AnyLongObjectId oid;
 
@@ -150,14 +157,13 @@ public static LfsPointer parseLfsPointer(InputStream in)
 				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) ||
+								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());
-				} else {
-					return null;
 				}
 			}
 			if (versionLine && id != null && sz > -1) {
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java
new file mode 100644
index 0000000..62b0cde
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@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.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;
+
+	/**
+	 * @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 --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
index c76df39..4459588 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
@@ -67,4 +67,6 @@ public static LfsText get() {
 	/***/ public String repositoryNotFound;
 	/***/ public String repositoryReadOnly;
 	/***/ public String lfsUnavailable;
+	/***/ public String lfsUnathorized;
+	/***/ public String lfsFailedToGetRepository;
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
index 1f0df88..caf034d 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
@@ -171,6 +171,7 @@ public final int getByte(int index) {
 	 * @return &lt; 0 if this id comes before other; 0 if this id is equal to
 	 *         other; &gt; 0 if this id comes after other.
 	 */
+	@Override
 	public final int compareTo(final AnyLongObjectId other) {
 		if (this == other)
 			return 0;
@@ -262,6 +263,7 @@ public boolean startsWith(final AbbreviatedLongObjectId abbr) {
 		return abbr.prefixCompare(this) == 0;
 	}
 
+	@Override
 	public final int hashCode() {
 		return (int) (w1 >> 32);
 	}
@@ -277,6 +279,7 @@ public final boolean equals(final AnyLongObjectId other) {
 		return other != null ? equals(this, other) : false;
 	}
 
+	@Override
 	public final boolean equals(final Object o) {
 		if (o instanceof AnyLongObjectId)
 			return equals((AnyLongObjectId) o);
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
index a88057a..0220743 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
@@ -87,6 +87,27 @@ public final class Constants {
 			* 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";
+
+	/**
 	 * Create a new digest function for objects.
 	 *
 	 * @return a new digest object.
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java
new file mode 100644
index 0000000..6f672b8
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/LfsPointerFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015, 2017, Dariusz Luksza <dariusz@luksza.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * 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;
+
+	/**
+	 * @return {@link LfsPointer} or {@code null}
+	 */
+	public LfsPointer getPointer() {
+		return pointer;
+	}
+
+	@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;
+		}
+	}
+
+	@Override
+	public boolean shouldBeRecursive() {
+		return false;
+	}
+
+	@Override
+	public TreeFilter clone() {
+		return new LfsPointerFilter();
+	}
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index 85ac88b..0e460d0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index 6f75186..ca0fdb0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index e272c0b..3df9982 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.http.apache"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 679f880..0165123 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index fe7e3ac..fd1168e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.junit"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index 0b0a335..d071ebd 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index 4aefd72..a106abf 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.lfs"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index 893c492..7592a06 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index 2c91754..d592b00 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -31,8 +31,8 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="4.6.2" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="4.6.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="4.7.8" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="4.7.8" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index b6c5846..2009d11 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
index 444a872..f5a4d32 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm.source"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
index a35034a..d205eae 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index d611d29..42a9d72 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index 20ab58c..fca28be 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="4.6.2.qualifier"
+      version="4.7.8.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index e11e042..1d989c7 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
index cd39231..9eaa829 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
@@ -2,4 +2,4 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: JGit Target Platform Bundle
 Bundle-SymbolicName: org.eclipse.jgit.target
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
index d0da0c4..be02826 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.5" sequenceNumber="1502746491">
+<target name="jgit-4.5" sequenceNumber="1502747250">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
index 065284d..bcef50b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.6" sequenceNumber="1502746433">
+<target name="jgit-4.6" sequenceNumber="1502747233">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
index d3a1131..52ea6f8 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.7" sequenceNumber="1502746477">
+<target name="jgit-4.7" sequenceNumber="1502747215">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index 652b122..3436f19 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
@@ -49,7 +49,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd
new file mode 100644
index 0000000..d5621a0
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd
@@ -0,0 +1,20 @@
+target "jetty-9.4.3" with source configurePhase
+
+location jetty-9.4.3 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.9.v20160517/" {
+	org.eclipse.jetty.client [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.client.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.continuation [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.continuation.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.http [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.http.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.io [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.io.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.security [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.security.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.server [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.server.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.servlet [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.servlet.source [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.util [9.3.9.v20160517,9.3.9.v20160517]
+	org.eclipse.jetty.util.source [9.3.9.v20160517,9.3.9.v20160517]
+}
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 583b128..fc38e76 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -53,7 +53,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>4.6.2-SNAPSHOT</version>
+  <version>4.7.8-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.pgm.test/BUCK b/org.eclipse.jgit.pgm.test/BUCK
deleted file mode 100644
index cd15510..0000000
--- a/org.eclipse.jgit.pgm.test/BUCK
+++ /dev/null
@@ -1,37 +0,0 @@
-TESTS = glob(['tst/**/*.java'])
-
-for t in TESTS:
-  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
-  java_test(
-    name = n,
-    labels = ['pgm'],
-    srcs = [t],
-    deps = [
-      ':helpers',
-      '//org.eclipse.jgit:jgit',
-      '//org.eclipse.jgit.archive:jgit-archive',
-      '//org.eclipse.jgit.junit:junit',
-      '//org.eclipse.jgit.pgm:pgm',
-      '//lib:hamcrest-core',
-      '//lib:hamcrest-library',
-      '//lib:javaewah',
-      '//lib:junit',
-      '//lib:slf4j-api',
-      '//lib:slf4j-simple',
-      '//lib:commons-compress',
-      '//lib:tukaani-xz',
-    ],
-    vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'],
-  )
-
-java_library(
-  name = 'helpers',
-  srcs = glob(['src/**/*.java']),
-  deps = [
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.pgm:pgm',
-    '//org.eclipse.jgit.junit:junit',
-    '//lib:args4j',
-    '//lib:junit',
-  ],
-)
diff --git a/org.eclipse.jgit.pgm.test/BUILD b/org.eclipse.jgit.pgm.test/BUILD
new file mode 100644
index 0000000..5d4a175
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/BUILD
@@ -0,0 +1,40 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+    "junit_tests",
+)
+
+junit_tests(
+    name = "pgm",
+    srcs = glob(["tst/**/*.java"]),
+    jvm_flags = [
+        "-Xmx256m",
+        "-Dfile.encoding=UTF-8",
+    ],
+    tags = ["pgm"],
+    deps = [
+        ":helpers",
+        "//lib:commons-compress",
+        "//lib:javaewah",
+        "//lib:junit",
+        "//lib:slf4j-api",
+        "//lib:slf4j-simple",
+        "//lib:xz",
+        "//org.eclipse.jgit.archive:jgit-archive",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit:junit",
+        "//org.eclipse.jgit.pgm:pgm",
+    ],
+)
+
+java_library(
+    name = "helpers",
+    testonly = 1,
+    srcs = glob(["src/**/*.java"]),
+    deps = [
+        "//lib:args4j",
+        "//lib:junit",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit:junit",
+        "//org.eclipse.jgit.pgm:pgm",
+    ],
+)
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 8e7d5ab..055a806 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -2,28 +2,28 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.api.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.diff;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.dircache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="4.6.2",
- org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.merge;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.pgm;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.pgm.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.pgm.opt;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util.io;version="[4.6.2,4.7.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.api.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.diff;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.dircache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="4.7.8",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.merge;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.pgm;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.pgm.opt;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.io;version="[4.7.8,4.8.0)",
  org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.junit;version="[4.11.0,5.0.0)",
  org.junit.rules;version="[4.11.0,5.0.0)",
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index c8188cb..2c98530 100644
--- a/org.eclipse.jgit.pgm.test/pom.xml
+++ b/org.eclipse.jgit.pgm.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
index b675d3c..0eeabab 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
@@ -79,7 +79,7 @@ public void setUp() throws Exception {
 	 * @throws Exception
 	 */
 	protected String[] executeUnchecked(String... cmds) throws Exception {
-		List<String> result = new ArrayList<String>(cmds.length);
+		List<String> result = new ArrayList<>(cmds.length);
 		for (String cmd : cmds) {
 			result.addAll(CLIGitCommand.executeUnchecked(cmd, db));
 		}
@@ -97,7 +97,7 @@ public void setUp() throws Exception {
 	 * @throws Exception
 	 */
 	protected String[] execute(String... cmds) throws Exception {
-		List<String> result = new ArrayList<String>(cmds.length);
+		List<String> result = new ArrayList<>(cmds.length);
 		for (String cmd : cmds) {
 			Result r = CLIGitCommand.executeRaw(cmd, db);
 			if (r.ex instanceof TerminatedByHelpException) {
@@ -127,6 +127,7 @@ protected File writeTrashFile(final String name, final String data)
 		return JGitTestUtil.writeTrashFile(db, name, data);
 	}
 
+	@Override
 	protected String read(final File file) throws IOException {
 		return JGitTestUtil.read(file);
 	}
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
index b08bc8a..69eb198 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
@@ -156,6 +156,7 @@ PrintWriter createErrorWriter() {
 		return new PrintWriter(result.err);
 	}
 
+	@Override
 	void init(final TextBuiltin cmd) throws IOException {
 		cmd.outs = result.out;
 		cmd.errs = result.err;
@@ -188,7 +189,7 @@ void exit(int status, Exception t) throws Exception {
 	 * @return the array
 	 */
 	static String[] split(String commandLine) {
-		final List<String> list = new ArrayList<String>();
+		final List<String> list = new ArrayList<>();
 		boolean inquote = false;
 		boolean inDblQuote = false;
 		StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
index 35467c6..6f32bfa 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
@@ -348,7 +348,7 @@ public void testArchivePrefixOption() throws Exception {
 		commitBazAndFooSlashBar();
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --prefix=x/ --format=zip master", db).outBytes();
-		String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
+		String[] expect = { "x/", "x/baz", "x/foo/", "x/foo/bar" };
 		String[] actual = listZipEntries(result);
 
 		Arrays.sort(expect);
@@ -361,7 +361,7 @@ public void testTarPrefixOption() throws Exception {
 		commitBazAndFooSlashBar();
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --prefix=x/ --format=tar master", db).outBytes();
-		String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
+		String[] expect = { "x/", "x/baz", "x/foo/", "x/foo/bar" };
 		String[] actual = listTarEntries(result);
 
 		Arrays.sort(expect);
@@ -380,7 +380,7 @@ public void testPrefixDoesNotNormalizeDoubleSlash() throws Exception {
 		commitFoo();
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --prefix=x// --format=zip master", db).outBytes();
-		String[] expect = { "x//foo" };
+		String[] expect = { "x/", "x//foo" };
 		assertArrayEquals(expect, listZipEntries(result));
 	}
 
@@ -389,7 +389,7 @@ public void testPrefixDoesNotNormalizeDoubleSlashInTar() throws Exception {
 		commitFoo();
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --prefix=x// --format=tar master", db).outBytes();
-		String[] expect = { "x//foo" };
+		String[] expect = { "x/", "x//foo" };
 		assertArrayEquals(expect, listTarEntries(result));
 	}
 
@@ -529,7 +529,7 @@ public void testTarPreservesMode() throws Exception {
 	@Test
 	public void testArchiveWithLongFilename() throws Exception {
 		StringBuilder filename = new StringBuilder();
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		for (int i = 0; i < 20; i++) {
 			filename.append("1234567890/");
 			l.add(filename.toString());
@@ -549,7 +549,7 @@ public void testArchiveWithLongFilename() throws Exception {
 	@Test
 	public void testTarWithLongFilename() throws Exception {
 		StringBuilder filename = new StringBuilder();
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		for (int i = 0; i < 20; i++) {
 			filename.append("1234567890/");
 			l.add(filename.toString());
@@ -691,7 +691,7 @@ private void writeRaw(String filename, byte[] data)
 	}
 
 	private static String[] listZipEntries(byte[] zipData) throws IOException {
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		ZipInputStream in = new ZipInputStream(
 				new ByteArrayInputStream(zipData));
 
@@ -706,6 +706,7 @@ private static Future<Object> writeAsync(final OutputStream stream, final byte[]
 		ExecutorService executor = Executors.newSingleThreadExecutor();
 
 		return executor.submit(new Callable<Object>() {
+			@Override
 			public Object call() throws IOException {
 				try {
 					stream.write(data);
@@ -718,7 +719,7 @@ public Object call() throws IOException {
 	}
 
 	private String[] listTarEntries(byte[] tarData) throws Exception {
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		Process proc = spawnAssumingCommandPresent("tar", "tf", "-");
 		BufferedReader reader = readFromProcess(proc);
 		OutputStream out = proc.getOutputStream();
@@ -749,7 +750,7 @@ public Object call() throws IOException {
 				continue;
 
 			// found!
-			List<String> l = new ArrayList<String>();
+			List<String> l = new ArrayList<>();
 			BufferedReader reader = new BufferedReader(
 					new InputStreamReader(in, "UTF-8"));
 			String line;
@@ -764,7 +765,7 @@ public Object call() throws IOException {
 
 	private String[] tarEntryContent(byte[] tarData, String path)
 			throws Exception {
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		Process proc = spawnAssumingCommandPresent("tar", "Oxf", "-", path);
 		BufferedReader reader = readFromProcess(proc);
 		OutputStream out = proc.getOutputStream();
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
index 23aa97e..0ce6451 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
@@ -73,7 +73,8 @@ public void testListConfig() throws Exception {
 				.equals("Mac OS X");
 
 		String[] output = execute("git config --list");
-		List<String> expect = new ArrayList<String>();
+		List<String> expect = new ArrayList<>();
+		expect.add("gc.autoDetach=false");
 		expect.add("core.filemode=" + !isWindows);
 		expect.add("core.logallrefupdates=true");
 		if (isMac)
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
index 4f1759f..06ddbab 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK
deleted file mode 100644
index 5d5e6f7..0000000
--- a/org.eclipse.jgit.pgm/BUCK
+++ /dev/null
@@ -1,81 +0,0 @@
-include_defs('//tools/git.defs')
-
-java_library(
-  name = 'pgm',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  deps = [
-    ':services',
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.archive:jgit-archive',
-    '//org.eclipse.jgit.http.apache:http-apache',
-    '//org.eclipse.jgit.lfs:jgit-lfs',
-    '//org.eclipse.jgit.lfs.server:jgit-lfs-server',
-    '//org.eclipse.jgit.ui:ui',
-    '//lib:args4j',
-    '//lib:httpcomponents',
-    '//lib:httpcore',
-    '//lib/jetty:http',
-    '//lib/jetty:io',
-    '//lib/jetty:server',
-    '//lib/jetty:servlet',
-    '//lib/jetty:security',
-    '//lib/jetty:util',
-    '//lib:servlet-api'
-  ],
-  visibility = ['PUBLIC'],
-)
-
-prebuilt_jar(
-  name = 'services',
-  binary_jar = ':services__jar',
-)
-
-genrule(
-  name = 'services__jar',
-  cmd = 'cd $SRCDIR ; zip -qr $OUT .',
-  srcs = glob(['META-INF/services/*']),
-  out = 'services.jar',
-)
-
-genrule(
-  name = 'jgit',
-  cmd = ''.join([
-    'mkdir $TMP/META-INF &&',
-    'cp $(location :binary_manifest) $TMP/META-INF/MANIFEST.MF &&',
-    'cp $(location :jgit_jar) $TMP/jgit.jar &&',
-    'cd $TMP && zip $TMP/jgit.jar META-INF/MANIFEST.MF &&',
-    'cat $SRCDIR/jgit.sh $TMP/jgit.jar >$OUT &&',
-    'chmod a+x $OUT',
-  ]),
-  srcs = ['jgit.sh'],
-  out = 'jgit',
-  visibility = ['PUBLIC'],
-)
-
-java_binary(
-  name = 'jgit_jar',
-  deps = [
-    ':pgm',
-    '//lib:slf4j-simple',
-    '//lib:tukaani-xz',
-  ],
-  blacklist = [
-    'META-INF/DEPENDENCIES',
-    'META-INF/maven/.*',
-  ],
-)
-
-genrule(
-  name = 'binary_manifest',
-  cmd = ';'.join(['echo "%s: %s" >>$OUT' % e for e in [
-    ('Manifest-Version', '1.0'),
-    ('Main-Class', 'org.eclipse.jgit.pgm.Main'),
-    ('Bundle-Version', git_version()),
-    ('Implementation-Title', 'JGit Command Line Interface'),
-    ('Implementation-Vendor', 'Eclipse.org - JGit'),
-    ('Implementation-Vendor-URL', 'http://www.eclipse.org/jgit/'),
-    ('Implementation-Vendor-Id', 'org.eclipse.jgit'),
-  ]] + ['echo >>$OUT']),
-  out = 'MANIFEST.MF',
-)
diff --git a/org.eclipse.jgit.pgm/BUILD b/org.eclipse.jgit.pgm/BUILD
new file mode 100644
index 0000000..6d32790
--- /dev/null
+++ b/org.eclipse.jgit.pgm/BUILD
@@ -0,0 +1,38 @@
+java_library(
+    name = "pgm",
+    srcs = glob(["src/**"]),
+    resource_strip_prefix = "org.eclipse.jgit.pgm/resources",
+    resources = glob(["resources/**"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":services",
+        "//lib:args4j",
+        "//lib:httpclient",
+        "//lib:httpcore",
+        "//lib:jetty-http",
+        "//lib:jetty-io",
+        "//lib:jetty-security",
+        "//lib:jetty-server",
+        "//lib:jetty-servlet",
+        "//lib:jetty-util",
+        "//lib:servlet-api",
+        "//org.eclipse.jgit.archive:jgit-archive",
+        "//org.eclipse.jgit.http.apache:http-apache",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.lfs:jgit-lfs",
+        "//org.eclipse.jgit.lfs.server:jgit-lfs-server",
+        "//org.eclipse.jgit.ui:ui",
+    ],
+)
+
+java_import(
+    name = "services",
+    jars = [":services_jar"],
+)
+
+genrule(
+    name = "services_jar",
+    srcs = glob(["META-INF/services/*"]),
+    outs = ["services_jar.jar"],
+    cmd = "r=$$PWD && cd org.eclipse.jgit.pgm && zip -qr $$r/$@ .",
+)
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 5c30da7..aeaef45 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -27,46 +27,46 @@
  org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.api;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.api.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.archive;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.awtui;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.blame;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.diff;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.dircache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.gitrepo;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.ketch;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.server;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs.server.s3;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.merge;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.notes;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revplot;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.pack;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.resolver;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util.io;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.api;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.api.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.archive;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.awtui;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.blame;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.diff;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.dircache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.gitrepo;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.ketch;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.server;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.merge;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.notes;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revplot;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.pack;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.io;version="[4.7.8,4.8.0)",
  org.kohsuke.args4j;version="[2.0.12,2.1.0)",
  org.kohsuke.args4j.spi;version="[2.0.15,2.1.0)"
-Export-Package: org.eclipse.jgit.console;version="4.6.2";
+Export-Package: org.eclipse.jgit.console;version="4.7.8";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="4.6.2";
+ org.eclipse.jgit.pgm;version="4.7.8";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -77,11 +77,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="4.6.2";
+ org.eclipse.jgit.pgm.debug;version="4.7.8";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="4.6.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="4.6.2";
+ org.eclipse.jgit.pgm.internal;version="4.7.8";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="4.7.8";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.kohsuke.args4j.spi,
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 443ccc8..ff3f3ba 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.pgm - Sources
 Bundle-SymbolicName: org.eclipse.jgit.pgm.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.6.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.6.2.qualifier";roots="."
+Bundle-Version: 4.7.8.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.7.8.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index c6f4aca..0ddbf29 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 1d4bf76..06e4d94 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -246,6 +246,8 @@
 usage_MakeCacheTree=Show the current cache tree structure
 usage_MergeBase=Find as good common ancestors as possible for a merge
 usage_MergesTwoDevelopmentHistories=Merges two development histories
+usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking
+usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files
 usage_ReadDirCache= Read the DirCache 100 times
 usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
 usage_RebuildRefTree=Copy references into a RefTree
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
index c36c485..3c13590 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
@@ -58,7 +58,7 @@ class Add extends TextBuiltin {
 	private boolean update = false;
 
 	@Argument(required = true, metaVar = "metaVar_filepattern", usage = "usage_filesToAddContentFrom")
-	private List<String> filepatterns = new ArrayList<String>();
+	private List<String> filepatterns = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
index 0f54171..4193254 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
@@ -114,7 +114,7 @@ void ignoreAllSpace(@SuppressWarnings("unused") boolean on) {
 	private String rangeString;
 
 	@Option(name = "--reverse", metaVar = "metaVar_blameReverse", usage = "usage_blameReverse")
-	private List<RevCommit> reverseRange = new ArrayList<RevCommit>(2);
+	private List<RevCommit> reverseRange = new ArrayList<>(2);
 
 	@Argument(index = 0, required = false, metaVar = "metaVar_revision")
 	private String revision;
@@ -124,7 +124,7 @@ void ignoreAllSpace(@SuppressWarnings("unused") boolean on) {
 
 	private ObjectReader reader;
 
-	private final Map<RevCommit, String> abbreviatedCommits = new HashMap<RevCommit, String>();
+	private final Map<RevCommit, String> abbreviatedCommits = new HashMap<>();
 
 	private SimpleDateFormat dateFmt;
 
@@ -163,7 +163,7 @@ protected void run() throws Exception {
 
 			if (!reverseRange.isEmpty()) {
 				RevCommit rangeStart = null;
-				List<RevCommit> rangeEnd = new ArrayList<RevCommit>(2);
+				List<RevCommit> rangeEnd = new ArrayList<>(2);
 				for (RevCommit c : reverseRange) {
 					if (c.has(RevFlag.UNINTERESTING))
 						rangeStart = c;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
index 5f3740c..f6e3810 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
@@ -149,7 +149,7 @@ public void moveRename(List<String> currentAndNew) {
 	@Argument(metaVar = "metaVar_name")
 	private String branch;
 
-	private final Map<String, Ref> printRefs = new LinkedHashMap<String, Ref>();
+	private final Map<String, Ref> printRefs = new LinkedHashMap<>();
 
 	/** Only set for verbose branch listing at-the-moment */
 	private RevWalk rw;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index 0c3b720..c2f3c46 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -78,7 +78,7 @@ class Checkout extends TextBuiltin {
 	private String name;
 
 	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
-	private List<String> paths = new ArrayList<String>();
+	private List<String> paths = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
index 2673cc8..cf4c6e3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
@@ -100,7 +100,7 @@ public static CommandRef get(final String name) {
 	 * @return all common commands, sorted by command name.
 	 */
 	public static CommandRef[] common() {
-		final ArrayList<CommandRef> common = new ArrayList<CommandRef>();
+		final ArrayList<CommandRef> common = new ArrayList<>();
 		for (final CommandRef c : INSTANCE.commands.values())
 			if (c.isCommon())
 				common.add(c);
@@ -110,6 +110,7 @@ public static CommandRef get(final String name) {
 	private static CommandRef[] toSortedArray(final Collection<CommandRef> c) {
 		final CommandRef[] r = c.toArray(new CommandRef[c.size()]);
 		Arrays.sort(r, new Comparator<CommandRef>() {
+			@Override
 			public int compare(final CommandRef o1, final CommandRef o2) {
 				return o1.getName().compareTo(o2.getName());
 			}
@@ -123,7 +124,7 @@ public int compare(final CommandRef o1, final CommandRef o2) {
 
 	private CommandCatalog() {
 		ldr = Thread.currentThread().getContextClassLoader();
-		commands = new HashMap<String, CommandRef>();
+		commands = new HashMap<>();
 
 		final Enumeration<URL> catalogs = catalogs();
 		while (catalogs.hasMoreElements())
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
index 2cfbd86..befc4ec 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
@@ -75,7 +75,7 @@ class Commit extends TextBuiltin {
 	private boolean amend;
 
 	@Argument(metaVar = "metaVar_commitPaths", usage = "usage_CommitPaths")
-	private List<String> paths = new ArrayList<String>();
+	private List<String> paths = new ArrayList<>();
 
 	@Override
 	protected void run() throws NoHeadException, NoMessageException,
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index a7bdde9..1008593 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -87,16 +87,16 @@ class Daemon extends TextBuiltin {
 	int timeout = -1;
 
 	@Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories", multiValued = true)
-	final List<String> enable = new ArrayList<String>();
+	final List<String> enable = new ArrayList<>();
 
 	@Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories", multiValued = true)
-	final List<String> disable = new ArrayList<String>();
+	final List<String> disable = new ArrayList<>();
 
 	@Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true)
-	final List<String> canOverride = new ArrayList<String>();
+	final List<String> canOverride = new ArrayList<>();
 
 	@Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true)
-	final List<String> forbidOverride = new ArrayList<String>();
+	final List<String> forbidOverride = new ArrayList<>();
 
 	@Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk")
 	boolean exportAll;
@@ -109,7 +109,7 @@ enum KetchServerType {
 	}
 
 	@Argument(required = true, metaVar = "metaVar_directory", usage = "usage_directoriesToExport")
-	final List<File> directory = new ArrayList<File>();
+	final List<File> directory = new ArrayList<>();
 
 	@Override
 	protected boolean requiresRepository() {
@@ -139,7 +139,7 @@ protected void run() throws Exception {
 		if (1 < threads)
 			packConfig.setExecutor(Executors.newFixedThreadPool(threads));
 
-		final FileResolver<DaemonClient> resolver = new FileResolver<DaemonClient>();
+		final FileResolver<DaemonClient> resolver = new FileResolver<>();
 		for (final File f : directory) {
 			outw.println(MessageFormat.format(CLIText.get().exporting, f.getAbsolutePath()));
 			resolver.exportDirectory(f);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
index 95c2132..56b6241 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
@@ -67,7 +67,7 @@ void tree_0(final AbstractTreeIterator c) {
 	}
 
 	@Argument(index = 1, metaVar = "metaVar_treeish", required = true)
-	private final List<AbstractTreeIterator> trees = new ArrayList<AbstractTreeIterator>();
+	private final List<AbstractTreeIterator> trees = new ArrayList<>();
 
 	@Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class)
 	private TreeFilter pathFilter = TreeFilter.ALL;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Gc.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Gc.java
index bf45476..7289abb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Gc.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Gc.java
@@ -52,10 +52,18 @@ class Gc extends TextBuiltin {
 	@Option(name = "--aggressive", usage = "usage_Aggressive")
 	private boolean aggressive;
 
+	@Option(name = "--preserve-oldpacks", usage = "usage_PreserveOldPacks")
+	private boolean preserveOldPacks;
+
+	@Option(name = "--prune-preserved", usage = "usage_PrunePreserved")
+	private boolean prunePreserved;
+
 	@Override
 	protected void run() throws Exception {
 		Git git = Git.wrap(db);
 		git.gc().setAggressive(aggressive)
+				.setPreserveOldPacks(preserveOldPacks)
+				.setPrunePreserved(prunePreserved)
 				.setProgressMonitor(new TextProgressMonitor(errw)).call();
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
index f07c3ca..7b71575 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
@@ -88,6 +88,7 @@ public void windowClosing(final WindowEvent e) {
 		final JButton repaint = new JButton();
 		repaint.setText(CLIText.get().repaint);
 		repaint.addActionListener(new ActionListener() {
+			@Override
 			public void actionPerformed(ActionEvent e) {
 				graphPane.repaint();
 			}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index 62e7728..1108ddd 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -53,6 +53,7 @@
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -93,7 +94,7 @@ class Log extends RevWalkTextBuiltin {
 	@Option(name = "--no-standard-notes", usage = "usage_noShowStandardNotes")
 	private boolean noStandardNotes;
 
-	private List<String> additionalNoteRefs = new ArrayList<String>();
+	private List<String> additionalNoteRefs = new ArrayList<>();
 
 	@Option(name = "--show-notes", usage = "usage_showNotes", metaVar = "metaVar_ref")
 	void addAdditionalNoteRef(String notesRef) {
@@ -102,8 +103,8 @@ void addAdditionalNoteRef(String notesRef) {
 
 	@Option(name = "--date", usage = "usage_date")
 	void dateFormat(String date) {
-		if (date.toLowerCase().equals(date))
-			date = date.toUpperCase();
+		if (date.toLowerCase(Locale.ROOT).equals(date))
+			date = date.toUpperCase(Locale.ROOT);
 		dateFormatter = new GitDateFormatter(Format.valueOf(date));
 	}
 
@@ -203,7 +204,7 @@ protected void run() throws Exception {
 
 			if (!noStandardNotes || !additionalNoteRefs.isEmpty()) {
 				createWalk();
-				noteMaps = new LinkedHashMap<String, NoteMap>();
+				noteMaps = new LinkedHashMap<>();
 				if (!noStandardNotes) {
 					addNoteMap(Constants.R_NOTES_COMMITS);
 				}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
index 6262ad2..7a5f3d8 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
@@ -74,8 +74,9 @@ class LsRemote extends TextBuiltin {
 	protected void run() throws Exception {
 		LsRemoteCommand command = Git.lsRemoteRepository().setRemote(remote)
 				.setTimeout(timeout).setHeads(heads).setTags(tags);
-		TreeSet<Ref> refs = new TreeSet<Ref>(new Comparator<Ref>() {
+		TreeSet<Ref> refs = new TreeSet<>(new Comparator<Ref>() {
 
+			@Override
 			public int compare(Ref r1, Ref r2) {
 				return r1.getName().compareTo(r2.getName());
 			}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
index 872ea67..02d61e5 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
@@ -68,7 +68,7 @@ class LsTree extends TextBuiltin {
 
 	@Argument(index = 1)
 	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class)
-	private List<String> paths = new ArrayList<String>();
+	private List<String> paths = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index a0f4d06..3addecb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -53,6 +53,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 import org.eclipse.jgit.awtui.AwtAuthenticator;
 import org.eclipse.jgit.awtui.AwtCredentialsProvider;
@@ -90,7 +91,7 @@ public class Main {
 	private TextBuiltin subcommand;
 
 	@Argument(index = 1, metaVar = "metaVar_arg")
-	private List<String> arguments = new ArrayList<String>();
+	private List<String> arguments = new ArrayList<>();
 
 	PrintWriter writer;
 
@@ -240,7 +241,8 @@ private void execute(final String[] argv) throws Exception {
 		}
 
 		if (version) {
-			String cmdId = Version.class.getSimpleName().toLowerCase();
+			String cmdId = Version.class.getSimpleName()
+					.toLowerCase(Locale.ROOT);
 			subcommand = CommandCatalog.get(cmdId).create();
 		}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
index 9dcd512..f8bae1d 100644
--- 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 @@ void commit_0(final RevCommit c) {
 	}
 
 	@Argument(index = 1, metaVar = "metaVar_commitish", required = true)
-	private final List<RevCommit> commits = new ArrayList<RevCommit>();
+	private final List<RevCommit> commits = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 98af186..1b805d1 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -77,7 +77,7 @@ class Push extends TextBuiltin {
 	private String remote = Constants.DEFAULT_REMOTE_NAME;
 
 	@Argument(index = 1, metaVar = "metaVar_refspec")
-	private final List<RefSpec> refSpecs = new ArrayList<RefSpec>();
+	private final List<RefSpec> refSpecs = new ArrayList<>();
 
 	@Option(name = "--all")
 	private boolean all;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
index 6833ad3..a66b7fa 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
@@ -68,7 +68,7 @@ class RevParse extends TextBuiltin {
 	boolean verify;
 
 	@Argument(index = 0, metaVar = "metaVar_commitish")
-	private final List<ObjectId> commits = new ArrayList<ObjectId>();
+	private final List<ObjectId> commits = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
index 1543586..74135e4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
@@ -124,12 +124,12 @@ void enableBoundary(final boolean on) {
 	private String followPath;
 
 	@Argument(index = 0, metaVar = "metaVar_commitish")
-	private final List<RevCommit> commits = new ArrayList<RevCommit>();
+	private final List<RevCommit> commits = new ArrayList<>();
 
 	@Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class)
 	protected TreeFilter pathFilter = TreeFilter.ALL;
 
-	private final List<RevFilter> revLimiter = new ArrayList<RevFilter>();
+	private final List<RevFilter> revLimiter = new ArrayList<>();
 
 	@Option(name = "--author")
 	void addAuthorRevFilter(final String who) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
index f4f864b..79c3f09 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
@@ -58,7 +58,7 @@ class Rm extends TextBuiltin {
 	@Argument(metaVar = "metaVar_path", usage = "usage_path", multiValued = true, required = true)
 
 	@Option(name = "--", handler = StopOptionHandler.class)
-	private List<String> paths = new ArrayList<String>();
+	private List<String> paths = new ArrayList<>();
 
 
 	@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
index 43b292e..b7f5e58 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
@@ -117,7 +117,7 @@ private void printPorcelainStatus(org.eclipse.jgit.api.Status status)
 		Map<String, StageState> conflicting = status.getConflictingStageState();
 
 		// build a sorted list of all paths except untracked and ignored
-		TreeSet<String> sorted = new TreeSet<String>();
+		TreeSet<String> sorted = new TreeSet<>();
 		sorted.addAll(added);
 		sorted.addAll(changed);
 		sorted.addAll(removed);
@@ -185,7 +185,7 @@ else if (missing.contains(path))
 
 		// untracked are always at the end of the list
 		if ("all".equals(untrackedFilesMode)) { //$NON-NLS-1$
-			TreeSet<String> untracked = new TreeSet<String>(
+			TreeSet<String> untracked = new TreeSet<>(
 					status.getUntracked());
 			for (String path : untracked)
 				printPorcelainLine('?', '?', path);
@@ -221,7 +221,7 @@ private void printLongStatus(org.eclipse.jgit.api.Status status)
 		Collection<String> untracked = status.getUntracked();
 		Map<String, StageState> unmergedStates = status
 				.getConflictingStageState();
-		Collection<String> toBeCommitted = new ArrayList<String>(added);
+		Collection<String> toBeCommitted = new ArrayList<>(added);
 		toBeCommitted.addAll(changed);
 		toBeCommitted.addAll(removed);
 		int nbToBeCommitted = toBeCommitted.size();
@@ -232,7 +232,7 @@ private void printLongStatus(org.eclipse.jgit.api.Status status)
 					toBeCommitted, added, changed, removed);
 			firstHeader = false;
 		}
-		Collection<String> notStagedForCommit = new ArrayList<String>(modified);
+		Collection<String> notStagedForCommit = new ArrayList<>(modified);
 		notStagedForCommit.addAll(missing);
 		int nbNotStagedForCommit = notStagedForCommit.size();
 		if (nbNotStagedForCommit > 0) {
@@ -274,7 +274,7 @@ protected void printSectionHeader(String pattern, Object... arguments)
 
 	protected int printList(Collection<String> list) throws IOException {
 		if (!list.isEmpty()) {
-			List<String> sortedList = new ArrayList<String>(list);
+			List<String> sortedList = new ArrayList<>(list);
 			java.util.Collections.sort(sortedList);
 			for (String filename : sortedList) {
 				outw.println(CLIText.formatLine(String.format(
@@ -291,7 +291,7 @@ protected int printList(String status1, String status2, String status3,
 			Collection<String> set2,
 			@SuppressWarnings("unused") Collection<String> set3)
 			throws IOException {
-		List<String> sortedList = new ArrayList<String>(list);
+		List<String> sortedList = new ArrayList<>(list);
 		java.util.Collections.sort(sortedList);
 		for (String filename : sortedList) {
 			String prefix;
@@ -311,7 +311,7 @@ else if (set2.contains(filename))
 
 	private void printUnmerged(Map<String, StageState> unmergedStates)
 			throws IOException {
-		List<String> paths = new ArrayList<String>(unmergedStates.keySet());
+		List<String> paths = new ArrayList<>(unmergedStates.keySet());
 		Collections.sort(paths);
 		for (String path : paths) {
 			StageState state = unmergedStates.get(path);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index 05d094f..44ec3f4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -84,12 +84,14 @@
 class DiffAlgorithms extends TextBuiltin {
 
 	final Algorithm myers = new Algorithm() {
+		@Override
 		DiffAlgorithm create() {
 			return MyersDiff.INSTANCE;
 		}
 	};
 
 	final Algorithm histogram = new Algorithm() {
+		@Override
 		DiffAlgorithm create() {
 			HistogramDiff d = new HistogramDiff();
 			d.setFallbackAlgorithm(null);
@@ -98,6 +100,7 @@ DiffAlgorithm create() {
 	};
 
 	final Algorithm histogram_myers = new Algorithm() {
+		@Override
 		DiffAlgorithm create() {
 			HistogramDiff d = new HistogramDiff();
 			d.setFallbackAlgorithm(MyersDiff.INSTANCE);
@@ -112,13 +115,13 @@ DiffAlgorithm create() {
 	//
 
 	@Option(name = "--algorithm", multiValued = true, metaVar = "NAME", usage = "Enable algorithm(s)")
-	List<String> algorithms = new ArrayList<String>();
+	List<String> 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")
-	List<File> gitDirs = new ArrayList<File>();
+	List<File> gitDirs = new ArrayList<>();
 
 	@Option(name = "--count", metaVar = "LIMIT", usage = "Number of file revisions to be compared")
 	int count = 0; // unlimited
@@ -234,6 +237,7 @@ private void run(Repository repo) throws Exception {
 		}
 
 		Collections.sort(all, new Comparator<Test>() {
+			@Override
 			public int compare(Test a, Test b) {
 				int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
 				if (result == 0) {
@@ -320,7 +324,7 @@ private void testOne(Test test, RawText a, RawText b) {
 	}
 
 	private List<Test> init() {
-		List<Test> all = new ArrayList<Test>();
+		List<Test> all = new ArrayList<>();
 
 		try {
 			for (Field f : DiffAlgorithms.class.getDeclaredFields()) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
index 52b6d19..5839f33 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
@@ -219,6 +219,7 @@ protected boolean requiresRepository() {
 		return false;
 	}
 
+	@Override
 	protected void run() throws Exception {
 		AppServer server = new AppServer(port);
 		URI baseURI = server.getURI();
@@ -254,7 +255,7 @@ protected void run() throws Exception {
 
 			@Override
 			protected LargeFileRepository getLargeFileRepository(
-					LfsRequest request, String path) {
+					LfsRequest request, String path, String auth) {
 				return repository;
 			}
 		};
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 8cfcba9..da602d0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -112,7 +112,7 @@ class RebuildCommitGraph extends TextBuiltin {
 
 	private final ProgressMonitor pm = new TextProgressMonitor(errw);
 
-	private Map<ObjectId, ObjectId> rewrites = new HashMap<ObjectId, ObjectId>();
+	private Map<ObjectId, ObjectId> rewrites = new HashMap<>();
 
 	@Override
 	protected void run() throws Exception {
@@ -137,8 +137,8 @@ protected void run() throws Exception {
 	}
 
 	private void recreateCommitGraph() throws IOException {
-		final Map<ObjectId, ToRewrite> toRewrite = new HashMap<ObjectId, ToRewrite>();
-		List<ToRewrite> queue = new ArrayList<ToRewrite>();
+		final Map<ObjectId, ToRewrite> toRewrite = new HashMap<>();
+		List<ToRewrite> queue = new ArrayList<>();
 		try (RevWalk rw = new RevWalk(db);
 				final BufferedReader br = new BufferedReader(
 						new InputStreamReader(new FileInputStream(graph),
@@ -176,7 +176,7 @@ private void recreateCommitGraph() throws IOException {
 			while (!queue.isEmpty()) {
 				final ListIterator<ToRewrite> itr = queue
 						.listIterator(queue.size());
-				queue = new ArrayList<ToRewrite>();
+				queue = new ArrayList<>();
 				REWRITE: while (itr.hasPrevious()) {
 					final ToRewrite t = itr.previous();
 					final ObjectId[] newParents = new ObjectId[t.oldParents.length];
@@ -278,7 +278,7 @@ protected void writeFile(final String name, final byte[] content)
 	}
 
 	private Map<String, Ref> computeNewRefs() throws IOException {
-		final Map<String, Ref> refs = new HashMap<String, Ref>();
+		final Map<String, Ref> refs = new HashMap<>();
 		try (RevWalk rw = new RevWalk(db);
 				BufferedReader br = new BufferedReader(
 						new InputStreamReader(new FileInputStream(refList),
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java
index aa25807..415c7d3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java
@@ -85,6 +85,7 @@ protected void run() throws Exception {
 	static enum Format {
 		/** */
 		USAGE {
+			@Override
 			void print(ThrowingPrintWriter err, final CommandRef c) throws IOException {
 				String usage = c.getUsage();
 				if (usage != null && usage.length() > 0)
@@ -94,6 +95,7 @@ void print(ThrowingPrintWriter err, final CommandRef c) throws IOException {
 
 		/** */
 		CLASSES {
+			@Override
 			void print(ThrowingPrintWriter err, final CommandRef c) throws IOException {
 				err.print(c.getImplementationClassName());
 			}
@@ -101,6 +103,7 @@ void print(ThrowingPrintWriter err, final CommandRef c) throws IOException {
 
 		/** */
 		URLS {
+			@Override
 			void print(ThrowingPrintWriter err, final CommandRef c) throws IOException {
 				final ClassLoader ldr = c.getImplementationClassLoader();
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index 28d92ae..0eb4e05 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -251,16 +251,16 @@ public int fold(int hash, int bits) {
 	//
 
 	@Option(name = "--hash", multiValued = true, metaVar = "NAME", usage = "Enable hash function(s)")
-	List<String> hashFunctions = new ArrayList<String>();
+	List<String> hashFunctions = new ArrayList<>();
 
 	@Option(name = "--fold", multiValued = true, metaVar = "NAME", usage = "Enable fold function(s)")
-	List<String> foldFunctions = new ArrayList<String>();
+	List<String> 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")
-	List<File> gitDirs = new ArrayList<File>();
+	List<File> gitDirs = new ArrayList<>();
 
 	@Override
 	protected boolean requiresRepository() {
@@ -327,7 +327,7 @@ private void run(Repository repo) throws Exception {
 				RawText txt = new RawText(raw);
 				int[] lines = new int[txt.size()];
 				int cnt = 0;
-				HashSet<Line> u = new HashSet<Line>();
+				HashSet<Line> u = new HashSet<>();
 				for (int i = 0; i < txt.size(); i++) {
 					if (u.add(new Line(txt, i)))
 						lines[cnt++] = i;
@@ -386,8 +386,8 @@ private static void testOne(Function fun, RawText txt, int[] elements,
 	}
 
 	private List<Function> init() {
-		List<Hash> hashes = new ArrayList<Hash>();
-		List<Fold> folds = new ArrayList<Fold>();
+		List<Hash> hashes = new ArrayList<>();
+		List<Fold> folds = new ArrayList<>();
 
 		try {
 			for (Field f : TextHashFunctions.class.getDeclaredFields()) {
@@ -410,7 +410,7 @@ private List<Function> init() {
 			throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
 		}
 
-		List<Function> all = new ArrayList<Function>();
+		List<Function> all = new ArrayList<>();
 		for (Hash cmp : hashes) {
 			if (include(cmp.name, hashFunctions)) {
 				for (Fold f : folds) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index 90c03e9..4842b98 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -68,7 +68,6 @@ public static CLIText get() {
 	 * @param line
 	 *            the line to format
 	 * @return the formatted line
-	 * @since 2.2
 	 */
 	public static String formatLine(String line) {
 		return MessageFormat.format(get().lineFormat, line);
@@ -81,7 +80,6 @@ public static String formatLine(String line) {
 	 * @param message
 	 *            the message to format
 	 * @return the formatted line
-	 * @since 4.2
 	 */
 	public static String fatalError(String message) {
 		return MessageFormat.format(get().fatalError, message);
diff --git 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
index b531ba6..020b625 100644
--- 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
@@ -141,7 +141,7 @@ public CmdLineParser(final Object bean, Repository repo) {
 
 	@Override
 	public void parseArgument(final String... args) throws CmdLineException {
-		final ArrayList<String> tmp = new ArrayList<String>(args.length);
+		final ArrayList<String> tmp = new ArrayList<>(args.length);
 		for (int argi = 0; argi < args.length; argi++) {
 			final String str = args[argi];
 			if (str.equals("--")) { //$NON-NLS-1$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
index e468023..b873c3d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
@@ -81,7 +81,7 @@ public PathTreeFilterHandler(final CmdLineParser parser,
 
 	@Override
 	public int parseArguments(final Parameters params) throws CmdLineException {
-		final List<PathFilter> filters = new ArrayList<PathFilter>();
+		final List<PathFilter> filters = new ArrayList<>();
 		for (int idx = 0;; idx++) {
 			final String path;
 			try {
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
index 10c29d5..64f7498 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.test/BUCK b/org.eclipse.jgit.test/BUCK
deleted file mode 100644
index 5b7be89..0000000
--- a/org.eclipse.jgit.test/BUCK
+++ /dev/null
@@ -1,95 +0,0 @@
-PKG = 'tst/org/eclipse/jgit/'
-HELPERS = glob(['src/**/*.java']) + [PKG + c for c in [
-  'api/AbstractRemoteCommandTest.java',
-  'diff/AbstractDiffTestCase.java',
-  'internal/storage/file/GcTestCase.java',
-  'internal/storage/file/PackIndexTestCase.java',
-  'internal/storage/file/XInputStream.java',
-  'nls/GermanTranslatedBundle.java',
-  'nls/MissingPropertyBundle.java',
-  'nls/NoPropertiesBundle.java',
-  'nls/NonTranslatedBundle.java',
-  'revwalk/RevQueueTestCase.java',
-  'revwalk/RevWalkTestCase.java',
-  'transport/SpiTransport.java',
-  'treewalk/FileTreeIteratorWithTimeControl.java',
-  'treewalk/filter/AlwaysCloneTreeFilter.java',
-  'test/resources/SampleDataRepositoryTestCase.java',
-  'util/CPUTimeStopWatch.java',
-  'util/io/Strings.java',
-]]
-
-DATA = [
-  PKG + 'lib/empty.gitindex.dat',
-  PKG + 'lib/sorttest.gitindex.dat',
-]
-
-TESTS = glob(
-  ['tst/**/*.java'],
-  excludes = HELPERS + DATA,
-)
-
-DEPS = {
-  PKG + 'nls/RootLocaleTest.java': [
-    '//org.eclipse.jgit.pgm:pgm',
-    '//org.eclipse.jgit.ui:ui',
-  ],
-}
-
-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')
-
-  java_test(
-    name = name,
-    labels = labels,
-    srcs = [src],
-    deps = [
-      ':helpers',
-      ':tst_rsrc',
-      '//org.eclipse.jgit:jgit',
-      '//org.eclipse.jgit.junit:junit',
-      '//org.eclipse.jgit.lfs:jgit-lfs',
-      '//lib:hamcrest-core',
-      '//lib:hamcrest-library',
-      '//lib:javaewah',
-      '//lib:junit',
-      '//lib:slf4j-api',
-      '//lib:slf4j-simple',
-    ] + DEPS.get(src, []),
-    vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'],
-  )
-
-java_library(
-  name = 'helpers',
-  srcs = HELPERS,
-  resources = DATA,
-  deps = [
-    '//org.eclipse.jgit:jgit',
-    '//org.eclipse.jgit.junit:junit',
-    '//lib:junit',
-  ],
-)
-
-prebuilt_jar(
-  name = 'tst_rsrc',
-  binary_jar = ':tst_rsrc_jar',
-)
-
-genrule(
-  name = 'tst_rsrc_jar',
-  cmd = 'cd $SRCDIR/tst-rsrc ; zip -qr $OUT .',
-  srcs = glob(['tst-rsrc/**']),
-  out = 'tst_rsrc.jar',
-)
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
new file mode 100644
index 0000000..bbda838
--- /dev/null
+++ b/org.eclipse.jgit.test/BUILD
@@ -0,0 +1,61 @@
+load(":tests.bzl", "tests")
+load(
+    "@com_googlesource_gerrit_bazlets//tools:genrule2.bzl",
+    "genrule2",
+)
+
+PKG = "tst/org/eclipse/jgit/"
+
+HELPERS = glob(["src/**/*.java"]) + [PKG + c for c in [
+    "api/AbstractRemoteCommandTest.java",
+    "diff/AbstractDiffTestCase.java",
+    "internal/storage/file/GcTestCase.java",
+    "internal/storage/file/PackIndexTestCase.java",
+    "internal/storage/file/XInputStream.java",
+    "nls/GermanTranslatedBundle.java",
+    "nls/MissingPropertyBundle.java",
+    "nls/NoPropertiesBundle.java",
+    "nls/NonTranslatedBundle.java",
+    "revwalk/RevQueueTestCase.java",
+    "revwalk/RevWalkTestCase.java",
+    "transport/SpiTransport.java",
+    "treewalk/FileTreeIteratorWithTimeControl.java",
+    "treewalk/filter/AlwaysCloneTreeFilter.java",
+    "test/resources/SampleDataRepositoryTestCase.java",
+    "util/CPUTimeStopWatch.java",
+    "util/io/Strings.java",
+]]
+
+DATA = [
+    PKG + "lib/empty.gitindex.dat",
+    PKG + "lib/sorttest.gitindex.dat",
+]
+
+tests(glob(
+    ["tst/**/*.java"],
+    exclude = HELPERS + DATA,
+))
+
+java_library(
+    name = "helpers",
+    testonly = 1,
+    srcs = HELPERS,
+    resources = DATA,
+    deps = [
+        "//lib:junit",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit:junit",
+    ],
+)
+
+java_import(
+    name = "tst_rsrc",
+    jars = [":tst_rsrc_jar"],
+)
+
+genrule2(
+    name = "tst_rsrc_jar",
+    srcs = glob(["tst-rsrc/**"]),
+    outs = ["tst_rsrc.jar"],
+    cmd = "o=$$PWD/$@ && tar cf - $(SRCS) | tar -C $$TMP --strip-components=2 -xf - && cd  $$TMP && zip -qr $$o .",
+)
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index f48cae0..9a4b3e1 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -2,53 +2,54 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
- org.eclipse.jgit.api;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.api.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.attributes;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.awtui;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.blame;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.diff;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.dircache;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.events;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.fnmatch;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.gitrepo;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.hooks;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.ignore;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.ignore.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.junit;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lfs;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.merge;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.notes;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.patch;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.pgm;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.pgm.internal;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revplot;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.file;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.storage.pack;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.submodule;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.http;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport.resolver;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util.io;version="[4.6.2,4.7.0)",
+ org.eclipse.jgit.api;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.api.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.attributes;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.awtui;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.blame;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.diff;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.dircache;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.events;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.fnmatch;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.gitrepo;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.hooks;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.ignore;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.ignore.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.junit;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lfs;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.merge;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.notes;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.patch;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.pgm;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revplot;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.file;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.storage.pack;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.submodule;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.http;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.io;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util.sha1;version="[4.7.8,4.8.0)",
  org.junit;version="[4.4.0,5.0.0)",
  org.junit.experimental.theories;version="[4.4.0,5.0.0)",
  org.junit.rules;version="[4.11.0,5.0.0)",
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
index 76930f2..7c0ea44 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
@@ -89,7 +89,7 @@ static class PatchReader extends CommitReader {
 			super(new String[] { "-p" });
 			stats = s;
 
-			offBy1 = new HashSet<String>();
+			offBy1 = new HashSet<>();
 			offBy1.add("9bda5ece6806cd797416eaa47c7b927cc6e9c3b2");
 		}
 
@@ -158,7 +158,7 @@ private static void dump(final byte[] buf) {
 	}
 
 	static class NumStatReader extends CommitReader {
-		final HashMap<String, HashMap<String, StatInfo>> stats = new HashMap<String, HashMap<String, StatInfo>>();
+		final HashMap<String, HashMap<String, StatInfo>> stats = new HashMap<>();
 
 		NumStatReader() throws IOException {
 			super(new String[] { "--numstat" });
@@ -166,7 +166,7 @@ static class NumStatReader extends CommitReader {
 
 		@Override
 		void onCommit(String commitId, byte[] buf) {
-			final HashMap<String, StatInfo> files = new HashMap<String, StatInfo>();
+			final HashMap<String, StatInfo> files = new HashMap<>();
 			final MutableInteger ptr = new MutableInteger();
 			while (ptr.value < buf.length) {
 				if (buf[ptr.value] == '\n')
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 1aff289..59b1207 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
@@ -155,6 +155,10 @@
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
           <argLine>-Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <includes>
+            <include>**/*Test.java</include>
+            <include>**/*Tests.java</include>
+          </includes>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
index 4454e1a..5a01eae 100644
--- a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
@@ -49,7 +49,7 @@
 public class Sets {
 	@SafeVarargs
 	public static <T> Set<T> of(T... elements) {
-		Set<T> ret = new HashSet<T>();
+		Set<T> ret = new HashSet<>();
 		for (T element : elements)
 			ret.add(element);
 		return ret;
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
new file mode 100644
index 0000000..8ae0065
--- /dev/null
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -0,0 +1,48 @@
+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",
+            ]
+
+        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 --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-1.pdf b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-1.pdf
new file mode 100644
index 0000000..ba9aaa1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-1.pdf
Binary files differ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-2.pdf b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-2.pdf
new file mode 100644
index 0000000..b621eec
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/sha1/shattered-2.pdf
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 5ad73f1..ed3907e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -1074,30 +1074,37 @@ public void testExecutableRetention() throws Exception {
 
 		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) {
 				try {
 					return read(f).startsWith("binary:");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index fc8df42..edab96b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -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 @@ public void setup() {
 		ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format);
 	}
 
+	@Override
 	@After
 	public void tearDown() {
 		ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0));
@@ -190,7 +192,7 @@ public void archiveByDirectoryPath() throws GitAPIException, IOException {
 
 	private class MockFormat implements ArchiveCommand.Format<MockOutputStream> {
 
-		private Map<String, String> entries = new HashMap<String, String>();
+		private Map<String, String> entries = new HashMap<>();
 
 		private int size() {
 			return entries.size();
@@ -203,12 +205,14 @@ private String getByPath(String path) {
 		private final List<String> SUFFIXES = Collections
 				.unmodifiableList(Arrays.asList(".mck"));
 
+		@Override
 		public MockOutputStream createArchiveOutputStream(OutputStream s)
 				throws IOException {
 			return createArchiveOutputStream(s,
 					Collections.<String, Object> emptyMap());
 		}
 
+		@Override
 		public MockOutputStream createArchiveOutputStream(OutputStream s,
 				Map<String, Object> o) throws IOException {
 			for (Map.Entry<String, Object> p : o.entrySet()) {
@@ -224,11 +228,18 @@ public MockOutputStream createArchiveOutputStream(OutputStream s,
 			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<String> suffixes() {
 			return SUFFIXES;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
index 0e1cf02..85436db 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
@@ -64,6 +64,7 @@
 public class CleanCommandTest extends RepositoryTestCase {
 	private Git git;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -143,7 +144,7 @@ public void testCleanWithPaths() throws NoWorkTreeException,
 		assertTrue(files.size() > 0);
 
 		// run clean with setPaths
-		Set<String> paths = new TreeSet<String>();
+		Set<String> paths = new TreeSet<>();
 		paths.add("File3.txt");
 		Set<String> cleanedFiles = git.clean().setPaths(paths).call();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index 8a728ca..ae0b8dd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
@@ -85,9 +85,10 @@ public class CloneCommandTest extends RepositoryTestCase {
 
 	private TestRepository<Repository> tr;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
-		tr = new TestRepository<Repository>(db);
+		tr = new TestRepository<>(db);
 
 		git = new Git(db);
 		// commit something
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 021c245..37fee40 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -93,30 +93,37 @@ public void testExecutableRetention() throws Exception {
 
 		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;
 			}
@@ -138,30 +145,37 @@ public boolean isCaseSensitive() {
 
 		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;
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
index 5f10131..48d3733 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
@@ -623,6 +623,7 @@ private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,
 			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();
 					}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java
new file mode 100644
index 0000000..73a705b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2017 David Pursehouse <david.pursehouse@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.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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
index 56a1f38..a36f6c5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
@@ -45,12 +45,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.Collection;
 
-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 +70,7 @@ public class FetchCommandTest extends RepositoryTestCase {
 	private Git remoteGit;
 
 	@Before
-	public void setupRemoteRepository() throws IOException, URISyntaxException {
+	public void setupRemoteRepository() throws Exception {
 		git = new Git(db);
 
 		// create other repository
@@ -91,8 +87,7 @@ public void setupRemoteRepository() throws IOException, URISyntaxException {
 	}
 
 	@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();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
index 3bff8f2..a4e5574 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
@@ -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;
 
@@ -80,14 +79,6 @@ public void setUp() throws Exception {
 		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);
@@ -141,7 +132,6 @@ public void testOpen() throws IOException, JGitInternalException,
 	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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
index 1310625..38178bf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
@@ -62,7 +62,7 @@ public class LogCommandTest extends RepositoryTestCase {
 
 	@Test
 	public void logAllCommits() throws Exception {
-		List<RevCommit> commits = new ArrayList<RevCommit>();
+		List<RevCommit> commits = new ArrayList<>();
 		Git git = Git.wrap(db);
 
 		writeTrashFile("Test.txt", "Hello world");
@@ -94,7 +94,7 @@ public void logAllCommits() throws Exception {
 
     @Test
     public void logAllCommitsWithTag() throws Exception {
-		List<RevCommit> commits = new ArrayList<RevCommit>();
+		List<RevCommit> commits = new ArrayList<>();
 		Git git = Git.wrap(db);
 
 		writeTrashFile("Test.txt", "Hello world");
@@ -123,7 +123,7 @@ public void logAllCommitsWithTag() throws Exception {
 	}
 
 	private List<RevCommit> createCommits(Git git) throws Exception {
-		List<RevCommit> commits = new ArrayList<RevCommit>();
+		List<RevCommit> commits = new ArrayList<>();
 		writeTrashFile("Test.txt", "Hello world");
 		git.add().addFilepattern("Test.txt").call();
 		commits.add(git.commit().setMessage("commit#1").call());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
index a853d6a..c9f77ea 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
@@ -58,6 +58,7 @@ public class LsRemoteCommandTest extends RepositoryTestCase {
 
 	private Git git;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
 		git = new Git(db);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index cb3dbf1..4b23349 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -1556,7 +1556,7 @@ public void testFastForwardOnlyNotPossible() throws Exception {
 
 	@Test
 	public void testRecursiveMergeWithConflict() throws Exception {
-		TestRepository<Repository> db_t = new TestRepository<Repository>(db);
+		TestRepository<Repository> 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();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
index bd62200..138b7af 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
@@ -64,7 +64,7 @@ public class NameRevCommandTest extends RepositoryTestCase {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		tr = new TestRepository<Repository>(db);
+		tr = new TestRepository<>(db);
 		git = new Git(db);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index a526fda..823516b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -346,6 +346,7 @@ private enum TestPullMode {
 	/** global rebase config should be respected */
 	public void testPullWithRebasePreserve1Config() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "preserve");
@@ -360,6 +361,7 @@ public PullResult call() throws Exception {
 	/** the branch-local config should win over the global config */
 	public void testPullWithRebasePreserveConfig2() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "false");
@@ -375,6 +377,7 @@ public PullResult call() throws Exception {
 	/** the branch-local config should be respected */
 	public void testPullWithRebasePreserveConfig3() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("branch", "master", "rebase", "preserve");
@@ -389,6 +392,7 @@ public PullResult call() throws Exception {
 	/** global rebase config should be respected */
 	public void testPullWithRebaseConfig1() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "true");
@@ -403,6 +407,7 @@ public PullResult call() throws Exception {
 	/** the branch-local config should win over the global config */
 	public void testPullWithRebaseConfig2() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "preserve");
@@ -418,6 +423,7 @@ public PullResult call() throws Exception {
 	/** the branch-local config should be respected */
 	public void testPullWithRebaseConfig3() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("branch", "master", "rebase", "true");
@@ -432,6 +438,7 @@ public PullResult call() throws Exception {
 	/** without config it should merge */
 	public void testPullWithoutConfig() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				return target.pull().call();
 			}
@@ -443,6 +450,7 @@ public PullResult call() throws Exception {
 	/** the branch local config should win over the global config */
 	public void testPullWithMergeConfig() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "true");
@@ -458,6 +466,7 @@ public PullResult call() throws Exception {
 	/** the branch local config should win over the global config */
 	public void testPullWithMergeConfig2() throws Exception {
 		Callable<PullResult> setup = new Callable<PullResult>() {
+			@Override
 			public PullResult call() throws Exception {
 				StoredConfig config = dbTarget.getConfig();
 				config.setString("pull", null, "rebase", "false");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 2a32540..8c613ec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -66,8 +66,10 @@
 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;
@@ -379,4 +381,56 @@ public void testPushAfterGC() throws Exception {
 					db2.resolve(commit3.getId().getName() + "^{commit}"));
 		}
 	}
+
+	@Test
+	public void testPushWithLease() throws JGitInternalException, IOException,
+			GitAPIException, URISyntaxException {
+
+		// 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();
+
+		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<PushResult> 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);
+			}
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 24cb522..0cc0816 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -2269,11 +2269,13 @@ public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps()
 
 		RebaseResult res = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> 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;
@@ -2284,6 +2286,7 @@ public String modifyCommitMessage(String commit) {
 
 		RebaseResult res2 = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							// delete RevCommit c4
@@ -2293,6 +2296,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						fail("modifyCommitMessage() was not expected to be called");
 						return commit;
@@ -2514,6 +2518,7 @@ public void testRebaseInteractiveReword() throws Exception {
 		RebaseResult res = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.REWORD);
@@ -2522,6 +2527,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return "rewritten commit message";
 					}
@@ -2560,6 +2566,7 @@ public void testRebaseInteractiveEdit() throws Exception {
 
 		RebaseResult res = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.EDIT);
@@ -2568,6 +2575,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return ""; // not used
 					}
@@ -2624,6 +2632,7 @@ public void testRebaseInteractiveSingleSquashAndModifyMessage() throws Exception
 		git.rebase().setUpstream("HEAD~3")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(1).setAction(Action.SQUASH);
@@ -2632,6 +2641,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						final File messageSquashFile = new File(db
 								.getDirectory(), "rebase-merge/message-squash");
@@ -2704,6 +2714,7 @@ public void testRebaseInteractiveMultipleSquash() throws Exception {
 		git.rebase().setUpstream("HEAD~4")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(1).setAction(Action.SQUASH);
@@ -2713,6 +2724,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						final File messageSquashFile = new File(db.getDirectory(),
 								"rebase-merge/message-squash");
@@ -2786,6 +2798,7 @@ public void testRebaseInteractiveMixedSquashAndFixup() throws Exception {
 		git.rebase().setUpstream("HEAD~4")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(1).setAction(Action.FIXUP);
@@ -2795,6 +2808,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						final File messageSquashFile = new File(db
 								.getDirectory(), "rebase-merge/message-squash");
@@ -2861,6 +2875,7 @@ public void testRebaseInteractiveSingleFixup() throws Exception {
 		git.rebase().setUpstream("HEAD~3")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(1).setAction(Action.FIXUP);
@@ -2869,6 +2884,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						fail("No callback to modify commit message expected for single fixup");
 						return commit;
@@ -2910,6 +2926,7 @@ public void testRebaseInteractiveFixupWithBlankLines() throws Exception {
 		git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(1).setAction(Action.FIXUP);
@@ -2918,6 +2935,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						fail("No callback to modify commit message expected for single fixup");
 						return commit;
@@ -2950,6 +2968,7 @@ public void testRebaseInteractiveFixupFirstCommitShouldFail()
 		git.rebase().setUpstream("HEAD~1")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.FIXUP);
@@ -2958,6 +2977,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
@@ -2982,6 +3002,7 @@ public void testRebaseInteractiveSquashFirstCommitShouldFail()
 		git.rebase().setUpstream("HEAD~1")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.SQUASH);
@@ -2990,6 +3011,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
@@ -3013,6 +3035,7 @@ public void testRebaseEndsIfLastStepIsEdit() throws Exception {
 		git.rebase().setUpstream("HEAD~1")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.EDIT);
@@ -3021,6 +3044,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
@@ -3055,6 +3079,7 @@ public void testRebaseShouldStopForEditInCaseOfConflict()
 		RebaseResult result = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						steps.remove(0);
 						try {
@@ -3064,6 +3089,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
@@ -3097,6 +3123,7 @@ public void testRebaseShouldStopForRewordInCaseOfConflict()
 		RebaseResult result = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						steps.remove(0);
 						try {
@@ -3106,6 +3133,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return "rewritten commit message";
 					}
@@ -3114,6 +3142,7 @@ public String modifyCommitMessage(String commit) {
 		git.add().addFilepattern(FILE1).call();
 		result = git.rebase().runInteractively(new InteractiveHandler() {
 
+			@Override
 			public void prepareSteps(List<RebaseTodoLine> steps) {
 				steps.remove(0);
 				try {
@@ -3123,6 +3152,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 				}
 			}
 
+			@Override
 			public String modifyCommitMessage(String commit) {
 				return "rewritten commit message";
 			}
@@ -3160,6 +3190,7 @@ public void testRebaseShouldSquashInCaseOfConflict() throws Exception {
 		RebaseResult result = git.rebase().setUpstream("HEAD~3")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.PICK);
@@ -3170,6 +3201,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return "squashed message";
 					}
@@ -3178,6 +3210,7 @@ public String modifyCommitMessage(String commit) {
 		git.add().addFilepattern(FILE1).call();
 		result = git.rebase().runInteractively(new InteractiveHandler() {
 
+			@Override
 			public void prepareSteps(List<RebaseTodoLine> steps) {
 				try {
 					steps.get(0).setAction(Action.PICK);
@@ -3188,6 +3221,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 				}
 			}
 
+			@Override
 			public String modifyCommitMessage(String commit) {
 				return "squashed message";
 			}
@@ -3226,6 +3260,7 @@ public void testRebaseShouldFixupInCaseOfConflict() throws Exception {
 		RebaseResult result = git.rebase().setUpstream("HEAD~3")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.PICK);
@@ -3236,6 +3271,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
@@ -3244,6 +3280,7 @@ public String modifyCommitMessage(String commit) {
 		git.add().addFilepattern(FILE1).call();
 		result = git.rebase().runInteractively(new InteractiveHandler() {
 
+			@Override
 			public void prepareSteps(List<RebaseTodoLine> steps) {
 				try {
 					steps.get(0).setAction(Action.PICK);
@@ -3254,6 +3291,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 				}
 			}
 
+			@Override
 			public String modifyCommitMessage(String commit) {
 				return "commit";
 			}
@@ -3297,6 +3335,7 @@ public void testInteractiveRebaseWithModificationShouldNotDeleteDataOnAbort()
 		RebaseResult result = git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
 
+					@Override
 					public void prepareSteps(List<RebaseTodoLine> steps) {
 						try {
 							steps.get(0).setAction(Action.EDIT);
@@ -3306,6 +3345,7 @@ public void prepareSteps(List<RebaseTodoLine> steps) {
 						}
 					}
 
+					@Override
 					public String modifyCommitMessage(String commit) {
 						return commit;
 					}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
index 4c09a82..5c437ac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
@@ -69,6 +69,7 @@ public class RenameBranchCommandTest extends RepositoryTestCase {
 
 	private Git git;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
index ce235a7..f2e4d5b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
@@ -77,6 +77,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
 
 	private File committedFile;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -736,4 +737,21 @@ public void untrackedFileConflictsWithWorkingDirectory()
 		}
 		assertEquals("working-directory", read(path));
 	}
+
+	@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));
+		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));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
index ae8551e..b9f9f5b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
@@ -82,6 +82,7 @@ public class StashCreateCommandTest extends RepositoryTestCase {
 
 	private File untrackedFile;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
index 859277e..5d5be5d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
@@ -73,6 +73,7 @@ public class StashDropCommandTest extends RepositoryTestCase {
 
 	private File committedFile;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
index b044c01..1d9cd78 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -204,7 +204,7 @@ public void testCheckinCheckoutDifferences() throws IOException,
 	@Test
 	public void testIndexOnly() throws IOException, NoFilepatternException,
 			GitAPIException {
-		List<File> attrFiles = new ArrayList<File>();
+		List<File> attrFiles = new ArrayList<>();
 		attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
 				"*.txt -custom2"));
 		attrFiles.add(writeAttributesFile(".git/info/attributes",
@@ -813,7 +813,7 @@ private void assertEntry(FileMode type, String pathName,
 	}
 
 	private static Set<Attribute> asSet(Collection<Attribute> attributes) {
-		Set<Attribute> ret = new HashSet<Attribute>();
+		Set<Attribute> ret = new HashSet<>();
 		for (Attribute a : attributes) {
 			ret.add(a);
 		}
@@ -853,7 +853,7 @@ private File writeGlobalAttributeFile(String fileName, String... attributes)
 	}
 
 	static Set<Attribute> asSet(Attribute... attrs) {
-		HashSet<Attribute> result = new HashSet<Attribute>();
+		HashSet<Attribute> result = new HashSet<>();
 		for (Attribute attr : attrs)
 			result.add(attr);
 		return result;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
index 443c956..73c230a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
@@ -395,6 +395,7 @@ public void shouldReportFileModeChange() throws Exception {
 			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));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
index b4234dc..fabf034 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
@@ -89,7 +89,7 @@ public class DiffFormatterTest extends RepositoryTestCase {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		testDb = new TestRepository<Repository>(db);
+		testDb = new TestRepository<>(db);
 		df = new DiffFormatter(DisabledOutputStream.INSTANCE);
 		df.setRepository(db);
 		df.setAbbreviationLength(8);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
index 4315be9..64eb1c9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
@@ -75,7 +75,7 @@ public class RenameDetectorTest extends RepositoryTestCase {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		testDb = new TestRepository<Repository>(db);
+		testDb = new TestRepository<>(db);
 		rd = new RenameDetector(db);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
index 5408f76..d12f302 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
@@ -211,6 +211,7 @@ final class ReceivedEventMarkerException extends RuntimeException {
 		DirCache dc = db.lockDirCache();
 		IndexChangedListener listener = new IndexChangedListener() {
 
+			@Override
 			public void onIndexChanged(IndexChangedEvent event) {
 				throw new ReceivedEventMarkerException();
 			}
@@ -238,6 +239,7 @@ public void onIndexChanged(IndexChangedEvent event) {
 		dc = db.lockDirCache();
 		listener = new IndexChangedListener() {
 
+			@Override
 			public void onIndexChanged(IndexChangedEvent event) {
 				throw new ReceivedEventMarkerException();
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index 3e78046..92ce4e1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -178,7 +178,7 @@ public void testReadIndex_DirCacheTree() throws Exception {
 				.getObjectId());
 		assertEquals(cList.size(), jTree.getEntrySpan());
 
-		final ArrayList<CGitLsTreeRecord> subtrees = new ArrayList<CGitLsTreeRecord>();
+		final ArrayList<CGitLsTreeRecord> subtrees = new ArrayList<>();
 		for (final CGitLsTreeRecord r : cTree.values()) {
 			if (FileMode.TREE.equals(r.mode))
 				subtrees.add(r);
@@ -233,7 +233,7 @@ private static File pathOf(final String name) {
 	}
 
 	private static Map<String, CGitIndexRecord> readLsFiles() throws Exception {
-		final LinkedHashMap<String, CGitIndexRecord> r = new LinkedHashMap<String, CGitIndexRecord>();
+		final LinkedHashMap<String, CGitIndexRecord> r = new LinkedHashMap<>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(pathOf("gitgit.lsfiles")), "UTF-8"));
 		try {
@@ -249,7 +249,7 @@ private static Map<String, CGitIndexRecord> readLsFiles() throws Exception {
 	}
 
 	private static Map<String, CGitLsTreeRecord> readLsTree() throws Exception {
-		final LinkedHashMap<String, CGitLsTreeRecord> r = new LinkedHashMap<String, CGitLsTreeRecord>();
+		final LinkedHashMap<String, CGitLsTreeRecord> r = new LinkedHashMap<>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(pathOf("gitgit.lstree")), "UTF-8"));
 		try {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
index c85e156..20897a8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
@@ -72,7 +72,7 @@ public void apply(DirCacheEntry ent) {
 	}
 
 	private static final class RecordingEdit extends PathEdit {
-		final List<DirCacheEntry> entries = new ArrayList<DirCacheEntry>();
+		final List<DirCacheEntry> entries = new ArrayList<>();
 
 		public RecordingEdit(String entryPath) {
 			super(entryPath);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
index 3c1f231..3624100 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
@@ -56,6 +56,7 @@ public void testFileRepository_ChangeEventsOnlyOnSave() throws Exception {
 		final ConfigChangedEvent[] events = new ConfigChangedEvent[1];
 		db.getListenerList().addConfigChangedListener(
 				new ConfigChangedListener() {
+					@Override
 					public void onConfigChanged(ConfigChangedEvent event) {
 						events[0] = event;
 					}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index b1138f0..c9673a6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -44,12 +44,16 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 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 {
 
@@ -57,7 +61,7 @@ public class ManifestParserTest {
 	public void testManifestParser() throws Exception {
 		String baseUrl = "https://git.google.com/";
 		StringBuilder xmlContent = new StringBuilder();
-		Set<String> results = new HashSet<String>();
+		Set<String> results = new HashSet<>();
 		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
 			.append("<manifest>")
 			.append("<remote name=\"remote1\" fetch=\".\" />")
@@ -110,4 +114,49 @@ public void testManifestParser() throws Exception {
 				"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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append("foo")
+				.append("\" groups=\"a,test\" />")
+				.append("<project path=\"bar\" name=\"").append("bar")
+				.append("\" groups=\"notdefault\" />")
+				.append("<project path=\"foo/a\" name=\"").append("a")
+				.append("\" groups=\"a\" />")
+				.append("<project path=\"b\" name=\"").append("b")
+				.append("\" groups=\"b\" />").append("</manifest>");
+
+		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index ccd15d0..9cf4569 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -48,15 +48,28 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+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;
@@ -79,6 +92,7 @@ public class RepoCommandTest extends RepositoryTestCase {
 
 	private ObjectId oldCommitId;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
 
@@ -123,6 +137,108 @@ public void setUp() throws Exception {
 		resolveRelativeUris();
 	}
 
+	class IndexedRepos implements RepoCommand.RemoteReader {
+		Map<String, Repository> 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 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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+					.append("<manifest>")
+					.append("<remote name=\"origin\" fetch=\"" + fetchUrl + "\" />")
+					.append("<default revision=\"master\" remote=\"origin\" />")
+					.append("<project path=\"src\" name=\"chromium/src\" />")
+					.append("</manifest>");
+				RepoCommand cmd = new RepoCommand(dest);
+
+				IndexedRepos repos = new IndexedRepos();
+				repos.put(repoUrl, child);
+
+				RevCommit commit = cmd
+					.setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(StandardCharsets.UTF_8)))
+					.setRemoteReader(repos)
+					.setURI(baseUrl)
+					.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 testAddRepoManifest() throws Exception {
 		StringBuilder xmlContent = new StringBuilder();
@@ -248,46 +364,42 @@ public void testRepoManifestCopyFile() throws Exception {
 
 	@Test
 	public void testBareRepo() throws Exception {
-		try (
-				Repository remoteDb = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent
-					.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"foo\" name=\"").append(defaultUri)
-					.append("\" />").append("</manifest>");
-			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
-					xmlContent.toString());
-			RepoCommand command = new RepoCommand(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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" />").append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				xmlContent.toString());
+		RepoCommand command = new RepoCommand(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
@@ -370,208 +482,192 @@ public void testRevisionTag() throws Exception {
 
 	@Test
 	public void testRevisionBare() throws Exception {
-		try (
-				Repository remoteDb = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"").append(BRANCH)
-					.append("\" remote=\"remote1\" />")
-					.append("<project path=\"foo\" name=\"").append(defaultUri)
-					.append("\" />").append("</manifest>");
-			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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"").append(BRANCH)
+				.append("\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" />").append("</manifest>");
+		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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"foo\" name=\"").append(defaultUri)
-					.append("\" revision=\"").append(BRANCH).append("\" >")
-					.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
-					.append("<copyfile src=\"hello.txt\" dest=\"foo/Hello\" />")
-					.append("</project>").append("</manifest>");
-			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);
-		}
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" revision=\"").append(BRANCH).append("\" >")
+				.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
+				.append("<copyfile src=\"hello.txt\" dest=\"foo/Hello\" />")
+				.append("</project>").append("</manifest>");
+		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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"foo\" name=\"").append(defaultUri)
-					.append("\" revision=\"").append(BRANCH).append("\" >")
-					.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
-					.append("</project>").append("</manifest>");
-			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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"bar\" name=\"")
-					.append(defaultUri)
-					.append("\" revision=\"")
-					.append(BRANCH)
-					.append("\" >")
-					.append("<copyfile src=\"hello.txt\" dest=\"Hello.txt\" />")
-					.append("</project>").append("</manifest>");
-			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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" revision=\"").append(BRANCH).append("\" >")
+				.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
+				.append("</project>").append("</manifest>");
+		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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"bar\" name=\"").append(defaultUri)
+				.append("\" revision=\"").append(BRANCH).append("\" >")
+				.append("<copyfile src=\"hello.txt\" dest=\"Hello.txt\" />")
+				.append("</project>").append("</manifest>");
+		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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"foo/bar\" name=\"")
-					.append(groupBUri).append("\" />")
-					.append("<project path=\"a\" name=\"").append(groupAUri)
-					.append("\" />").append("<project path=\"foo\" name=\"")
-					.append(defaultUri).append("\" />").append("</manifest>");
-			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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo/bar\" name=\"").append(groupBUri)
+				.append("\" />").append("<project path=\"a\" name=\"")
+				.append(groupAUri).append("\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" />").append("</manifest>");
+		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
@@ -670,178 +766,157 @@ public void testRemoteAlias() throws Exception {
 
 	@Test
 	public void testTargetBranch() throws Exception {
-		try (
-				Repository remoteDb1 = createBareRepository();
-				Repository remoteDb2 = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent
-					.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
-					.append("<manifest>")
-					.append("<remote name=\"remote1\" fetch=\".\" />")
-					.append("<default revision=\"master\" remote=\"remote1\" />")
-					.append("<project path=\"foo\" name=\"").append(defaultUri)
-					.append("\" />").append("</manifest>");
-			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
-					xmlContent.toString());
-			RepoCommand command = new RepoCommand(remoteDb1);
-			command
-				.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
-				.setURI(rootUri)
-				.setTargetBranch("test")
-				.call();
-			ObjectId branchId = remoteDb1.resolve(
-					Constants.R_HEADS + "test^{tree}");
-			command = new RepoCommand(remoteDb2);
-			command
-				.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
-				.setURI(rootUri)
-				.call();
-			ObjectId defaultId = remoteDb2.resolve(Constants.HEAD + "^{tree}");
-			assertEquals(
+		Repository remoteDb1 = createBareRepository();
+		Repository remoteDb2 = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" />").append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				xmlContent.toString());
+		RepoCommand command = new RepoCommand(remoteDb1);
+		command.setPath(
+				tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri).setTargetBranch("test").call();
+		ObjectId branchId = remoteDb1
+				.resolve(Constants.R_HEADS + "test^{tree}");
+		command = new RepoCommand(remoteDb2);
+		command.setPath(
+				tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri).call();
+		ObjectId defaultId = remoteDb2.resolve(Constants.HEAD + "^{tree}");
+		assertEquals(
 				"The tree id of branch db and default db should be the same",
 				branchId, defaultId);
-		}
 	}
 
 	@Test
 	public void testRecordRemoteBranch() throws Exception {
-		try (
-				Repository remoteDb = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent
-				.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
 				.append("<manifest>")
 				.append("<remote name=\"remote1\" fetch=\".\" />")
 				.append("<default revision=\"master\" remote=\"remote1\" />")
 				.append("<project path=\"with-branch\" ")
-					.append("revision=\"master\" ")
-					.append("name=\"").append(notDefaultUri).append("\" />")
+				.append("revision=\"master\" ").append("name=\"")
+				.append(notDefaultUri).append("\" />")
 				.append("<project path=\"with-long-branch\" ")
-					.append("revision=\"refs/heads/master\" ")
-					.append("name=\"").append(defaultUri).append("\" />")
-				.append("</manifest>");
-			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				.append("revision=\"refs/heads/master\" ").append("name=\"")
+				.append(defaultUri).append("\" />").append("</manifest>");
+		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"));
-			}
+		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 {
-		try (
-				Repository remoteDb = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent
-				.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
 				.append("<manifest>")
 				.append("<remote name=\"remote1\" fetch=\".\" />")
 				.append("<default revision=\"master\" remote=\"remote1\" />")
 				.append("<project path=\"test\" ")
-					.append("revision=\"master\" ")
-					.append("name=\"").append(notDefaultUri).append("\" ")
-					.append("groups=\"a1,a2\" />")
-				.append("</manifest>");
-			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				.append("revision=\"master\" ").append("name=\"")
+				.append(notDefaultUri).append("\" ")
+				.append("groups=\"a1,a2\" />").append("</manifest>");
+		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(),
+		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",
+			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 {
-		try (
-				Repository remoteDb = createBareRepository();
-				Repository tempDb = createWorkRepository()) {
-			StringBuilder xmlContent = new StringBuilder();
-			xmlContent
-				.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
 				.append("<manifest>")
 				.append("<remote name=\"remote1\" fetch=\".\" />")
 				.append("<default revision=\"master\" remote=\"remote1\" />")
-				.append("<project path=\"shallow-please\" ")
-					.append("name=\"").append(defaultUri).append("\" ")
-					.append("clone-depth=\"1\" />")
-				.append("<project path=\"non-shallow\" ")
-					.append("name=\"").append(defaultUri).append("\" />")
-				.append("</manifest>");
-			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				.append("<project path=\"shallow-please\" ").append("name=\"")
+				.append(defaultUri).append("\" ").append("clone-depth=\"1\" />")
+				.append("<project path=\"non-shallow\" ").append("name=\"")
+				.append(defaultUri).append("\" />").append("</manifest>");
+		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"));
-			}
+		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"));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index c7125bb..c70b6f2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -1,25 +1,34 @@
 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.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.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.junit.MockSystemReader;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.AnyObjectId;
 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.util.SystemReader;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -27,6 +36,7 @@ public class DfsGarbageCollectorTest {
 	private TestRepository<InMemoryRepository> git;
 	private InMemoryRepository repo;
 	private DfsObjDatabase odb;
+	private MockSystemReader mockSystemReader;
 
 	@Before
 	public void setUp() throws IOException {
@@ -34,6 +44,13 @@ public void setUp() throws IOException {
 		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
@@ -62,6 +79,70 @@ public void testCollectionWithNoGarbage() throws Exception {
 	}
 
 	@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();
@@ -169,6 +250,297 @@ public void testCollectionWithGarbageNoCoalescence() throws Exception {
 		}
 	}
 
+	@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());
+			}
+		}
+	}
+
 	private TestRepository<InMemoryRepository>.CommitBuilder commit() {
 		return git.commit();
 	}
@@ -179,20 +551,17 @@ private void gcNoTtl() throws IOException {
 		run(gc);
 	}
 
-	private void gcWithTtl() throws InterruptedException, IOException {
-		// Wait for the system clock to move by at least 1 millisecond.
-		// This allows the DfsGarbageCollector to recognize the boundary.
-		long start = System.currentTimeMillis();
-		do {
-			Thread.sleep(10);
-		} while (System.currentTimeMillis() <= start);
-
+	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.MILLISECONDS);
+		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();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
index b738f7e..e71ee6d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
@@ -206,7 +206,7 @@ public void testInserterIgnoresUnreachable() throws IOException {
 		// 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<PackSource> pack_sources = new HashSet<PackSource>();
+		Set<PackSource> pack_sources = new HashSet<>();
 
 		assertEquals(2, packs.length);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java
new file mode 100644
index 0000000..db5b24a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java
@@ -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<InMemoryRepository> 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<InMemoryRepository>.CommitBuilder commit() {
+		return git.commit();
+	}
+
+	private void compact() throws IOException {
+		DfsPackCompactor compactor = new DfsPackCompactor(repo);
+		compactor.autoAdd();
+		compactor.compact(null);
+		odb.clearCache();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
index ece8f67..343120e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
@@ -81,14 +81,16 @@ public class AbbreviationTest extends LocalDiskRepositoryTestCase {
 
 	private TestRepository<Repository> test;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
 		db = createBareRepository();
 		reader = db.newObjectReader();
-		test = new TestRepository<Repository>(db);
+		test = new TestRepository<>(db);
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		if (reader != null) {
@@ -169,7 +171,7 @@ public void testAbbreviateIsActuallyUnique() throws Exception {
 
 		ObjectId id = id("9d5b926ed164e8ee88d3b8b1e525d699adda01ba");
 		byte[] idBuf = toByteArray(id);
-		List<PackedObjectInfo> objects = new ArrayList<PackedObjectInfo>();
+		List<PackedObjectInfo> objects = new ArrayList<>();
 		for (int i = 0; i < 256; i++) {
 			idBuf[9] = (byte) i;
 			objects.add(new PackedObjectInfo(ObjectId.fromRaw(idBuf)));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
index 1d71cb3..09438e96 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
@@ -77,6 +77,7 @@
 import org.junit.Test;
 
 public class ConcurrentRepackTest extends RepositoryTestCase {
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		WindowCacheConfig windowCacheConfig = new WindowCacheConfig();
@@ -85,6 +86,7 @@ public void setUp() throws Exception {
 		super.setUp();
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		super.tearDown();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
index 98a9501..9998666 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
@@ -57,7 +57,7 @@
 
 public class FileSnapshotTest {
 
-	private List<File> files = new ArrayList<File>();
+	private List<File> files = new ArrayList<>();
 
 	private File trash;
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index 41a1a5d..a2e1305 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -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;
@@ -175,14 +181,9 @@ public void testNotPackTwice(boolean aggressive) throws Exception {
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 
-		Iterator<PackFile> 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<PackFile> packs = new ArrayList<>(
+				repo.getObjectDatabase().getPacks());
+		assertEquals(11, packs.get(0).getObjectCount());
 	}
 
 	@Test
@@ -190,16 +191,26 @@ public void testDonePruneTooYoungPacks() 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();
 		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
@@ -210,7 +221,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		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 loose objects. Since
@@ -221,23 +232,102 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		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
 		gc.setPackExpireAgeMillis(0);
+
+		// we want to keep newly-loosened objects though
+		gc.setExpireAgeMillis(-1);
+
 		gc.gc();
 		stats = gc.getStatistics();
-		assertEquals(0, stats.numberOfLooseObjects);
+		assertEquals(1, stats.numberOfLooseObjects);
 		// if objects exist in multiple packFiles then they are counted multiple
 		// times
 		assertEquals(6, stats.numberOfPackedObjects);
 		assertEquals(1, stats.numberOfPackFiles);
-
 	}
 
-	private void configureGc(GC myGc, boolean aggressive) {
+	@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 PackConfig configureGc(GC myGc, boolean aggressive) {
 		PackConfig pconfig = new PackConfig(repo);
 		if (aggressive) {
 			pconfig.setDeltaSearchWindowSize(250);
@@ -246,5 +336,6 @@ private void configureGc(GC myGc, boolean aggressive) {
 		} else
 			pconfig = new PackConfig(repo);
 		myGc.setPackConfig(pconfig);
+		return pconfig;
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
index 776226b..643bb49 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -78,6 +78,7 @@ public void concurrentRepack() throws Exception {
 		class DoRepack extends EmptyProgressMonitor implements
 				Callable<Integer> {
 
+			@Override
 			public void beginTask(String title, int totalWork) {
 				if (title.equals(JGitText.get().writingObjects)) {
 					try {
@@ -91,6 +92,7 @@ public void beginTask(String title, int totalWork) {
 			}
 
 			/** @return 0 for success, 1 in case of error when writing pack */
+			@Override
 			public Integer call() throws Exception {
 				try {
 					gc.setProgressMonitor(this);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java
new file mode 100644
index 0000000..b37cd7a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java
@@ -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());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
index 9e28298..1a50844 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
@@ -73,7 +73,10 @@ public void testKeepFiles() throws Exception {
 				.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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
index d9317b8..5393987 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
@@ -67,6 +67,7 @@ public class GcOrphanFilesTest extends GcTestCase {
 
 	private File packDir;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index ea8dfa2..11a2a22 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -46,6 +46,7 @@
 import static java.lang.Integer.valueOf;
 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;
 
@@ -89,7 +90,7 @@ public void emptyRefDirectoryDeleted() throws Exception {
 		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));
 	}
@@ -104,6 +105,7 @@ public void concurrentOnlyOneWritesPackedRefs() throws Exception {
 		Callable<Integer> packRefs = new Callable<Integer>() {
 
 			/** @return 0 for success, 1 in case of error when writing pack */
+			@Override
 			public Integer call() throws Exception {
 				syncPoint.await();
 				try {
@@ -157,6 +159,7 @@ public void whileRefUpdatedRefUpdateSucceeds()
 		try {
 			Future<Result> result = pool.submit(new Callable<Result>() {
 
+				@Override
 				public Result call() throws Exception {
 					RefUpdate update = new RefDirectoryUpdate(
 							(RefDirectory) repo.getRefDatabase(),
@@ -181,6 +184,7 @@ public boolean isForceUpdate() {
 			});
 
 			pool.submit(new Callable<Void>() {
+				@Override
 				public Void call() throws Exception {
 					refUpdateLockedRef.await();
 					gc.packRefs();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
index 90c1152..d16998d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -62,15 +62,17 @@ public abstract class GcTestCase extends LocalDiskRepositoryTestCase {
 	protected GC gc;
 	protected RepoStatistics stats;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
 		repo = createWorkRepository();
-		tr = new TestRepository<FileRepository>(repo, new RevWalk(repo),
+		tr = new TestRepository<>(repo, new RevWalk(repo),
 				mockSystemReader);
 		gc = new GC(repo);
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		super.tearDown();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index 87554ae..f1cbb99 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -47,13 +47,18 @@
 
 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.JGitText;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -61,10 +66,15 @@
 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 {
@@ -162,7 +172,7 @@ public boolean accept(File dir, String name) {
 					return name.endsWith(".pack");
 				}
 			});
-			assertTrue(ret.length == 1);
+			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
@@ -171,9 +181,44 @@ public boolean accept(File dir, String name) {
 		}
 	}
 
+	@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<ObjectId> 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<Callable<ObjectId>> blobInsertersForTheSameFanOutDir(
 			final ObjectDirectory dir) {
 		Callable<ObjectId> callable = new Callable<ObjectId>() {
+			@Override
 			public ObjectId call() throws Exception {
 				return dir.newInserter().insert(Constants.OBJ_BLOB, new byte[0]);
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
index 1c10bb3..91bd523 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
@@ -107,6 +107,7 @@ private TestRng getRng() {
 		return rng;
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -116,10 +117,11 @@ public void setUp() throws Exception {
 		cfg.install();
 
 		repo = createBareRepository();
-		tr = new TestRepository<Repository>(repo);
+		tr = new TestRepository<>(repo);
 		wc = (WindowCursor) repo.newObjectReader();
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		if (wc != null)
@@ -289,7 +291,7 @@ public void testDelta_FailsOver2GiB() throws Exception {
 
 			f = new FileOutputStream(idxName);
 			try {
-				List<PackedObjectInfo> list = new ArrayList<PackedObjectInfo>();
+				List<PackedObjectInfo> list = new ArrayList<>();
 				list.add(a);
 				list.add(b);
 				Collections.sort(list);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
index b6aa4bc..9a43742 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
@@ -62,6 +62,7 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
 
 	PackIndex denseIdx;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
 		smallIdx = PackIndex.open(getFileForPack34be9032());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
index a4aa8bc..91dac09 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
@@ -65,6 +65,7 @@ public class PackReverseIndexTest extends RepositoryTestCase {
 	/**
 	 * Set up tested class instance, test constructor by the way.
 	 */
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
index 2e57952..c817dc3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
@@ -131,6 +131,7 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {
 
 	private RevCommit c5;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -143,6 +144,7 @@ public void setUp() throws Exception {
 		write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n");
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		if (writer != null) {
@@ -364,8 +366,8 @@ public void testWritePack3() throws MissingObjectException, IOException {
 				ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
 				ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
 				ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
-				ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"),
-				ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") };
+				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++)
@@ -412,6 +414,8 @@ public void testWritePack4ThinPack() throws IOException {
 	 */
 	@Test
 	public void testWritePack2SizeDeltasVsNoDeltas() throws Exception {
+		config.setReuseDeltas(false);
+		config.setDeltaCompress(false);
 		testWritePack2();
 		final long sizePack2NoDeltas = os.size();
 		tearDown();
@@ -465,7 +469,7 @@ public void testWritePack4SizeThinVsNoThin() throws Exception {
 	public void testDeltaStatistics() throws Exception {
 		config.setDeltaCompress(true);
 		FileRepository repo = createBareRepository();
-		TestRepository<FileRepository> testRepo = new TestRepository<FileRepository>(repo);
+		TestRepository<FileRepository> testRepo = new TestRepository<>(repo);
 		ArrayList<RevObject> blobs = new ArrayList<>();
 		blobs.add(testRepo.blob(genDeltableData(1000)));
 		blobs.add(testRepo.blob(genDeltableData(1005)));
@@ -534,7 +538,7 @@ public void testWriteIndex() throws Exception {
 	public void testExclude() throws Exception {
 		FileRepository repo = createBareRepository();
 
-		TestRepository<FileRepository> testRepo = new TestRepository<FileRepository>(
+		TestRepository<FileRepository> testRepo = new TestRepository<>(
 				repo);
 		BranchBuilder bb = testRepo.branch("refs/heads/master");
 		contentA = testRepo.blob("A");
@@ -659,7 +663,7 @@ public void testShallowFetchShallowAncestorDepth2() throws Exception {
 
 	private FileRepository setupRepoForShallowFetch() throws Exception {
 		FileRepository repo = createBareRepository();
-		TestRepository<Repository> r = new TestRepository<Repository>(repo);
+		TestRepository<Repository> r = new TestRepository<>(repo);
 		BranchBuilder bb = r.branch("refs/heads/master");
 		contentA = r.blob("A");
 		contentB = r.blob("B");
@@ -727,7 +731,7 @@ private static PackIndex writePack(FileRepository repo, RevWalk walk,
 	// TODO: testWritePackDeltasDepth()
 
 	private void writeVerifyPack1() throws IOException {
-		final HashSet<ObjectId> interestings = new HashSet<ObjectId>();
+		final HashSet<ObjectId> interestings = new HashSet<>();
 		interestings.add(ObjectId
 				.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
 		createVerifyOpenPack(interestings, NONE, false, false);
@@ -739,8 +743,8 @@ private void writeVerifyPack1() throws IOException {
 				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);
@@ -750,10 +754,10 @@ private void writeVerifyPack1() throws IOException {
 
 	private void writeVerifyPack2(boolean deltaReuse) throws IOException {
 		config.setReuseDeltas(deltaReuse);
-		final HashSet<ObjectId> interestings = new HashSet<ObjectId>();
+		final HashSet<ObjectId> interestings = new HashSet<>();
 		interestings.add(ObjectId
 				.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-		final HashSet<ObjectId> uninterestings = new HashSet<ObjectId>();
+		final HashSet<ObjectId> uninterestings = new HashSet<>();
 		uninterestings.add(ObjectId
 				.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"));
 		createVerifyOpenPack(interestings, uninterestings, false, false);
@@ -763,13 +767,11 @@ private void writeVerifyPack2(boolean deltaReuse) throws IOException {
 				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);
@@ -777,11 +779,17 @@ private void writeVerifyPack2(boolean deltaReuse) throws IOException {
 				.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<ObjectId> interestings = new HashSet<ObjectId>();
+		final HashSet<ObjectId> interestings = new HashSet<>();
 		interestings.add(ObjectId
 				.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-		final HashSet<ObjectId> uninterestings = new HashSet<ObjectId>();
+		final HashSet<ObjectId> uninterestings = new HashSet<>();
 		uninterestings.add(ObjectId
 				.fromString("c59759f143fb1fe21c197981df75a7ee00290799"));
 		createVerifyOpenPack(interestings, uninterestings, thin, false);
@@ -870,12 +878,13 @@ private PackParser index(final byte[] packData) throws IOException {
 	}
 
 	private void verifyObjectsOrder(final ObjectId objectsOrder[]) {
-		final List<PackIndex.MutableEntry> entries = new ArrayList<PackIndex.MutableEntry>();
+		final List<PackIndex.MutableEntry> entries = new ArrayList<>();
 
 		for (MutableEntry me : pack) {
 			entries.add(me.cloneEntry());
 		}
 		Collections.sort(entries, new Comparator<PackIndex.MutableEntry>() {
+			@Override
 			public int compare(MutableEntry o1, MutableEntry o2) {
 				return Long.signum(o1.getOffset() - o2.getOffset());
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 5999acf..53db123 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -100,6 +100,7 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
 
 	private RevTag v1_0;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -107,7 +108,7 @@ public void setUp() throws Exception {
 		diskRepo = createBareRepository();
 		refdir = (RefDirectory) diskRepo.getRefDatabase();
 
-		repo = new TestRepository<Repository>(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 +548,7 @@ public void testGetRefs_LooseSorting_Bug_348834() throws IOException {
 		ListenerHandle listener = Repository.getGlobalListenerList()
 				.addRefsChangedListener(new RefsChangedListener() {
 
+					@Override
 					public void onRefsChanged(RefsChangedEvent event) {
 						count[0]++;
 					}
@@ -1021,7 +1023,7 @@ public void test_repack() throws Exception {
 		assertEquals(v0_1.getId(), all.get("refs/tags/v0.1").getObjectId());
 
 		all = refdir.getRefs(RefDatabase.ALL);
-		refdir.pack(new ArrayList<String>(all.keySet()));
+		refdir.pack(new ArrayList<>(all.keySet()));
 
 		all = refdir.getRefs(RefDatabase.ALL);
 		assertEquals(5, all.size());
@@ -1265,12 +1267,13 @@ public void testRefsChangedStackOverflow() throws Exception {
 		final RefDatabase refDb = newRepo.getRefDatabase();
 		File packedRefs = new File(newRepo.getDirectory(), "packed-refs");
 		assertTrue(packedRefs.createNewFile());
-		final AtomicReference<StackOverflowError> error = new AtomicReference<StackOverflowError>();
-		final AtomicReference<IOException> exception = new AtomicReference<IOException>();
+		final AtomicReference<StackOverflowError> error = new AtomicReference<>();
+		final AtomicReference<IOException> exception = new AtomicReference<>();
 		final AtomicInteger changeCount = new AtomicInteger();
 		newRepo.getListenerList().addRefsChangedListener(
 				new RefsChangedListener() {
 
+					@Override
 					public void onRefsChanged(RefsChangedEvent event) {
 						try {
 							refDb.getRefs("ref");
@@ -1440,23 +1443,28 @@ private void deleteLooseRef(String name) {
 	private static final class StrictWorkMonitor implements ProgressMonitor {
 		private int lastWork, totalWork;
 
+		@Override
 		public void start(int totalTasks) {
 			// empty
 		}
 
+		@Override
 		public void beginTask(String title, int total) {
 			this.totalWork = total;
 			lastWork = 0;
 		}
 
+		@Override
 		public void update(int completed) {
 			lastWork += completed;
 		}
 
+		@Override
 		public void endTask() {
 			assertEquals("Units of work recorded", totalWork, lastWork);
 		}
 
+		@Override
 		public boolean isCancelled() {
 			return false;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 4be1d6d..0f26b0f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -46,6 +46,7 @@
 package org.eclipse.jgit.internal.storage.file;
 
 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;
@@ -239,14 +240,14 @@ public void testDeleteHead() throws IOException {
 
 	@Test
 	public void testDeleteHeadInBareRepo() throws IOException {
-		try (Repository bareRepo = createBareRepository()) {
-			RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
-			ref.setNewObjectId(ObjectId.fromString("0123456789012345678901234567890123456789"));
-			// Create the HEAD ref so we can delete it.
-			assertEquals(Result.NEW, ref.update());
-			ref = bareRepo.updateRef(Constants.HEAD);
-			delete(bareRepo, ref, Result.NO_CHANGE, true, true);
-		}
+		Repository bareRepo = createBareRepository();
+		RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
+		ref.setNewObjectId(ObjectId
+				.fromString("0123456789012345678901234567890123456789"));
+		// Create the HEAD ref so we can delete it.
+		assertEquals(Result.NEW, ref.update());
+		ref = bareRepo.updateRef(Constants.HEAD);
+		delete(bareRepo, ref, Result.NO_CHANGE, true, true);
 	}
 	/**
 	 * Delete a loose ref and make sure the directory in refs is deleted too,
@@ -758,11 +759,11 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 			// 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));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
index c6653bf..c5ab766 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
@@ -93,6 +93,7 @@ private TestRng getRng() {
 		return rng;
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -105,6 +106,7 @@ public void setUp() throws Exception {
 		wc = (WindowCursor) repo.newObjectReader();
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		if (wc != null)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
index aa061ba..cc34838 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
@@ -73,7 +73,7 @@ public class WindowCacheGetTest extends SampleDataRepositoryTestCase {
 	public void setUp() throws Exception {
 		super.setUp();
 
-		toLoad = new ArrayList<TestObject>();
+		toLoad = new ArrayList<>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(JGitTestUtil
 						.getTestResourceFile("all_packed_objects.txt")),
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
index 47f70d7..67a7819 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
@@ -77,6 +77,7 @@ public class LocalDiskRefTreeDatabaseTest extends LocalDiskRepositoryTestCase {
 	private RevCommit A;
 	private RevCommit B;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		FileRepository init = createWorkRepository();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
index e4d0f1d..9aef943 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
@@ -711,6 +711,7 @@ private class InMemRefTreeRepo extends InMemoryRepository {
 			RefTreeDatabaseTest.this.refdb = refs;
 		}
 
+		@Override
 		public RefDatabase getRefDatabase() {
 			return refs;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java
index e261b96..8e56419 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchTrackingStatusTest.java
@@ -60,7 +60,7 @@ public class BranchTrackingStatusTest extends RepositoryTestCase {
 	@Override
 	public void setUp() throws Exception {
 		super.setUp();
-		util = new TestRepository<Repository>(db);
+		util = new TestRepository<>(db);
 		StoredConfig config = util.getRepository().getConfig();
 		config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, "master",
 				ConfigConstants.CONFIG_KEY_REMOTE, "origin");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index aebbafe..e9505f6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -139,7 +139,7 @@ public void test004_PutGetSimple() {
 	@Test
 	public void test005_PutGetStringList() {
 		Config c = new Config();
-		final LinkedList<String> values = new LinkedList<String>();
+		final LinkedList<String> values = new LinkedList<>();
 		values.add("value1");
 		values.add("value2");
 		c.setStringList("my", null, "somename", values);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index bb553a4..75b574e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -130,7 +130,7 @@ private static HashMap<String, String> mkmap(String... args) {
 		if ((args.length % 2) > 0)
 			throw new IllegalArgumentException("needs to be pairs");
 
-		HashMap<String, String> map = new HashMap<String, String>();
+		HashMap<String, String> map = new HashMap<>();
 		for (int i = 0; i < args.length; i += 2) {
 			map.put(args[i], args[i + 1]);
 		}
@@ -228,7 +228,7 @@ public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
 	@Test
 	public void testInitialCheckout() throws Exception {
 		try (Git git = new Git(db)) {
-			TestRepository<Repository> db_t = new TestRepository<Repository>(db);
+			TestRepository<Repository> 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());
@@ -377,7 +377,7 @@ public void testRules4thru13_IndexEntryNotInHead() throws IOException {
 		// rules 4 and 5
 		HashMap<String, String> idxMap;
 
-		idxMap = new HashMap<String, String>();
+		idxMap = new HashMap<>();
 		idxMap.put("foo", "foo");
 		setupCase(null, null, idxMap);
 		go();
@@ -387,7 +387,7 @@ public void testRules4thru13_IndexEntryNotInHead() throws IOException {
 		assertTrue(getConflicts().isEmpty());
 
 		// rules 6 and 7
-		idxMap = new HashMap<String, String>();
+		idxMap = new HashMap<>();
 		idxMap.put("foo", "foo");
 		setupCase(null, idxMap, idxMap);
 		go();
@@ -396,7 +396,7 @@ public void testRules4thru13_IndexEntryNotInHead() throws IOException {
 
 		// rules 8 and 9
 		HashMap<String, String> mergeMap;
-		mergeMap = new HashMap<String, String>();
+		mergeMap = new HashMap<>();
 
 		mergeMap.put("foo", "merge");
 		setupCase(null, mergeMap, idxMap);
@@ -408,7 +408,7 @@ public void testRules4thru13_IndexEntryNotInHead() throws IOException {
 
 		// rule 10
 
-		HashMap<String, String> headMap = new HashMap<String, String>();
+		HashMap<String, String> headMap = new HashMap<>();
 		headMap.put("foo", "foo");
 		setupCase(headMap, null, idxMap);
 		go();
@@ -1705,7 +1705,8 @@ public void assertWorkDir(Map<String, String> i)
 							+ " in workDir. ", buffer, i.get(path).getBytes());
 					nrFiles++;
 				} else if (file.isDirectory()) {
-					if (file.list().length == 0) {
+					String[] files = file.list();
+					if (files != null && files.length == 0) {
 						assertEquals("found unexpected empty folder for path "
 								+ path + " in workDir. ", "/", i.get(path));
 						nrFiles++;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index 3259f62..0111b94 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -90,7 +90,6 @@ public void setUp() throws Exception {
 				.setPath("modules/submodule")
 				.setURI(submoduleStandalone.getDirectory().toURI().toString())
 				.call();
-		submoduleStandalone.close();
 		submodule_trash = submodule_db.getWorkTree();
 		addRepoToClose(submodule_db);
 		writeTrashFile("fileInRoot", "root");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index 733f166..2cb8f86 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -86,6 +86,7 @@ static PathEdit add(final Repository db, final File workdir,
 		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());
@@ -209,7 +210,7 @@ public void testConflicting() throws Exception {
 		diff.diff();
 
 		assertEquals("[b]",
-				new TreeSet<String>(diff.getChanged()).toString());
+				new TreeSet<>(diff.getChanged()).toString());
 		assertEquals("[]", diff.getAdded().toString());
 		assertEquals("[]", diff.getRemoved().toString());
 		assertEquals("[]", diff.getMissing().toString());
@@ -250,7 +251,7 @@ public void testConflictingDeletedAndModified() throws Exception {
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
 		diff.diff();
 
-		assertEquals("[]", new TreeSet<String>(diff.getChanged()).toString());
+		assertEquals("[]", new TreeSet<>(diff.getChanged()).toString());
 		assertEquals("[]", diff.getAdded().toString());
 		assertEquals("[]", diff.getRemoved().toString());
 		assertEquals("[]", diff.getMissing().toString());
@@ -290,7 +291,7 @@ public void testConflictingFromMultipleCreations() throws Exception {
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
 		diff.diff();
 
-		assertEquals("[]", new TreeSet<String>(diff.getChanged()).toString());
+		assertEquals("[]", new TreeSet<>(diff.getChanged()).toString());
 		assertEquals("[]", diff.getAdded().toString());
 		assertEquals("[]", diff.getRemoved().toString());
 		assertEquals("[]", diff.getMissing().toString());
@@ -443,7 +444,7 @@ public void testUntrackedFolders() throws Exception {
 			diff = new IndexDiff(db, Constants.HEAD,
 					new FileTreeIterator(db));
 			diff.diff();
-			assertEquals(new HashSet<String>(Arrays.asList("target")),
+			assertEquals(new HashSet<>(Arrays.asList("target")),
 					diff.getUntrackedFolders());
 
 			writeTrashFile("src/tst/A.java", "");
@@ -451,7 +452,7 @@ public void testUntrackedFolders() throws Exception {
 
 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
 			diff.diff();
-			assertEquals(new HashSet<String>(Arrays.asList("target", "src/tst")),
+			assertEquals(new HashSet<>(Arrays.asList("target", "src/tst")),
 					diff.getUntrackedFolders());
 
 			git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org")
@@ -462,7 +463,7 @@ public void testUntrackedFolders() throws Exception {
 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
 			diff.diff();
 			assertEquals(
-					new HashSet<String>(Arrays.asList("src/org", "src/tst",
+					new HashSet<>(Arrays.asList("src/org", "src/tst",
 							"target")),
 					diff.getUntrackedFolders());
 		}
@@ -496,7 +497,7 @@ public void testUntrackedNotIgnoredFolders() throws Exception {
 
 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
 			diff.diff();
-			assertEquals(new HashSet<String>(Arrays.asList("src")),
+			assertEquals(new HashSet<>(Arrays.asList("src")),
 					diff.getUntrackedFolders());
 
 			git.add().addFilepattern("src").call();
@@ -509,7 +510,7 @@ public void testUntrackedNotIgnoredFolders() throws Exception {
 			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
 			diff.diff();
 			assertEquals(
-					new HashSet<String>(Arrays.asList("srcs/com", "sr", "src/tst",
+					new HashSet<>(Arrays.asList("srcs/com", "sr", "src/tst",
 							"target")),
 					diff.getUntrackedFolders());
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 43160fb..7d298ed 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -768,6 +768,112 @@ public void testValidTree6() throws CorruptObjectException {
 	}
 
 	@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' },
@@ -1551,11 +1657,20 @@ private void checkOneName(String name) throws CorruptObjectException {
 		checker.checkTree(encodeASCII(b.toString()));
 	}
 
-	private static void entry(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 < 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) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java
index a36a5e9..898fcb6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdOwnerMapTest.java
@@ -73,7 +73,7 @@ public void init() {
 
 	@Test
 	public void testEmptyMap() {
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		assertTrue(m.isEmpty());
 		assertEquals(0, m.size());
 
@@ -86,7 +86,7 @@ public void testEmptyMap() {
 
 	@Test
 	public void testAddGetAndContains() {
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		m.add(id_1);
 		m.add(id_2);
 		m.add(id_3);
@@ -108,7 +108,7 @@ public void testAddGetAndContains() {
 
 	@Test
 	public void testClear() {
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 
 		m.add(id_1);
 		assertSame(id_1, m.get(id_1));
@@ -126,7 +126,7 @@ public void testClear() {
 
 	@Test
 	public void testAddIfAbsent() {
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		m.add(id_1);
 
 		assertSame(id_1, m.addIfAbsent(new SubId(id_1)));
@@ -145,7 +145,7 @@ public void testAddIfAbsent() {
 	@Test
 	public void testAddGrowsWithObjects() {
 		int n = 16384;
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		m.add(id_1);
 		for (int i = 32; i < n; i++)
 			m.add(new SubId(id(i)));
@@ -159,7 +159,7 @@ public void testAddGrowsWithObjects() {
 	@Test
 	public void testAddIfAbsentGrowsWithObjects() {
 		int n = 16384;
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		m.add(id_1);
 		for (int i = 32; i < n; i++)
 			m.addIfAbsent(new SubId(id(i)));
@@ -172,7 +172,7 @@ public void testAddIfAbsentGrowsWithObjects() {
 
 	@Test
 	public void testIterator() {
-		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<SubId>();
+		ObjectIdOwnerMap<SubId> m = new ObjectIdOwnerMap<>();
 		m.add(id_1);
 		m.add(id_2);
 		m.add(id_3);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java
index 07561de..adb2b12 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdSubclassMapTest.java
@@ -73,7 +73,7 @@ public void init() {
 
 	@Test
 	public void testEmptyMap() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		assertTrue(m.isEmpty());
 		assertEquals(0, m.size());
 
@@ -86,7 +86,7 @@ public void testEmptyMap() {
 
 	@Test
 	public void testAddGetAndContains() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		m.add(id_1);
 		m.add(id_2);
 		m.add(id_3);
@@ -108,7 +108,7 @@ public void testAddGetAndContains() {
 
 	@Test
 	public void testClear() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 
 		m.add(id_1);
 		assertSame(id_1, m.get(id_1));
@@ -126,7 +126,7 @@ public void testClear() {
 
 	@Test
 	public void testAddIfAbsent() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		m.add(id_1);
 
 		assertSame(id_1, m.addIfAbsent(new SubId(id_1)));
@@ -144,7 +144,7 @@ public void testAddIfAbsent() {
 
 	@Test
 	public void testAddGrowsWithObjects() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		m.add(id_1);
 		for (int i = 32; i < 8000; i++)
 			m.add(new SubId(id(i)));
@@ -157,7 +157,7 @@ public void testAddGrowsWithObjects() {
 
 	@Test
 	public void testAddIfAbsentGrowsWithObjects() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		m.add(id_1);
 		for (int i = 32; i < 8000; i++)
 			m.addIfAbsent(new SubId(id(i)));
@@ -170,7 +170,7 @@ public void testAddIfAbsentGrowsWithObjects() {
 
 	@Test
 	public void testIterator() {
-		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<SubId>();
+		ObjectIdSubclassMap<SubId> m = new ObjectIdSubclassMap<>();
 		m.add(id_1);
 		m.add(id_2);
 		m.add(id_3);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
index 80ece1c..0adea0b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
@@ -49,6 +49,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Locale;
+
 import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.junit.Test;
 
@@ -122,7 +124,7 @@ public void test010_toString() {
 	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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
index aa46d1a..9236b4e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -51,16 +51,19 @@
 import java.io.IOException;
 import java.util.TreeSet;
 
+import org.eclipse.jgit.api.Git;
 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.junit.Test;
 
 public class RacyGitTests extends RepositoryTestCase {
+	@Test
 	public void testIterator() throws IllegalStateException, IOException,
 			InterruptedException {
-		TreeSet<Long> modTimes = new TreeSet<Long>();
+		TreeSet<Long> modTimes = new TreeSet<>();
 		File lastFile = null;
 		for (int i = 0; i < 10; i++) {
 			lastFile = new File(db.getWorkTree(), "0." + i);
@@ -121,11 +124,16 @@ public void testIterator() throws IllegalStateException, IOException,
 		}
 	}
 
-	public void testRacyGitDetection() throws IOException,
-			IllegalStateException, InterruptedException {
-		TreeSet<Long> modTimes = new TreeSet<Long>();
+	@Test
+	public void testRacyGitDetection() throws Exception {
+		TreeSet<Long> modTimes = new TreeSet<>();
 		File lastFile;
 
+		// 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())));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
index c88eb3b..1c21194 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
@@ -63,7 +63,7 @@ public class RefDatabaseConflictingNamesTest {
 		@Override
 		public Map<String, Ref> getRefs(String prefix) throws IOException {
 			if (ALL.equals(prefix)) {
-				Map<String, Ref> existing = new HashMap<String, Ref>();
+				Map<String, Ref> 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 assertNoConflictingNames(String proposed) throws IOException {
 
 	private void assertConflictingNames(String proposed, String... conflicts)
 			throws IOException {
-		Set<String> expected = new HashSet<String>(Arrays.asList(conflicts));
+		Set<String> expected = new HashSet<>(Arrays.asList(conflicts));
 		assertEquals(expected,
-				new HashSet<String>(refDatabase.getConflictingNames(proposed)));
+				new HashSet<>(refDatabase.getConflictingNames(proposed)));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 707757b..7fb3309 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -99,7 +99,7 @@ public void testRemoteNames() throws Exception {
 				"ab/c", "dummy", true);
 		config.save();
 		assertEquals("[ab/c, origin]",
-				new TreeSet<String>(db.getRemoteNames()).toString());
+				new TreeSet<>(db.getRemoteNames()).toString());
 
 		// one-level deep remote branch
 		assertEquals("master",
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
index b44b4c3..1107c2c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
@@ -109,7 +109,7 @@ public void testFileKeyOpenExisting() throws IOException {
 
 	@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);
@@ -187,12 +187,14 @@ public void testRepositoryUsageCount() throws Exception {
 	}
 
 	@Test
-	public void testRepositoryUsageCountWithRegisteredRepository() {
-		assertEquals(1, ((Repository) db).useCnt.get());
-		RepositoryCache.register(db);
-		assertEquals(1, ((Repository) db).useCnt.get());
-		db.close();
-		assertEquals(0, ((Repository) db).useCnt.get());
+	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
@@ -232,8 +234,8 @@ public void testRepositoryUnregisteringWhenExpiredAndUsageCountNegative()
 
 	@Test
 	public void testRepositoryUnregisteringWhenExpired() throws Exception {
-		Repository repoA = createBareRepository();
-		Repository repoB = createBareRepository();
+		Repository repoA = createRepository(true, false);
+		Repository repoB = createRepository(true, false);
 		Repository repoC = createBareRepository();
 		RepositoryCache.register(repoA);
 		RepositoryCache.register(repoB);
@@ -265,11 +267,12 @@ public void testRepositoryUnregisteringWhenExpired() throws Exception {
 	}
 
 	@Test
-	public void testReconfigure() throws InterruptedException {
-		RepositoryCache.register(db);
-		assertTrue(RepositoryCache.isCached(db));
-		db.close();
-		assertTrue(RepositoryCache.isCached(db));
+	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
@@ -287,7 +290,7 @@ public void testReconfigure() throws InterruptedException {
 		// This wait will time out after 2048 ms
 		for (int i = 0; i <= 10; i++) {
 			Thread.sleep(1 << i);
-			if (!RepositoryCache.isCached(db)) {
+			if (!RepositoryCache.isCached(repo)) {
 				return;
 			}
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
index 2845f8a..545a188 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
@@ -62,6 +62,7 @@ public void testFailsMethodsOnBackgroundThread()
 		final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock);
 
 		runOnThread(new Runnable() {
+			@Override
 			public void run() {
 				try {
 					pm.start(1);
@@ -128,6 +129,7 @@ public void testUpdateOnBackgroundThreads() throws InterruptedException {
 		final CountDownLatch doEndWorker = new CountDownLatch(1);
 
 		final Thread bg = new Thread() {
+			@Override
 			public void run() {
 				assertFalse(pm.isCancelled());
 
@@ -175,24 +177,29 @@ private static class MockProgressMonitor implements ProgressMonitor {
 
 		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
index e9d46bb..d431a89 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
@@ -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 assertInvalidOnWindows(final String name) {
 		}
 	}
 
+	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 @@ public void testWindowsReservedNames() {
 		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
index 0e7109c..039a6e8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
@@ -110,7 +110,7 @@ public enum WorktreeState {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		db_t = new TestRepository<FileRepository>(db);
+		db_t = new TestRepository<>(db);
 	}
 
 	@Theory
@@ -778,7 +778,7 @@ void modifyIndex(IndexState indexState, String path, String other)
 			db.close();
 			file.delete();
 			db = new FileRepository(db.getDirectory());
-			db_t = new TestRepository<FileRepository>(db);
+			db_t = new TestRepository<>(db);
 			break;
 		}
 	}
@@ -846,7 +846,7 @@ void modifyWorktree(WorktreeState worktreeState, String path, String other)
 				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<FileRepository>(db);
+				db_t = new TestRepository<>(db);
 			}
 		} finally {
 			if (fos != null)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java
index a74ea98..65f2d09 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/nls/NLSTest.java
@@ -123,6 +123,7 @@ class GetBundle implements Callable<TranslationBundle> {
 				this.locale = locale;
 			}
 
+			@Override
 			public TranslationBundle call() throws Exception {
 				NLS.setLocale(locale);
 				barrier.await(); // wait for the other thread to set its locale
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java
index d3a6f18..a8d3904 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/DefaultNoteMergerTest.java
@@ -75,7 +75,7 @@ public class DefaultNoteMergerTest extends RepositoryTestCase {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		tr = new TestRepository<Repository>(db);
+		tr = new TestRepository<>(db);
 		reader = db.newObjectReader();
 		inserter = db.newObjectInserter();
 		merger = new DefaultNoteMerger();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java
index be7bead..117ea0c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapMergerTest.java
@@ -97,7 +97,7 @@ public class NoteMapMergerTest extends RepositoryTestCase {
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		tr = new TestRepository<Repository>(db);
+		tr = new TestRepository<>(db);
 		reader = db.newObjectReader();
 		inserter = db.newObjectInserter();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
index 325c9e2..a4a4909 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
@@ -83,7 +83,7 @@ public class NoteMapTest extends RepositoryTestCase {
 	public void setUp() throws Exception {
 		super.setUp();
 
-		tr = new TestRepository<Repository>(db);
+		tr = new TestRepository<>(db);
 		reader = db.newObjectReader();
 		inserter = db.newObjectInserter();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java
index d32172a..f265315 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/AbstractPlotRendererTest.java
@@ -96,7 +96,7 @@ private PlotCommitList<PlotLane> createCommitList(ObjectId start)
 			throws IOException {
 		TestPlotWalk walk = new TestPlotWalk(db);
 		walk.markStart(walk.parseCommit(start));
-		PlotCommitList<PlotLane> commitList = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> commitList = new PlotCommitList<>();
 		commitList.source(walk);
 		commitList.fillTo(1000);
 		return commitList;
@@ -116,7 +116,7 @@ public TestPlotWalk(Repository repo) {
 	private static class TestPlotRenderer extends
 			AbstractPlotRenderer<PlotLane, Object> {
 
-		List<Integer> indentations = new LinkedList<Integer>();
+		List<Integer> indentations = new LinkedList<>();
 
 		@Override
 		protected int drawLabel(int x, int y, Ref ref) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
index ecc119b..9a6043f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
@@ -123,7 +123,7 @@ public CommitListAssert noMoreCommits() {
 	}
 
 	private static Set<Integer> asSet(int... numbers) {
-		Set<Integer> result = new HashSet<Integer>();
+		Set<Integer> result = new HashSet<>();
 		for (int n : numbers)
 			result.add(Integer.valueOf(n));
 		return result;
@@ -138,7 +138,7 @@ public void testLinear() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(c.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -159,7 +159,7 @@ public void testMerged() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(d.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -181,7 +181,7 @@ public void testSideBranch() throws Exception {
 		pw.markStart(pw.lookupCommit(b.getId()));
 		pw.markStart(pw.lookupCommit(c.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -205,7 +205,7 @@ public void test2SideBranches() throws Exception {
 		pw.markStart(pw.lookupCommit(c.getId()));
 		pw.markStart(pw.lookupCommit(d.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -240,7 +240,7 @@ public void testBug300282_1() throws Exception {
 		// pw.markStart(pw.lookupCommit(f.getId()));
 		pw.markStart(pw.lookupCommit(g.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -274,7 +274,7 @@ public void testBug368927() throws Exception {
 		pw.markStart(pw.lookupCommit(i.getId()));
 		pw.markStart(pw.lookupCommit(g.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 		Set<Integer> childPositions = asSet(0, 1);
@@ -333,7 +333,7 @@ public void testEgitHistory() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(merge_fixed_logged_npe.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -406,7 +406,7 @@ public void testDuplicateParents() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(m3));
 		pw.markStart(pw.lookupCommit(s2));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -471,7 +471,7 @@ public void testBug419359() throws Exception {
 		pw.markStart(pw.lookupCommit(e.getId()));
 		pw.markStart(pw.lookupCommit(a5.getId()));
 
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -520,7 +520,7 @@ public void testMultipleMerges() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(a4));
 		pw.markStart(pw.lookupCommit(b3));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -565,7 +565,7 @@ public void testMergeBlockedBySelf() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(a4));
 		pw.markStart(pw.lookupCommit(b3));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -615,7 +615,7 @@ public void testMergeBlockedByOther() throws Exception {
 		pw.markStart(pw.lookupCommit(a4));
 		pw.markStart(pw.lookupCommit(b2));
 		pw.markStart(pw.lookupCommit(c));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -654,7 +654,7 @@ public void testDanglingCommitShouldContinueLane() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(a3));
 		pw.markStart(pw.lookupCommit(b1));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(2); // don't process a1
 
@@ -677,7 +677,7 @@ public void testTwoRoots1() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(a));
 		pw.markStart(pw.lookupCommit(b));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
@@ -696,7 +696,7 @@ public void testTwoRoots2() throws Exception {
 		PlotWalk pw = new PlotWalk(db);
 		pw.markStart(pw.lookupCommit(a));
 		pw.markStart(pw.lookupCommit(b2));
-		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+		PlotCommitList<PlotLane> pcl = new PlotCommitList<>();
 		pcl.source(pw);
 		pcl.fillTo(Integer.MAX_VALUE);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java
index 766a279..e5e51f7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java
@@ -50,10 +50,12 @@
 import org.junit.Test;
 
 public class DateRevQueueTest extends RevQueueTestCase<DateRevQueue> {
+	@Override
 	protected DateRevQueue create() {
 		return new DateRevQueue();
 	}
 
+	@Override
 	@Test
 	public void testEmpty() throws Exception {
 		super.testEmpty();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java
index 8877c38..a280045 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java
@@ -52,10 +52,12 @@
 import org.junit.Test;
 
 public class FIFORevQueueTest extends RevQueueTestCase<FIFORevQueue> {
+	@Override
 	protected FIFORevQueue create() {
 		return new FIFORevQueue();
 	}
 
+	@Override
 	@Test
 	public void testEmpty() throws Exception {
 		super.testEmpty();
@@ -70,7 +72,7 @@ public void testCloneEmpty() throws Exception {
 
 	@Test
 	public void testAddLargeBlocks() throws Exception {
-		final ArrayList<RevCommit> lst = new ArrayList<RevCommit>();
+		final ArrayList<RevCommit> lst = new ArrayList<>();
 		for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) {
 			final RevCommit c = commit();
 			lst.add(c);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java
index 1b2c5e2..ed8bc70 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java
@@ -53,10 +53,12 @@
 import org.junit.Test;
 
 public class LIFORevQueueTest extends RevQueueTestCase<LIFORevQueue> {
+	@Override
 	protected LIFORevQueue create() {
 		return new LIFORevQueue();
 	}
 
+	@Override
 	@Test
 	public void testEmpty() throws Exception {
 		super.testEmpty();
@@ -71,7 +73,7 @@ public void testCloneEmpty() throws Exception {
 
 	@Test
 	public void testAddLargeBlocks() throws Exception {
-		final ArrayList<RevCommit> lst = new ArrayList<RevCommit>();
+		final ArrayList<RevCommit> lst = new ArrayList<>();
 		for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) {
 			final RevCommit c = commit();
 			lst.add(c);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
index 4d75322..c069ba8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
@@ -62,7 +62,7 @@ public void setup(int count) throws Exception {
 			for (int i = 0; i < count; i++)
 				git.commit().setCommitter(committer).setAuthor(author)
 						.setMessage("commit " + i).call();
-			list = new RevCommitList<RevCommit>();
+			list = new RevCommitList<>();
 			w.markStart(w.lookupCommit(db.resolve(Constants.HEAD)));
 			list.source(w);
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java
index 154fcdf..2b5fc00 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java
@@ -146,6 +146,5 @@ public void testContains() {
 		set.add(flag1);
 		assertTrue(set.contains(flag1));
 		assertFalse(set.contains(flag2));
-		assertFalse(set.contains("bob"));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java
index 51cf2f1..bedbedd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java
@@ -53,6 +53,7 @@ public abstract class RevQueueTestCase<T extends AbstractRevQueue> extends
 		RevWalkTestCase {
 	protected T q;
 
+	@Override
 	public void setUp() throws Exception {
 		super.setUp();
 		q = create();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
index 05e552e..1860185 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
@@ -58,7 +58,7 @@
 public class RevWalkFollowFilterTest extends RevWalkTestCase {
 
 	private static class DiffCollector extends RenameCallback {
-		List<DiffEntry> diffs = new ArrayList<DiffEntry>();
+		List<DiffEntry> diffs = new ArrayList<>();
 
 		@Override
 		public void renamed(DiffEntry diff) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java
index db19f00..631e395 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java
@@ -69,6 +69,7 @@ public class RevWalkPathFilter6012Test extends RevWalkTestCase {
 
 	private HashMap<RevCommit, String> byName;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -94,7 +95,7 @@ public void setUp() throws Exception {
 		h = commit(f.getTree(), g, f);
 		i = commit(tree(file(pA, zS), file(pE, zY), file(pF, zF)), h);
 
-		byName = new HashMap<RevCommit, String>();
+		byName = new HashMap<>();
 		for (Field z : RevWalkPathFilter6012Test.class.getDeclaredFields()) {
 			if (z.getType() == RevCommit.class)
 				byName.put((RevCommit) z.get(this), z.getName());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
index 30586ee..f12cc08 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
@@ -62,7 +62,7 @@ public abstract class RevWalkTestCase extends RepositoryTestCase {
 	@Override
 	public void setUp() throws Exception {
 		super.setUp();
-		util = new TestRepository<Repository>(db, createRevWalk());
+		util = new TestRepository<>(db, createRevWalk());
 		rw = util.getRevWalk();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index a131e5e..93f4709 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -161,6 +161,7 @@ public void addExistentSubmodule() throws Exception {
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
@@ -182,6 +183,37 @@ public void apply(DirCacheEntry ent) {
 	}
 
 	@Test
+	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);
+		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");
@@ -268,4 +300,4 @@ public void addSubmoduleWithExistingSubmoduleDefined() throws Exception {
 					ConfigConstants.CONFIG_KEY_URL));
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java
index 2b46498..c7a009c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java
@@ -321,6 +321,7 @@ private String addSubmoduleToIndex() throws IOException {
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
index 51c180c..61df9d9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
@@ -92,6 +92,7 @@ public void repositoryWithMissingSubmodule() throws IOException,
 		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 @@ public void repositoryWithUninitializedSubmodule() throws IOException,
 		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 @@ public void repositoryWithNoHeadInSubmodule() throws IOException,
 		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 @@ public void repositoryWithNoSubmoduleRepository() throws IOException,
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
@@ -261,6 +265,7 @@ public void repositoryWithInitializedSubmodule() throws IOException,
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
@@ -316,6 +321,7 @@ public void repositoryWithDifferentRevCheckedOutSubmodule()
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java
index 54a6f77..13db44a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java
@@ -95,6 +95,7 @@ public void repositoryWithSubmodule() throws Exception {
 		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 @@ public void repositoryWithRelativeUriSubmodule() throws Exception {
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
index bcdd5e2..7064f60 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
@@ -93,6 +93,7 @@ public void repositoryWithSubmodule() throws Exception {
 		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 @@ public void repositoryWithUnconfiguredSubmodule() throws IOException,
 		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 @@ public void repositoryWithInitializedSubmodule() throws IOException,
 		DirCacheEditor editor = cache.editor();
 		editor.add(new PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
index 72b4611..8998a85 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
@@ -87,10 +87,11 @@
 public class SubmoduleWalkTest extends RepositoryTestCase {
 	private TestRepository<Repository> testDb;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		testDb = new TestRepository<Repository>(db);
+		testDb = new TestRepository<>(db);
 	}
 
 	@Test
@@ -118,6 +119,7 @@ public void repositoryWithRootLevelSubmodule() throws IOException,
 		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 @@ public void repositoryWithRootLevelSubmoduleAbsoluteRef()
 		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 @@ public void repositoryWithRootLevelSubmoduleRelativeRef()
 		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 @@ public void repositoryWithNestedSubmodule() throws IOException,
 		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 @@ public void generatorFilteredToOneOfTwoSubmodules() throws IOException {
 		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 @@ public void apply(DirCacheEntry ent) {
 		});
 		editor.add(new PathEdit(path2) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.GITLINK);
 				ent.setObjectId(id2);
@@ -330,6 +337,7 @@ public void indexWithGitmodules() throws Exception {
 		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 @@ public void apply(DirCacheEntry ent) {
 		});
 		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 @@ public void treeIdWithGitmodules() throws Exception {
 				.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 @@ public void testTreeIteratorWithGitmodules() throws Exception {
 				.add(DOT_GIT_MODULES, gitmodules.toText())
 				.edit(new PathEdit(path) {
 
+							@Override
 							public void apply(DirCacheEntry ent) {
 								ent.setFileMode(FileMode.GITLINK);
 								ent.setObjectId(subId);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
index 3dc022d..ea15ebe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
@@ -113,7 +113,7 @@ private static String getAuthMethodName(HttpAuthMethod authMethod) {
 	}
 
 	private static class AuthHeadersResponse extends JDKHttpConnection {
-		Map<String, List<String>> headerFields = new HashMap<String, List<String>>();
+		Map<String, List<String>> headerFields = new HashMap<>();
 
 		public AuthHeadersResponse(String[] authHeaders)
 				throws MalformedURLException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
index 3e427e5..1a86aaf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
@@ -57,7 +57,7 @@ public class LongMapTest {
 
 	@Before
 	public void setUp() throws Exception {
-		map = new LongMap<Long>();
+		map = new LongMap<>();
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
index ba44950..2fea8a9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
@@ -62,6 +62,7 @@ public class NetRCTest extends RepositoryTestCase {
 
 	private File configFile;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index 8ec39bf..fc520ab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -68,6 +68,7 @@ public class OpenSshConfigTest extends RepositoryTestCase {
 
 	private OpenSshConfig osc;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
index c829be9..b2497b8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
@@ -169,7 +169,7 @@ public void testTinyThinPack() throws Exception {
 	@Test
 	public void testPackWithDuplicateBlob() throws Exception {
 		final byte[] data = Constants.encode("0123456789abcdefg");
-		TestRepository<Repository> d = new TestRepository<Repository>(db);
+		TestRepository<Repository> d = new TestRepository<>(db);
 		assertTrue(db.hasObject(d.blob(data)));
 
 		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
index 33a9105..0e4e9cc 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -173,4 +173,27 @@ public void invalidCommand() throws IOException {
 			}
 		}
 	}
+
+	@Test
+	public void limitCommandBytes() throws IOException {
+		Map<String, RemoteRefUpdate> 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", 190);
+		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
index c346d79..f26201d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
@@ -86,6 +86,7 @@ public class PushOptionsTest extends RepositoryTestCase {
 	private ObjectId obj2;
 	private ReceivePack receivePack;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -115,6 +116,7 @@ public ReceivePack create(Object req, Repository git)
 		}
 	}
 
+	@Override
 	@After
 	public void tearDown() {
 		Transport.unregister(testProtocol);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
index 28473c7..104a69c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
@@ -83,8 +83,8 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
 	public void setUp() throws Exception {
 		super.setUp();
 		transport = new MockTransport(db, new URIish());
-		refUpdates = new HashSet<RemoteRefUpdate>();
-		advertisedRefs = new HashSet<Ref>();
+		refUpdates = new HashSet<>();
+		advertisedRefs = new HashSet<>();
 		connectionUpdateStatus = Status.OK;
 	}
 
@@ -421,7 +421,7 @@ public void close() {
 	private class MockPushConnection extends BaseConnection implements
 			PushConnection {
 		MockPushConnection() {
-			final Map<String, Ref> refsMap = new HashMap<String, Ref>();
+			final Map<String, Ref> refsMap = new HashMap<>();
 			for (final Ref r : advertisedRefs)
 				refsMap.put(r.getName(), r);
 			available(refsMap);
@@ -432,12 +432,14 @@ public void close() {
 			// nothing here
 		}
 
+		@Override
 		public void push(ProgressMonitor monitor,
 				Map<String, RemoteRefUpdate> refsToUpdate, OutputStream out)
 				throws TransportException {
 			push(monitor, refsToUpdate);
 		}
 
+		@Override
 		public void push(ProgressMonitor monitor,
 				Map<String, RemoteRefUpdate> refsToUpdate)
 				throws TransportException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index 94bc383..abd2840 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -108,7 +108,7 @@ public void setUp() throws Exception {
 
 		// Fill dst with a some common history.
 		//
-		TestRepository<Repository> d = new TestRepository<Repository>(dst);
+		TestRepository<Repository> d = new TestRepository<>(dst);
 		a = d.blob("a");
 		A = d.commit(d.tree(d.file("a", a)));
 		B = d.commit().parent(A).create();
@@ -128,16 +128,6 @@ public void setUp() throws Exception {
 		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<String, Ref> refs;
@@ -193,7 +183,7 @@ public void testSuccess() throws Exception {
 
 		// Now use b but in a different commit than what is hidden.
 		//
-		TestRepository<Repository> s = new TestRepository<Repository>(src);
+		TestRepository<Repository> s = new TestRepository<>(src);
 		RevCommit N = s.commit().parent(B).add("q", b).create();
 		s.update(R_MASTER, N);
 
@@ -284,7 +274,7 @@ private static void receive(final ReceivePack rp,
 	@Test
 	public void testUsingHiddenDeltaBaseFails() throws Exception {
 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
-		TestRepository<Repository> s = new TestRepository<Repository>(src);
+		TestRepository<Repository> s = new TestRepository<>(src);
 		RevCommit N = s.commit().parent(B).add("q",
 				s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta)))
 				.create();
@@ -337,7 +327,7 @@ public void testUsingHiddenDeltaBaseFails() throws Exception {
 	public void testUsingHiddenCommonBlobFails() throws Exception {
 		// Try to use the 'b' blob that is hidden.
 		//
-		TestRepository<Repository> s = new TestRepository<Repository>(src);
+		TestRepository<Repository> s = new TestRepository<>(src);
 		RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
 
 		// But don't include it in the pack.
@@ -387,7 +377,7 @@ public void testUsingHiddenCommonBlobFails() throws Exception {
 	public void testUsingUnknownBlobFails() throws Exception {
 		// Try to use the 'n' blob that is not on the server.
 		//
-		TestRepository<Repository> s = new TestRepository<Repository>(src);
+		TestRepository<Repository> s = new TestRepository<>(src);
 		RevBlob n = s.blob("n");
 		RevCommit N = s.commit().parent(B).add("q", n).create();
 
@@ -435,8 +425,73 @@ public void testUsingUnknownBlobFails() throws Exception {
 	}
 
 	@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<Repository> 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<Repository> s = new TestRepository<Repository>(src);
+		TestRepository<Repository> s = new TestRepository<>(src);
 		RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
 		RevTree t = s.parseBody(N).getTree();
 
@@ -556,8 +611,9 @@ private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)
 	}
 
 	private static final class HidePrivateHook extends AbstractAdvertiseRefsHook {
+		@Override
 		public Map<String, Ref> getAdvertisedRefs(Repository r, RevWalk revWalk) {
-			Map<String, Ref> refs = new HashMap<String, Ref>(r.getAllRefs());
+			Map<String, Ref> refs = new HashMap<>(r.getAllRefs());
 			assertNotNull(refs.remove(R_PRIVATE));
 			return refs;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java
index bb6c738..71b909e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SpiTransport.java
@@ -64,14 +64,17 @@ public class SpiTransport extends Transport {
 	 */
 	public static final TransportProtocol PROTO = new TransportProtocol() {
 
+		@Override
 		public String getName() {
 			return "Test SPI Transport Protocol";
 		}
 
+		@Override
 		public Set<String> 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 @@ private SpiTransport(Repository local, URIish uri) {
 		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 31b6418..b926e48 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
@@ -98,10 +98,10 @@ public ReceivePack create(User req, Repository db) {
 
   @Before
 	public void setUp() throws Exception {
-		protos = new ArrayList<TransportProtocol>();
-		local = new TestRepository<InMemoryRepository>(
+		protos = new ArrayList<>();
+		local = new TestRepository<>(
 				new InMemoryRepository(new DfsRepositoryDescription("local")));
-		remote = new TestRepository<InMemoryRepository>(
+		remote = new TestRepository<>(
 				new InMemoryRepository(new DfsRepositoryDescription("remote")));
   }
 
@@ -240,7 +240,7 @@ private TestProtocol<User> registerDefault() {
 
 	private TestProtocol<User> registerProto(UploadPackFactory<User> upf,
 			ReceivePackFactory<User> rpf) {
-		TestProtocol<User> proto = new TestProtocol<User>(upf, rpf);
+		TestProtocol<User> proto = new TestProtocol<>(upf, rpf);
 		protos.add(proto);
 		Transport.register(proto);
 		return proto;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
index 5519f61..d4c47d3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
@@ -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;
@@ -209,6 +211,45 @@ public void testFindRemoteRefUpdatesTrackingRef() throws IOException {
 		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<RefSpec> specs = Arrays.asList(specA, specC);
+		final Map<String, RefLeaseSpec> leases = new HashMap<>();
+		leases.put("refs/heads/b",
+				new RefLeaseSpec("refs/heads/b", "refs/heads/c"));
+
+		Collection<RemoteRefUpdate> 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();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
index cc5870e..1b434d3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
@@ -85,12 +85,12 @@
 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.Cipher;
 import javax.crypto.SecretKeyFactory;
 
 import org.eclipse.jgit.api.Git;
@@ -459,14 +459,14 @@ static String securityProviderName(String algorithm) throws Exception {
 
 		static List<String> cryptoCipherList(String regex) {
 			Set<String> source = Security.getAlgorithms("Cipher");
-			Set<String> target = new TreeSet<String>();
+			Set<String> target = new TreeSet<>();
 			for (String algo : source) {
-				algo = algo.toUpperCase();
+				algo = algo.toUpperCase(Locale.ROOT);
 				if (algo.matches(regex)) {
 					target.add(algo);
 				}
 			}
-			return new ArrayList<String>(target);
+			return new ArrayList<>(target);
 		}
 
 		/**
@@ -598,7 +598,7 @@ static void reportPolicy() {
 		}
 
 		static List<Object[]> product(List<String> one, List<String> two) {
-			List<Object[]> result = new ArrayList<Object[]>();
+			List<Object[]> result = new ArrayList<>();
 			for (String s1 : one) {
 				for (String s2 : two) {
 					result.add(new Object[] { s1, s2 });
@@ -759,7 +759,7 @@ static boolean isAlgorithmPresent(String algorithm) {
 			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();
+				String target = algorithm.toUpperCase(Locale.ROOT);
 				if (source.equalsIgnoreCase(target)) {
 					return true;
 				}
@@ -771,16 +771,16 @@ 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 crytoAlgo;
+			String cryptoAlgo;
 			String keyAlgo;
 			switch (version) {
 			case WalkEncryption.Vals.DEFAULT_VERS:
 			case WalkEncryption.JGitV1.VERSION:
-				crytoAlgo = profile;
+				cryptoAlgo = profile;
 				keyAlgo = profile;
 				break;
 			case WalkEncryption.JGitV2.VERSION:
-				crytoAlgo = props
+				cryptoAlgo = props
 						.getProperty(profile + WalkEncryption.Keys.X_ALGO);
 				keyAlgo = props
 						.getProperty(profile + WalkEncryption.Keys.X_KEY_ALGO);
@@ -789,7 +789,7 @@ static boolean isAlgorithmPresent(Properties props) {
 				return false;
 			}
 			try {
-				Cipher.getInstance(crytoAlgo);
+				InsecureCipherFactory.create(cryptoAlgo);
 				SecretKeyFactory.getInstance(keyAlgo);
 				return true;
 			} catch (Throwable e) {
@@ -1240,10 +1240,10 @@ public static class TestablePBE extends Base {
 
 		@Parameters(name = "Profile: {0}   Version: {1}")
 		public static Collection<Object[]> argsList() {
-			List<String> algorithmList = new ArrayList<String>();
+			List<String> algorithmList = new ArrayList<>();
 			algorithmList.addAll(cryptoCipherListPBE());
 
-			List<String> versionList = new ArrayList<String>();
+			List<String> versionList = new ArrayList<>();
 			versionList.add("0");
 			versionList.add("1");
 
@@ -1283,10 +1283,10 @@ public static class TestableTransformation extends Base {
 
 		@Parameters(name = "Profile: {0}   Version: {1}")
 		public static Collection<Object[]> argsList() {
-			List<String> algorithmList = new ArrayList<String>();
+			List<String> algorithmList = new ArrayList<>();
 			algorithmList.addAll(cryptoCipherListTrans());
 
-			List<String> versionList = new ArrayList<String>();
+			List<String> versionList = new ArrayList<>();
 			versionList.add("1");
 
 			return product(algorithmList, versionList);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
deleted file mode 100644
index cd55cba..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2012-2013, Robin Rosenberg <robin.rosenberg@dewire.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;
-
-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 {
-		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());
-		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
-			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
-			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
-			assertEquals("link", fti.getEntryPathString());
-			assertEquals("link", dci.getEntryPathString());
-
-			// test
-			assertTrue(fti.isModified(dci.getDirCacheEntry(), true,
-					db.newObjectReader()));
-		}
-	}
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index ef0f2d9..e34cb97 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -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;
@@ -68,6 +69,7 @@
 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;
@@ -85,6 +87,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
 
 	private long[] mtime;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -381,6 +384,7 @@ public void submoduleHeadMatchesIndex() throws Exception {
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
 
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -415,6 +419,7 @@ public void submoduleWithNoGitDirectory() throws Exception {
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
 
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -450,6 +455,7 @@ public void submoduleWithNoHead() throws Exception {
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
 
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -484,6 +490,7 @@ public void submoduleDirectoryIterator() throws Exception {
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
 
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -519,6 +526,7 @@ public void submoduleNestedWithHeadMatchingIndex() throws Exception {
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
 
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -653,6 +661,157 @@ public void testCustomFileModeStrategyFromParentIterator() throws Exception {
 		}
 	}
 
+	@Test
+	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)
 			throws MissingObjectException, IncorrectObjectTypeException,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java
index bb4b066..2f797e3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/IndexDiffFilterTest.java
@@ -89,6 +89,7 @@ public class IndexDiffFilterTest extends RepositoryTestCase {
 
 	private Git git;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
index 9f0f067..cba35d8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
@@ -66,6 +66,7 @@ public class InterIndexDiffFilterTest extends LocalDiskRepositoryTestCase {
 
 	private Repository db;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
index 5edc192..d921aab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
@@ -259,6 +259,7 @@ TreeWalk fakeWalk(final String path) throws IOException {
 		DirCacheEditor dce = dc.editor();
 		dce.add(new DirCacheEditor.PathEdit(path) {
 
+			@Override
 			public void apply(DirCacheEntry ent) {
 				ent.setFileMode(FileMode.REGULAR_FILE);
 			}
@@ -277,6 +278,7 @@ 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);
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
new file mode 100644
index 0000000..7c819c5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
@@ -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 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+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<String> expected = Arrays.asList("sub1/suba/a.txt",
+				"sub1/subb/b.txt");
+
+		TreeFilter tf = PathFilter.create("sub1");
+		List<String> paths = getMatchingPaths(treeId, tf);
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testSingleSubPath() throws IOException {
+		List<String> expected = Collections.singletonList("sub1/suba/a.txt");
+
+		TreeFilter tf = PathFilter.create("sub1/suba");
+		List<String> paths = getMatchingPaths(treeId, tf);
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testSinglePathNegate() throws IOException {
+		List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+				"sub2/suba/a.txt");
+
+		TreeFilter tf = PathFilter.create("sub1").negate();
+		List<String> paths = getMatchingPaths(treeId, tf);
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testSingleSubPathNegate() throws IOException {
+		List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+				"sub1/subb/b.txt", "sub2/suba/a.txt");
+
+		TreeFilter tf = PathFilter.create("sub1/suba").negate();
+		List<String> paths = getMatchingPaths(treeId, tf);
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiTwoPath() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiThreePath() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiTwoSubPath() throws IOException {
+		List<String> expected = Arrays.asList("sub1/subb/b.txt",
+				"sub2/suba/a.txt");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"),
+				PathFilter.create("sub2/suba")};
+		List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiTwoMixSubPath() throws IOException {
+		List<String> expected = Arrays.asList("sub1/subb/b.txt",
+				"sub2/suba/a.txt");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"),
+				PathFilter.create("sub2")};
+		List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiTwoMixSubPathNegate() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testOrMultiThreeMixSubPathNegate() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testPatternParentFileMatch() throws IOException {
+		List<String> expected = Collections.emptyList();
+
+		TreeFilter tf = PathFilter.create("a.txt/test/path");
+		List<String> paths = getMatchingPaths(treeId, tf);
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiPath() throws IOException {
+		List<String> expected = Collections.emptyList();
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+				PathFilter.create("sub2")};
+		List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiPathNegate() throws IOException {
+		List<String> expected = Arrays.asList("sub1/suba/a.txt",
+				"sub1/subb/b.txt");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+				PathFilter.create("sub2").negate()};
+		List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiSubPathDualNegate() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiSubPath() throws IOException {
+		List<String> expected = Collections.emptyList();
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+				PathFilter.create("sub2/suba")};
+		List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiSubPathNegate() throws IOException {
+		List<String> expected = Collections.singletonList("sub1/subb/b.txt");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+				PathFilter.create("sub1/suba").negate()};
+		List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testAndMultiThreeSubPathNegate() throws IOException {
+		List<String> 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<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testTopAndMultiPathDualNegate() throws IOException {
+		List<String> expected = Arrays.asList("a.txt", "sub1.txt");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(),
+				PathFilter.create("sub2").negate()};
+		List<String> paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testTopAndMultiSubPathDualNegate() throws IOException {
+		List<String> 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<String> paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testTopOrMultiPathDual() throws IOException {
+		List<String> expected = Arrays.asList("sub1.txt", "sub2");
+
+		TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1.txt"),
+				PathFilter.create("sub2")};
+		List<String> paths = getMatchingPathsFlat(treeId, OrTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	@Test
+	public void testTopNotPath() throws IOException {
+		List<String> expected = Arrays.asList("a.txt", "sub1.txt", "sub2");
+
+		TreeFilter tf = PathFilter.create("sub1");
+		List<String> paths = getMatchingPathsFlat(treeId, NotTreeFilter.create(tf));
+
+		assertEquals(expected, paths);
+	}
+
+	private List<String> getMatchingPaths(final ObjectId objId,
+			TreeFilter tf) throws IOException {
+		return getMatchingPaths(objId, tf, true);
+	}
+
+	private List<String> getMatchingPathsFlat(final ObjectId objId,
+			TreeFilter tf) throws IOException {
+		return getMatchingPaths(objId, tf, false);
+	}
+
+	private List<String> 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<String> 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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
index 3885c41..38adda3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
@@ -118,7 +118,7 @@ private List<String> getMatchingPaths(String suffixFilter,
 			tw.setRecursive(recursiveWalk);
 			tw.addTree(treeId);
 
-			List<String> paths = new ArrayList<String>();
+			List<String> paths = new ArrayList<>();
 			while (tw.next())
 				paths.add(tw.getPathString());
 			return paths;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
index 8b042bd..0a3de85 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
@@ -57,22 +57,22 @@ public class BlockListTest {
 	public void testEmptyList() {
 		BlockList<String> empty;
 
-		empty = new BlockList<String>();
+		empty = new BlockList<>();
 		assertEquals(0, empty.size());
 		assertTrue(empty.isEmpty());
 		assertFalse(empty.iterator().hasNext());
 
-		empty = new BlockList<String>(0);
+		empty = new BlockList<>(0);
 		assertEquals(0, empty.size());
 		assertTrue(empty.isEmpty());
 		assertFalse(empty.iterator().hasNext());
 
-		empty = new BlockList<String>(1);
+		empty = new BlockList<>(1);
 		assertEquals(0, empty.size());
 		assertTrue(empty.isEmpty());
 		assertFalse(empty.iterator().hasNext());
 
-		empty = new BlockList<String>(64);
+		empty = new BlockList<>(64);
 		assertEquals(0, empty.size());
 		assertTrue(empty.isEmpty());
 		assertFalse(empty.iterator().hasNext());
@@ -80,7 +80,7 @@ public void testEmptyList() {
 
 	@Test
 	public void testGet() {
-		BlockList<String> list = new BlockList<String>(4);
+		BlockList<String> list = new BlockList<>(4);
 
 		try {
 			list.get(-1);
@@ -121,7 +121,7 @@ public void testGet() {
 
 	@Test
 	public void testSet() {
-		BlockList<String> list = new BlockList<String>(4);
+		BlockList<String> list = new BlockList<>(4);
 
 		try {
 			list.set(-1, "foo");
@@ -168,7 +168,7 @@ public void testSet() {
 
 	@Test
 	public void testAddToEnd() {
-		BlockList<Integer> list = new BlockList<Integer>(4);
+		BlockList<Integer> list = new BlockList<>(4);
 		int cnt = BlockList.BLOCK_SIZE * 3;
 
 		for (int i = 0; i < cnt; i++)
@@ -192,7 +192,7 @@ public void testAddToEnd() {
 
 	@Test
 	public void testAddSlowPath() {
-		BlockList<String> list = new BlockList<String>(4);
+		BlockList<String> list = new BlockList<>(4);
 
 		String fooStr = "foo";
 		String barStr = "bar";
@@ -223,7 +223,7 @@ public void testAddSlowPath() {
 
 	@Test
 	public void testRemoveFromEnd() {
-		BlockList<String> list = new BlockList<String>(4);
+		BlockList<String> list = new BlockList<>(4);
 
 		String fooStr = "foo";
 		String barStr = "bar";
@@ -245,7 +245,7 @@ public void testRemoveFromEnd() {
 
 	@Test
 	public void testRemoveSlowPath() {
-		BlockList<String> list = new BlockList<String>(4);
+		BlockList<String> list = new BlockList<>(4);
 
 		String fooStr = "foo";
 		String barStr = "bar";
@@ -270,7 +270,7 @@ public void testRemoveSlowPath() {
 
 	@Test
 	public void testAddRemoveAdd() {
-		BlockList<Integer> list = new BlockList<Integer>();
+		BlockList<Integer> 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 @@ public void testAddRemoveAdd() {
 
 	@Test
 	public void testAddAllFromOtherList() {
-		BlockList<Integer> src = new BlockList<Integer>(4);
+		BlockList<Integer> 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<Integer> dst = new BlockList<Integer>(4);
+		BlockList<Integer> dst = new BlockList<>(4);
 		dst.add(Integer.valueOf(255));
 		dst.addAll(src);
 		assertEquals(cnt + 2, dst.size());
@@ -301,7 +301,7 @@ public void testAddAllFromOtherList() {
 
 	@Test
 	public void testFastIterator() {
-		BlockList<Integer> list = new BlockList<Integer>(4);
+		BlockList<Integer> list = new BlockList<>(4);
 		int cnt = BlockList.BLOCK_SIZE * 3;
 
 		for (int i = 0; i < cnt; i++)
@@ -318,7 +318,7 @@ public void testFastIterator() {
 
 	@Test
 	public void testAddRejectsBadIndexes() {
-		BlockList<Integer> list = new BlockList<Integer>(4);
+		BlockList<Integer> list = new BlockList<>(4);
 		list.add(Integer.valueOf(41));
 
 		try {
@@ -336,7 +336,7 @@ public void testAddRejectsBadIndexes() {
 
 	@Test
 	public void testRemoveRejectsBadIndexes() {
-		BlockList<Integer> list = new BlockList<Integer>(4);
+		BlockList<Integer> list = new BlockList<>(4);
 		list.add(Integer.valueOf(41));
 
 		try {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index aaeb79c..6fed233 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -583,23 +583,6 @@ public void testChangeIdAfterBugOrIssue() throws Exception {
 						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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
deleted file mode 100644
index cc1fdc2..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2013, Robin Rosenberg <robin.rosenberg@dewire.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.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 {
-		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()));
-	}
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
similarity index 87%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
index 1f78e02..109d0e6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
@@ -50,15 +50,32 @@
 
 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 FileUtilTest {
+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
@@ -508,8 +525,60 @@ public void testRelativize_whitespaces() {
 		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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java
index 1fe3dbd..796af99 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ReadLinesTest.java
@@ -51,7 +51,7 @@
 import org.junit.Test;
 
 public class ReadLinesTest {
-	List<String> l = new ArrayList<String>();
+	List<String> l = new ArrayList<>();
 
 	@Before
 	public void clearList() {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
index 4b7ab26..5a1468a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
@@ -89,7 +89,7 @@ public void testEmpty() {
 
 	@Test
 	public void testEmptyBuilder() {
-		RefList<Ref> list = new RefList.Builder<Ref>().toRefList();
+		RefList<Ref> list = new RefList.Builder<>().toRefList();
 		assertEquals(0, list.size());
 		assertFalse(list.iterator().hasNext());
 		assertEquals(-1, list.find("a"));
@@ -111,7 +111,7 @@ public void testEmptyBuilder() {
 
 	@Test
 	public void testBuilder_AddThenSort() {
-		RefList.Builder<Ref> builder = new RefList.Builder<Ref>(1);
+		RefList.Builder<Ref> builder = new RefList.Builder<>(1);
 		builder.add(REF_B);
 		builder.add(REF_A);
 
@@ -129,7 +129,7 @@ public void testBuilder_AddThenSort() {
 
 	@Test
 	public void testBuilder_AddAll() {
-		RefList.Builder<Ref> builder = new RefList.Builder<Ref>(1);
+		RefList.Builder<Ref> builder = new RefList.Builder<>(1);
 		Ref[] src = { REF_A, REF_B, REF_c, REF_A };
 		builder.addAll(src, 1, 2);
 
@@ -141,7 +141,7 @@ public void testBuilder_AddAll() {
 
 	@Test
 	public void testBuilder_Set() {
-		RefList.Builder<Ref> builder = new RefList.Builder<Ref>();
+		RefList.Builder<Ref> builder = new RefList.Builder<>();
 		builder.add(REF_A);
 		builder.add(REF_A);
 
@@ -163,7 +163,7 @@ public void testBuilder_Set() {
 
 	@Test
 	public void testBuilder_Remove() {
-		RefList.Builder<Ref> builder = new RefList.Builder<Ref>();
+		RefList.Builder<Ref> builder = new RefList.Builder<>();
 		builder.add(REF_A);
 		builder.add(REF_B);
 		builder.remove(0);
@@ -364,7 +364,7 @@ public void testBuilder_ToString() {
 		exp.append(REF_B);
 		exp.append("]");
 
-		RefList.Builder<Ref> list = new RefList.Builder<Ref>();
+		RefList.Builder<Ref> list = new RefList.Builder<>();
 		list.add(REF_A);
 		list.add(REF_B);
 		assertEquals(exp.toString(), list.toString());
@@ -442,16 +442,16 @@ public void testCopyLeadingPrefix() {
 
 	@Test
 	public void testCopyConstructorReusesArray() {
-		RefList.Builder<Ref> one = new RefList.Builder<Ref>();
+		RefList.Builder<Ref> one = new RefList.Builder<>();
 		one.add(REF_A);
 
-		RefList<Ref> two = new RefList<Ref>(one.toRefList());
+		RefList<Ref> two = new RefList<>(one.toRefList());
 		one.set(0, REF_B);
 		assertSame(REF_B, two.get(0));
 	}
 
 	private static RefList<Ref> toList(Ref... refs) {
-		RefList.Builder<Ref> b = new RefList.Builder<Ref>(refs.length);
+		RefList.Builder<Ref> b = new RefList.Builder<>(refs.length);
 		b.addAll(refs, 0, refs.length);
 		return b.toRefList();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
index 78ab27a..e6402a4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
@@ -481,7 +481,7 @@ public void testEntryTypeSet() {
 	}
 
 	private static RefList<Ref> toList(Ref... refs) {
-		RefList.Builder<Ref> b = new RefList.Builder<Ref>(refs.length);
+		RefList.Builder<Ref> b = new RefList.Builder<>(refs.length);
 		b.addAll(refs, 0, refs.length);
 		return b.toRefList();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
index c213157..6b5ef7e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
@@ -166,6 +166,7 @@ public void testSkip() throws IOException {
 		assertEquals(-1, u.read());
 
 		u.add(new ByteArrayInputStream(new byte[] { 20, 30 }) {
+			@Override
 			public long skip(long n) {
 				return 0;
 			}
@@ -180,11 +181,13 @@ public void testAutoCloseDuringRead() throws IOException {
 		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 @@ public void testCloseDuringClose() throws IOException {
 		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 close() {
 	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 --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java
new file mode 100644
index 0000000..0778989
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java
@@ -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 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.nio.charset.StandardCharsets;
+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(StandardCharsets.UTF_8));
+		ObjectId m1 = ObjectId.fromRaw(m.digest());
+
+		SHA1 s = SHA1.newInstance();
+		s.update(TEST1.getBytes(StandardCharsets.UTF_8));
+		ObjectId s1 = ObjectId.fromRaw(s.digest());
+
+		s.reset();
+		s.update(TEST1.getBytes(StandardCharsets.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(StandardCharsets.UTF_8));
+		ObjectId m1 = ObjectId.fromRaw(m.digest());
+
+		SHA1 s = SHA1.newInstance();
+		s.update(TEST2.getBytes(StandardCharsets.UTF_8));
+		ObjectId s1 = ObjectId.fromRaw(s.digest());
+
+		s.reset();
+		s.update(TEST2.getBytes(StandardCharsets.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 --git a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
index 808ec3a..ede0f7d 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit.ui/BUCK b/org.eclipse.jgit.ui/BUCK
deleted file mode 100644
index fcd87cf..0000000
--- a/org.eclipse.jgit.ui/BUCK
+++ /dev/null
@@ -1,7 +0,0 @@
-java_library(
-  name = 'ui',
-  srcs = glob(['src/**']),
-  resources = glob(['resources/**']),
-  deps = ['//org.eclipse.jgit:jgit'],
-  visibility = ['PUBLIC'],
-)
diff --git a/org.eclipse.jgit.ui/BUILD b/org.eclipse.jgit.ui/BUILD
new file mode 100644
index 0000000..85ae5c0
--- /dev/null
+++ b/org.eclipse.jgit.ui/BUILD
@@ -0,0 +1,9 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "ui",
+    srcs = glob(["src/**"]),
+    resource_strip_prefix = "org.eclipse.jgit.ui/resources",
+    resources = glob(["resources/**"]),
+    deps = ["//org.eclipse.jgit:jgit"],
+)
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 667e3b2..15fbe6c 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="4.6.2"
-Import-Package: org.eclipse.jgit.errors;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.lib;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.nls;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revplot;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.revwalk;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.transport;version="[4.6.2,4.7.0)",
- org.eclipse.jgit.util;version="[4.6.2,4.7.0)"
+Export-Package: org.eclipse.jgit.awtui;version="4.7.8"
+Import-Package: org.eclipse.jgit.errors;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.lib;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.nls;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revplot;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.revwalk;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.transport;version="[4.7.8,4.8.0)",
+ org.eclipse.jgit.util;version="[4.7.8,4.8.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 280399b..18c1119 100644
--- a/org.eclipse.jgit.ui/pom.xml
+++ b/org.eclipse.jgit.ui/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
index 4d32235..4c8cf53 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
@@ -146,14 +146,17 @@ class CommitTableModel extends AbstractTableModel {
 
 		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<SwingLane> c = allCommits.get(rowIndex);
 			switch (columnIndex) {
@@ -180,6 +183,7 @@ PersonIdent authorFor(final PlotCommit<SwingLane> c) {
 	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 +205,7 @@ static class DateCellRender extends DefaultTableCellRenderer {
 		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 +228,7 @@ static class GraphCellRender extends DefaultTableCellRenderer {
 
 		PlotCommit<SwingLane> commit;
 
+		@Override
 		@SuppressWarnings("unchecked")
 		public Component getTableCellRendererComponent(final JTable table,
 				final Object value, final boolean isSelected,
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java
index 7359093..fe0c486 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java
@@ -53,7 +53,7 @@ class SwingCommitList extends PlotCommitList<SwingCommitList.SwingLane> {
 	final LinkedList<Color> colors;
 
 	SwingCommitList() {
-		colors = new LinkedList<Color>();
+		colors = new LinkedList<>();
 		repackColors();
 	}
 
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index c74a500..9fa97b4 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,35 +3,8 @@
     <resource path="META-INF/MANIFEST.MF">
         <filter id="924844039">
             <message_arguments>
-                <message_argument value="4.6.2"/>
-                <message_argument value="4.6.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/errors/NoPackSignatureException.java" type="org.eclipse.jgit.errors.NoPackSignatureException">
-        <filter id="1108344834">
-            <message_arguments>
-                <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
-                <message_argument value="org.eclipse.jgit.errors.NoPackSignatureException"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java" type="org.eclipse.jgit.errors.UnsupportedPackIndexVersionException">
-        <filter id="1108344834">
-            <message_arguments>
-                <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
-                <message_argument value="org.eclipse.jgit.errors.UnsupportedPackIndexVersionException"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java" type="org.eclipse.jgit.errors.UnsupportedPackVersionException">
-        <filter id="1108344834">
-            <message_arguments>
-                <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
-                <message_argument value="org.eclipse.jgit.errors.UnsupportedPackVersionException"/>
+                <message_argument value="4.7.8"/>
+                <message_argument value="4.7.0"/>
             </message_arguments>
         </filter>
     </resource>
@@ -39,48 +12,60 @@
         <filter id="336658481">
             <message_arguments>
                 <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="CONFIG_KEY_AUTODETACH"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="CONFIG_KEY_LOGEXPIRY"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
                 <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
             </message_arguments>
         </filter>
         <filter id="1141899266">
             <message_arguments>
                 <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
+                <message_argument value="4.7"/>
                 <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/lib/GitmoduleEntry.java" type="org.eclipse.jgit.lib.GitmoduleEntry">
+        <filter id="1109393411">
+            <message_arguments>
+                <message_argument value="4.7.5"/>
+                <message_argument value="org.eclipse.jgit.lib.GitmoduleEntry"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/ObjectChecker.java" type="org.eclipse.jgit.lib.ObjectChecker">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="4.7.5"/>
+                <message_argument value="getGitsubmodules()"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
         <filter id="1141899266">
             <message_arguments>
                 <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
+                <message_argument value="4.7"/>
                 <message_argument value="createNewFile(File)"/>
             </message_arguments>
         </filter>
         <filter id="1141899266">
             <message_arguments>
                 <message_argument value="4.5"/>
-                <message_argument value="4.6"/>
+                <message_argument value="4.7"/>
                 <message_argument value="supportsAtomicCreateNewFile()"/>
             </message_arguments>
         </filter>
-        <filter id="924844039">
-            <message_arguments>
-                <message_argument value="4.5.6"/>
-                <message_argument value="4.5.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
-        <filter id="336658481">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
-                <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
         <filter id="1142947843">
             <message_arguments>
                 <message_argument value="4.5.6"/>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 4f1759f..06ddbab 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/org.eclipse.jgit/BUCK b/org.eclipse.jgit/BUCK
deleted file mode 100644
index 2bae6dc..0000000
--- a/org.eclipse.jgit/BUCK
+++ /dev/null
@@ -1,21 +0,0 @@
-SRCS = glob(['src/**'])
-RESOURCES = glob(['resources/**'])
-
-java_library(
-  name = 'jgit',
-  srcs = SRCS,
-  resources = RESOURCES,
-  deps = [
-    '//lib:javaewah',
-    '//lib:jsch',
-    '//lib:httpcomponents',
-    '//lib:servlet-api',
-    '//lib:slf4j-api',
-  ],
-  visibility = ['PUBLIC'],
-)
-
-java_sources(
-  name = 'jgit_src',
-  srcs = SRCS + RESOURCES,
-)
diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD
new file mode 100644
index 0000000..75f4fe6
--- /dev/null
+++ b/org.eclipse.jgit/BUILD
@@ -0,0 +1,31 @@
+package(default_visibility = ["//visibility:public"])
+
+INSECURE_CIPHER_FACTORY = [
+    "src/org/eclipse/jgit/transport/InsecureCipherFactory.java",
+]
+
+SRCS = glob(
+    ["src/**"],
+    exclude = INSECURE_CIPHER_FACTORY,
+)
+
+RESOURCES = glob(["resources/**"])
+
+java_library(
+    name = "jgit",
+    srcs = SRCS,
+    resource_strip_prefix = "org.eclipse.jgit/resources",
+    resources = RESOURCES,
+    deps = [
+        ":insecure_cipher_factory",
+        "//lib:javaewah",
+        "//lib:jsch",
+        "//lib:slf4j-api",
+    ],
+)
+
+java_library(
+    name = "insecure_cipher_factory",
+    srcs = INSECURE_CIPHER_FACTORY,
+    javacopts = ["-Xep:InsecureCryptoUsage:OFF"],
+)
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 701efcf..94cbb8b 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -2,12 +2,12 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 4.6.2.qualifier
+Bundle-Version: 4.7.8.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="4.6.2",
- org.eclipse.jgit.api;version="4.6.2";
+Export-Package: org.eclipse.jgit.annotations;version="4.7.8",
+ org.eclipse.jgit.api;version="4.7.8";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -21,47 +21,51 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="4.6.2",
- org.eclipse.jgit.blame;version="4.6.2";
+ org.eclipse.jgit.api.errors;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="4.7.8",
+ org.eclipse.jgit.blame;version="4.7.8";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="4.6.2";
+ org.eclipse.jgit.diff;version="4.7.8";
   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.6.2";
+ org.eclipse.jgit.dircache;version="4.7.8";
   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.6.2";
+ org.eclipse.jgit.errors;version="4.7.8";
   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.6.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="4.6.2",
- org.eclipse.jgit.gitrepo;version="4.6.2";
+ org.eclipse.jgit.events;version="4.7.8";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="4.7.8",
+ org.eclipse.jgit.gitrepo;version="4.7.8";
   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.6.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.6.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="4.6.2",
- org.eclipse.jgit.ignore.internal;version="4.6.2";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="4.6.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.ketch;version="4.6.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="4.6.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.storage.file;version="4.6.2";
+ org.eclipse.jgit.gitrepo.internal;version="4.7.8";x-internal:=true,
+ org.eclipse.jgit.hooks;version="4.7.8";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="4.7.8",
+ org.eclipse.jgit.ignore.internal;version="4.7.8";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="4.7.8";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.ketch;version="4.7.8";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.dfs;version="4.7.8";
+  x-friends:="org.eclipse.jgit.test,
+   org.eclipse.jgit.http.server,
+   org.eclipse.jgit.http.test,
+   org.eclipse.jgit.lfs.test",
+ org.eclipse.jgit.internal.storage.file;version="4.7.8";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -69,9 +73,9 @@
    org.eclipse.jgit.lfs,
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.pack;version="4.6.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="4.6.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="4.6.2";
+ org.eclipse.jgit.internal.storage.pack;version="4.7.8";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="4.7.8";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="4.7.8";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -81,32 +85,32 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.merge;version="4.6.2";
+ org.eclipse.jgit.merge;version="4.7.8";
   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.6.2",
- org.eclipse.jgit.notes;version="4.6.2";
+ org.eclipse.jgit.nls;version="4.7.8",
+ org.eclipse.jgit.notes;version="4.7.8";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="4.6.2";
+ org.eclipse.jgit.patch;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="4.7.8";
   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.6.2";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.6.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="4.6.2";
+ org.eclipse.jgit.revwalk.filter;version="4.7.8";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.7.8";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="4.7.8";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -118,29 +122,29 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.6.2";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.6.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="4.6.2";
+ org.eclipse.jgit.transport.http;version="4.7.8";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.7.8";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="4.7.8";
   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.6.2";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="4.6.2";
+ org.eclipse.jgit.treewalk.filter;version="4.7.8";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="4.7.8";
   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.6.2",
- org.eclipse.jgit.util.time;version="4.6.2"
+ org.eclipse.jgit.util.io;version="4.7.8",
+ org.eclipse.jgit.util.sha1;version="4.7.8",
+ org.eclipse.jgit.util.time;version="4.7.8"
 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,
- javax.servlet.http;version="[2.5.0,3.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)",
  org.xml.sax,
  org.xml.sax.helpers
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index c863b04..f130a77 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.6.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="4.6.2.qualifier";roots="."
+Bundle-Version: 4.7.8.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="4.7.8.qualifier";roots="."
diff --git a/org.eclipse.jgit/about.html b/org.eclipse.jgit/about.html
index 01a2671..f971af1 100644
--- a/org.eclipse.jgit/about.html
+++ b/org.eclipse.jgit/about.html
@@ -21,6 +21,10 @@
     margin-top:  0.05em;
     margin-bottom: 0.05em;
     }
+  .ubc-name {
+    margin-left: 0.5in;
+    white-space: pre;
+  }
   </style>
 
 </head>
@@ -54,6 +58,39 @@
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.</p>
 
+<hr>
+<p><b>SHA-1 UbcCheck - MIT</b></p>
+
+<p>Copyright (c) 2017:</p>
+<div class="ubc-name">
+Marc Stevens
+Cryptology Group
+Centrum Wiskunde & Informatica
+P.O. Box 94079, 1090 GB Amsterdam, Netherlands
+marc@marc-stevens.nl
+</div>
+<div class="ubc-name">
+Dan Shumow
+Microsoft Research
+danshu@microsoft.com
+</div>
+<p>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:
+</p>
+<ul><li>The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.</li></ul>
+<p>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.</p>
+
 </body>
 
 </html>
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 4e31e0b..b932b79 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -53,7 +53,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.6.2-SNAPSHOT</version>
+    <version>4.7.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
@@ -89,11 +89,6 @@
       <artifactId>slf4j-api</artifactId>
     </dependency>
 
-    <dependency>
-      <groupId>javax.servlet</groupId>
-      <artifactId>javax.servlet-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
   </dependencies>
 
   <build>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
index 7443ad3..e942038 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
@@ -3,6 +3,7 @@
 errorIncludeNotImplemented=Error: <include> 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 --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index cde45cf..55e786c 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -31,6 +31,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
@@ -44,6 +45,7 @@
 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}.
@@ -76,7 +78,7 @@
 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}
@@ -117,6 +119,7 @@
 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}
 commandRejectedByHook=Rejected by "{0}" hook.\n{1}
 commandWasCalledInTheWrongState=Command {0} was called in the wrong state
@@ -188,7 +191,7 @@
 corruptObjectTruncatedInName=truncated in name
 corruptObjectTruncatedInObjectId=truncated in object id
 corruptObjectZeroId=entry points to null SHA-1
-corruptUseCnt=close() called when useCnt is already zero
+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
@@ -282,6 +285,7 @@
 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:
@@ -299,6 +303,8 @@
 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
@@ -341,6 +347,7 @@
 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
@@ -352,6 +359,7 @@
 invalidLineInConfigFile=Invalid line in config file
 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}
@@ -564,6 +572,7 @@
 sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm.
 serviceNotEnabledNoName=Service not enabled
 serviceNotPermitted={0} not permitted
+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}
@@ -598,7 +607,10 @@
 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.
@@ -609,6 +621,7 @@
 theFactoryMustNotBeNull=The factory must not be null
 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
@@ -639,6 +652,7 @@
 tSizeMustBeGreaterOrEqual1=tSize must be >= 1
 unableToCheckConnectivity=Unable to check connectivity.
 unableToCreateNewObject=Unable to create new object: {0}
+unableToRemovePath=Unable to remove path ''{0}''
 unableToStore=Unable to store {0}.
 unableToWrite=Unable to write {0}
 unauthorized=Unauthorized
@@ -661,6 +675,7 @@
 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
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress b/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress
new file mode 100644
index 0000000..3a80fd2
--- /dev/null
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.compress
@@ -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 --git a/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress b/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress
new file mode 100644
index 0000000..44d3d6d
--- /dev/null
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/util/sha1/SHA1.recompress
@@ -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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index 16ec146..1ed7944 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -96,7 +96,7 @@ public class AddCommand extends GitCommand<DirCache> {
 	 */
 	public AddCommand(Repository repo) {
 		super(repo);
-		filepatterns = new LinkedList<String>();
+		filepatterns = new LinkedList<>();
 	}
 
 	/**
@@ -134,6 +134,7 @@ public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
 	 *
 	 * @return the DirCache after Add
 	 */
+	@Override
 	public DirCache call() throws GitAPIException, NoFilepatternException {
 
 		if (filepatterns.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
index 4235e37..fa88fb7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
@@ -81,6 +81,7 @@ protected AddNoteCommand(Repository repo) {
 		super(repo);
 	}
 
+	@Override
 	public Note call() throws GitAPIException {
 		checkCallable();
 		NoteMap map = NoteMap.newEmptyMap();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index d74e991..ba5673d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -109,6 +109,7 @@ public ApplyCommand setPatch(InputStream in) {
 	 * @throws PatchFormatException
 	 * @throws PatchApplyException
 	 */
+	@Override
 	public ApplyResult call() throws GitAPIException, PatchFormatException,
 			PatchApplyException {
 		checkCallable();
@@ -197,10 +198,10 @@ private File getFile(String path, boolean create)
 	private void apply(File f, FileHeader fh)
 			throws IOException, PatchApplyException {
 		RawText rt = new RawText(f);
-		List<String> oldLines = new ArrayList<String>(rt.size());
+		List<String> oldLines = new ArrayList<>(rt.size());
 		for (int i = 0; i < rt.size(); i++)
 			oldLines.add(rt.getString(i));
-		List<String> newLines = new ArrayList<String>(oldLines);
+		List<String> newLines = new ArrayList<>(oldLines);
 		for (HunkHeader hh : fh.getHunks()) {
 
 			byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()];
@@ -208,7 +209,7 @@ private void apply(File f, FileHeader fh)
 					b.length);
 			RawText hrt = new RawText(b);
 
-			List<String> hunkLines = new ArrayList<String>(hrt.size());
+			List<String> hunkLines = new ArrayList<>(hrt.size());
 			for (int i = 0; i < hrt.size(); i++)
 				hunkLines.add(hrt.getString(i));
 			int pos = 0;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java
index 558ef0f..2ef6650 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyResult.java
@@ -53,7 +53,7 @@
  */
 public class ApplyResult {
 
-	private List<File> updatedFiles = new ArrayList<File>();
+	private List<File> updatedFiles = new ArrayList<>();
 
 	/**
 	 * @param f
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
index 8543bd5..7ea8e73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
@@ -162,8 +162,8 @@ T createArchiveOutputStream(OutputStream s, Map<String, Object> o)
 		 * @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 +171,36 @@ T createArchiveOutputStream(OutputStream s, Map<String, Object> o)
 		 *            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 +259,7 @@ public FormatEntry(Format<?> format, int refcnt) {
 	 * the --format= option)
 	 */
 	private static final ConcurrentMap<String, FormatEntry> formats =
-			new ConcurrentHashMap<String, FormatEntry>();
+			new ConcurrentHashMap<>();
 
 	/**
 	 * Replaces the entry for a key only if currently mapped to a given
@@ -350,7 +377,7 @@ private static Format<?> lookupFormat(String formatName) throws UnsupportedForma
 	private String prefix;
 	private String format;
 	private Map<String, Object> formatOptions = new HashMap<>();
-	private List<String> paths = new ArrayList<String>();
+	private List<String> paths = new ArrayList<>();
 
 	/** Filename suffix, for automatically choosing a format. */
 	private String suffix;
@@ -376,6 +403,12 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 				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,11 +422,11 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 						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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
index 2a2e07d..b1c81ff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
@@ -188,7 +188,7 @@ public BlameCommand reverse(AnyObjectId start, AnyObjectId end)
 	public BlameCommand reverse(AnyObjectId start, Collection<ObjectId> end)
 			throws IOException {
 		startCommit = start.toObjectId();
-		reverseEndCommits = new ArrayList<ObjectId>(end);
+		reverseEndCommits = new ArrayList<>(end);
 		return this;
 	}
 
@@ -198,6 +198,7 @@ public BlameCommand reverse(AnyObjectId start, Collection<ObjectId> end)
 	 *
 	 * @return list of lines
 	 */
+	@Override
 	public BlameResult call() throws GitAPIException {
 		checkCallable();
 		try (BlameGenerator gen = new BlameGenerator(repo, path)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index c17ae5c..21d6283 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -180,7 +180,7 @@ private Stage(int number) {
 	 */
 	protected CheckoutCommand(Repository repo) {
 		super(repo);
-		this.paths = new LinkedList<String>();
+		this.paths = new LinkedList<>();
 	}
 
 	/**
@@ -196,6 +196,7 @@ protected CheckoutCommand(Repository repo) {
 	 *             if the checkout results in a conflict
 	 * @return the newly created branch
 	 */
+	@Override
 	public Ref call() throws GitAPIException, RefAlreadyExistsException,
 			RefNotFoundException, InvalidRefNameException,
 			CheckoutConflictException {
@@ -319,10 +320,10 @@ else if (orphan) {
 			if (!dco.getToBeDeleted().isEmpty()) {
 				status = new CheckoutResult(Status.NONDELETED,
 						dco.getToBeDeleted(),
-						new ArrayList<String>(dco.getUpdated().keySet()),
+						new ArrayList<>(dco.getUpdated().keySet()),
 						dco.getRemoved());
 			} else
-				status = new CheckoutResult(new ArrayList<String>(dco
+				status = new CheckoutResult(new ArrayList<>(dco
 						.getUpdated().keySet()), dco.getRemoved());
 
 			return ref;
@@ -455,6 +456,7 @@ private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
 			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) {
@@ -491,6 +493,7 @@ private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
 			final String filterCommand = treeWalk
 					.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
 			editor.add(new PathEdit(treeWalk.getPathString()) {
+				@Override
 				public void apply(DirCacheEntry ent) {
 					ent.setObjectId(blobId);
 					ent.setFileMode(mode);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java
index 92a67f4..2186eb4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutResult.java
@@ -139,11 +139,11 @@ public enum Status {
 		if (status == Status.CONFLICTS)
 			this.conflictList = fileList;
 		else
-			this.conflictList = new ArrayList<String>(0);
+			this.conflictList = new ArrayList<>(0);
 		if (status == Status.NONDELETED)
 			this.undeletedList = fileList;
 		else
-			this.undeletedList = new ArrayList<String>(0);
+			this.undeletedList = new ArrayList<>(0);
 
 		this.modifiedList = modified;
 		this.removedList = removed;
@@ -160,8 +160,8 @@ public enum Status {
 	CheckoutResult(List<String> modified, List<String> removed) {
 		myStatus = Status.OK;
 
-		this.conflictList = new ArrayList<String>(0);
-		this.undeletedList = new ArrayList<String>(0);
+		this.conflictList = new ArrayList<>(0);
+		this.undeletedList = new ArrayList<>(0);
 
 		this.modifiedList = modified;
 		this.removedList = removed;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 276bf96..eed7b2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -85,7 +85,7 @@
 public class CherryPickCommand extends GitCommand<CherryPickResult> {
 	private String reflogPrefix = "cherry-pick:"; //$NON-NLS-1$
 
-	private List<Ref> commits = new LinkedList<Ref>();
+	private List<Ref> commits = new LinkedList<>();
 
 	private String ourCommitName = null;
 
@@ -116,11 +116,12 @@ protected CherryPickCommand(Repository repo) {
 	 * @throws NoMessageException
 	 * @throws NoHeadException
 	 */
+	@Override
 	public CherryPickResult call() throws GitAPIException, NoMessageException,
 			UnmergedPathsException, ConcurrentRefUpdateException,
 			WrongRepositoryStateException, NoHeadException {
 		RevCommit newHead = null;
-		List<Ref> cherryPickedRefs = new LinkedList<Ref>();
+		List<Ref> cherryPickedRefs = new LinkedList<>();
 		checkCallable();
 
 		try (RevWalk revWalk = new RevWalk(repo)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 7e331fd..c58efb1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -94,15 +94,16 @@ protected CleanCommand(Repository repo) {
 	 * @throws GitAPIException
 	 * @throws NoWorkTreeException
 	 */
+	@Override
 	public Set<String> call() throws NoWorkTreeException, GitAPIException {
-		Set<String> files = new TreeSet<String>();
+		Set<String> files = new TreeSet<>();
 		try {
 			StatusCommand command = new StatusCommand(repo);
 			Status status = command.call();
 
-			Set<String> untrackedAndIgnoredFiles = new TreeSet<String>(
+			Set<String> untrackedAndIgnoredFiles = new TreeSet<>(
 					status.getUntracked());
-			Set<String> untrackedAndIgnoredDirs = new TreeSet<String>(
+			Set<String> untrackedAndIgnoredDirs = new TreeSet<>(
 					status.getUntrackedFolders());
 
 			FS fs = getRepository().getFS();
@@ -191,7 +192,7 @@ private Set<String> cleanPath(String path, Set<String> inFiles)
 	private Set<String> filterIgnorePaths(Set<String> inputPaths,
 			Set<String> ignoredNotInIndex, boolean exact) {
 		if (ignore) {
-			Set<String> filtered = new TreeSet<String>(inputPaths);
+			Set<String> filtered = new TreeSet<>(inputPaths);
 			for (String path : inputPaths)
 				for (String ignored : ignoredNotInIndex)
 					if ((exact && path.equals(ignored))
@@ -207,7 +208,7 @@ private Set<String> filterIgnorePaths(Set<String> inputPaths,
 
 	private Set<String> filterFolders(Set<String> untracked,
 			Set<String> untrackedFolders) {
-		Set<String> filtered = new TreeSet<String>(untracked);
+		Set<String> filtered = new TreeSet<>(untracked);
 		for (String file : untracked)
 			for (String folder : untrackedFolders)
 				if (file.startsWith(folder)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index dd5da15..4b815b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -127,6 +127,7 @@ public CloneCommand() {
 	 * @throws org.eclipse.jgit.api.errors.TransportException
 	 * @throws GitAPIException
 	 */
+	@Override
 	public Git call() throws GitAPIException, InvalidRemoteException,
 			org.eclipse.jgit.api.errors.TransportException {
 		Repository repository = null;
@@ -151,23 +152,35 @@ public Git call() throws GitAPIException, InvalidRemoteException,
 		}
 	}
 
+	private static boolean isNonEmptyDirectory(File dir) {
+		if (dir != null && dir.exists()) {
+			File[] files = dir.listFiles();
+			return files != null && files.length != 0;
+		}
+		return false;
+	}
+
 	private Repository init(URIish u) throws GitAPIException {
 		InitCommand command = Git.init();
 		command.setBare(bare);
-		if (directory == null && gitDir == null)
+		if (directory == null && gitDir == null) {
 			directory = new File(u.getHumanishName(), Constants.DOT_GIT);
+		}
 		validateDirs(directory, gitDir, bare);
-		if (directory != null && directory.exists()
-				&& directory.listFiles().length != 0)
+		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)
+		}
+		if (directory != null) {
 			command.setDirectory(directory);
-		if (gitDir != null)
+		}
+		if (gitDir != null) {
 			command.setGitDir(gitDir);
+		}
 		return command.call().getRepository();
 	}
 
@@ -207,7 +220,7 @@ private List<RefSpec> calculateRefSpecs(final String dst) {
 		RefSpec wcrs = new RefSpec();
 		wcrs = wcrs.setForceUpdate(true);
 		wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$
-		List<RefSpec> specs = new ArrayList<RefSpec>();
+		List<RefSpec> specs = new ArrayList<>();
 		if (cloneAllBranches)
 			specs.add(wcrs);
 		else if (branchesToClone != null
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index e1793f3..274ece6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -112,7 +112,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
 
 	private boolean all;
 
-	private List<String> only = new ArrayList<String>();
+	private List<String> only = new ArrayList<>();
 
 	private boolean[] onlyProcessed;
 
@@ -124,7 +124,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
 	 * parents this commit should have. The current HEAD will be in this list
 	 * and also all commits mentioned in .git/MERGE_HEAD
 	 */
-	private List<ObjectId> parents = new LinkedList<ObjectId>();
+	private List<ObjectId> parents = new LinkedList<>();
 
 	private String reflogComment;
 
@@ -168,6 +168,7 @@ protected CommitCommand(Repository repo) {
 	 *             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,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
index 69d82bc..39420d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
@@ -120,6 +120,7 @@ protected CreateBranchCommand(Repository repo) {
 	 *             invalid
 	 * @return the newly created branch
 	 */
+	@Override
 	public Ref call() throws GitAPIException, RefAlreadyExistsException,
 			RefNotFoundException, InvalidRefNameException {
 		checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
index ecc1ce5..d7e7e5c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
@@ -79,7 +79,7 @@
  *      >Git documentation about Branch</a>
  */
 public class DeleteBranchCommand extends GitCommand<List<String>> {
-	private final Set<String> branchNames = new HashSet<String>();
+	private final Set<String> branchNames = new HashSet<>();
 
 	private boolean force;
 
@@ -97,10 +97,11 @@ protected DeleteBranchCommand(Repository repo) {
 	 * @throws CannotDeleteCurrentBranchException
 	 * @return the list with the (full) names of the deleted branches
 	 */
+	@Override
 	public List<String> call() throws GitAPIException,
 			NotMergedException, CannotDeleteCurrentBranchException {
 		checkCallable();
-		List<String> result = new ArrayList<String>();
+		List<String> result = new ArrayList<>();
 		if (branchNames.isEmpty())
 			return result;
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
index 3aeec48..77e3539 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
@@ -68,7 +68,7 @@
  */
 public class DeleteTagCommand extends GitCommand<List<String>> {
 
-	private final Set<String> tags = new HashSet<String>();
+	private final Set<String> tags = new HashSet<>();
 
 	/**
 	 * @param repo
@@ -80,9 +80,10 @@ protected DeleteTagCommand(Repository repo) {
 	/**
 	 * @return the list with the full names of the deleted tags
 	 */
+	@Override
 	public List<String> call() throws GitAPIException {
 		checkCallable();
-		List<String> result = new ArrayList<String>();
+		List<String> result = new ArrayList<>();
 		if (tags.isEmpty())
 			return result;
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index be45666..389c511 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -189,7 +189,7 @@ public String call() throws GitAPIException {
 			if (target == null)
 				setTarget(Constants.HEAD);
 
-			Map<ObjectId, Ref> tags = new HashMap<ObjectId, Ref>();
+			Map<ObjectId, Ref> tags = new HashMap<>();
 
 			for (Ref r : repo.getRefDatabase().getRefs(R_TAGS).values()) {
 				ObjectId key = repo.peel(r).getPeeledObjectId();
@@ -240,7 +240,7 @@ String describe(ObjectId tip) throws IOException {
 				}
 
 			}
-			List<Candidate> candidates = new ArrayList<Candidate>();    // all the candidates we find
+			List<Candidate> 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);
@@ -305,6 +305,7 @@ String describe(ObjectId tip) throws IOException {
 				return null;
 
 			Candidate best = Collections.min(candidates, new Comparator<Candidate>() {
+				@Override
 				public int compare(Candidate o1, Candidate o2) {
 					return o1.depth - o2.depth;
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
index 3e3a7a8..b137fc5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
@@ -109,6 +109,7 @@ protected DiffCommand(Repository repo) {
 	 *
 	 * @return a DiffEntry for each path which is different
 	 */
+	@Override
 	public List<DiffEntry> call() throws GitAPIException {
 		final DiffFormatter diffFmt;
 		if (out != null && !showNameAndStatusOnly)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index de51276..cc3302b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -42,14 +42,17 @@
  */
 package org.eclipse.jgit.api;
 
+import java.io.IOException;
 import java.net.URISyntaxException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
 
 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 +60,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 +81,6 @@
  *      >Git documentation about Fetch</a>
  */
 public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
-
 	private String remote = Constants.DEFAULT_REMOTE_NAME;
 
 	private List<RefSpec> refSpecs;
@@ -91,12 +97,90 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
 
 	private TagOpt tagOption;
 
+	private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
+
 	/**
 	 * @param repo
 	 */
 	protected FetchCommand(Repository repo) {
 		super(repo);
-		refSpecs = new ArrayList<RefSpec>(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()) {
+				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);
+					results.addSubmodule(walk.getPath(), f.call());
+				}
+			}
+		} catch (IOException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		} catch (ConfigInvalidException e) {
+			throw new InvalidConfigurationException(e.getMessage(), e);
+		}
 	}
 
 	/**
@@ -112,6 +196,7 @@ protected FetchCommand(Repository repo) {
 	 * @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();
@@ -126,6 +211,10 @@ public FetchResult call() throws GitAPIException, InvalidRemoteException,
 			configure(transport);
 
 			FetchResult result = transport.fetch(monitor, refSpecs);
+			if (!repo.isBare()) {
+				fetchSubmodules(result);
+			}
+
 			return result;
 		} catch (NoRemoteRepositoryException e) {
 			throw new InvalidRemoteException(MessageFormat.format(
@@ -145,6 +234,20 @@ public FetchResult call() throws GitAPIException, InvalidRemoteException,
 	}
 
 	/**
+	 * Set the mode to be used for recursing into submodules.
+	 *
+	 * @param recurse
+	 * @return {@code this}
+	 * @since 4.7
+	 */
+	public FetchCommand setRecurseSubmodules(
+			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 <code>Constants.DEFAULT_REMOTE_NAME</code> will
 	 * be used.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
index d0f729c..0f38db5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -159,6 +159,38 @@ public GarbageCollectCommand setAggressive(boolean aggressive) {
 		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;
+	}
+
 	@Override
 	public Properties call() throws GitAPIException {
 		checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 2cd5f59..9699569 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -147,6 +147,7 @@ public static Git wrap(Repository repo) {
 	 *
 	 * @since 3.2
 	 */
+	@Override
 	public void close() {
 		if (closeRepo)
 			repo.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java
index e9751f9..2a23408 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GitCommand.java
@@ -126,5 +126,6 @@ protected void checkCallable() {
 	 * @throws GitAPIException
 	 *             or subclass thereof when an error occurs
 	 */
+	@Override
 	public abstract T call() throws GitAPIException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
index 37a788e..649484c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
@@ -73,6 +73,7 @@ public class InitCommand implements Callable<Git> {
 	 *
 	 * @return the newly created {@code Git} object with associated repository
 	 */
+	@Override
 	public Git call() throws GitAPIException {
 		try {
 			RepositoryBuilder builder = new RepositoryBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
index efcfd16..961eeaa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
@@ -101,11 +101,12 @@ protected ListBranchCommand(Repository repo) {
 		super(repo);
 	}
 
+	@Override
 	public List<Ref> call() throws GitAPIException {
 		checkCallable();
 		List<Ref> resultRefs;
 		try {
-			Collection<Ref> refs = new ArrayList<Ref>();
+			Collection<Ref> refs = new ArrayList<>();
 
 			// Also return HEAD if it's detached
 			Ref head = repo.exactRef(Constants.HEAD);
@@ -120,12 +121,13 @@ public List<Ref> call() throws GitAPIException {
 				refs.addAll(getRefs(Constants.R_HEADS));
 				refs.addAll(getRefs(Constants.R_REMOTES));
 			}
-			resultRefs = new ArrayList<Ref>(filterRefs(refs));
+			resultRefs = new ArrayList<>(filterRefs(refs));
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 
 		Collections.sort(resultRefs, new Comparator<Ref>() {
+			@Override
 			public int compare(Ref o1, Ref o2) {
 				return o1.getName().compareTo(o2.getName());
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
index ff963ed..476c10b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
@@ -77,9 +77,10 @@ protected ListNotesCommand(Repository repo) {
 	/**
 	 * @return the requested notes
 	 */
+	@Override
 	public List<Note> call() throws GitAPIException {
 		checkCallable();
-		List<Note> notes = new ArrayList<Note>();
+		List<Note> notes = new ArrayList<>();
 		NoteMap map = NoteMap.newEmptyMap();
 		try (RevWalk walk = new RevWalk(repo)) {
 			Ref ref = repo.findRef(notesRef);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
index a3b701b..d649a53 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
@@ -74,10 +74,11 @@ protected ListTagCommand(Repository repo) {
 	/**
 	 * @return the tags available
 	 */
+	@Override
 	public List<Ref> call() throws GitAPIException {
 		checkCallable();
 		Map<String, Ref> refList;
-		List<Ref> tags = new ArrayList<Ref>();
+		List<Ref> tags = new ArrayList<>();
 		try (RevWalk revWalk = new RevWalk(repo)) {
 			refList = repo.getRefDatabase().getRefs(Constants.R_TAGS);
 			for (Ref ref : refList.values()) {
@@ -87,6 +88,7 @@ public List<Ref> call() throws GitAPIException {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 		Collections.sort(tags, new Comparator<Ref>() {
+			@Override
 			public int compare(Ref o1, Ref o2) {
 				return o1.getName().compareTo(o2.getName());
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
index bb1a738..f8fe95a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
@@ -107,7 +107,7 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
 
 	private RevFilter revFilter;
 
-	private final List<PathFilter> pathFilters = new ArrayList<PathFilter>();
+	private final List<PathFilter> pathFilters = new ArrayList<>();
 
 	private int maxCount = -1;
 
@@ -132,6 +132,7 @@ protected LogCommand(Repository repo) {
 	 * @throws NoHeadException
 	 *             of the references ref cannot be resolved
 	 */
+	@Override
 	public Iterable<RevCommit> call() throws GitAPIException, NoHeadException {
 		checkCallable();
 		if (pathFilters.size() > 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
index f3527fd..5157a41 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
@@ -153,6 +153,7 @@ public LsRemoteCommand setUploadPack(String uploadPack) {
 	 * @throws org.eclipse.jgit.api.errors.TransportException
 	 *             for errors that occurs during transport
 	 */
+	@Override
 	public Collection<Ref> call() throws GitAPIException,
 			InvalidRemoteException,
 			org.eclipse.jgit.api.errors.TransportException {
@@ -187,14 +188,14 @@ private Map<String, Ref> execute() throws GitAPIException,
 				: Transport.open(new URIish(remote))) {
 			transport.setOptionUploadPack(uploadPack);
 			configure(transport);
-			Collection<RefSpec> refSpecs = new ArrayList<RefSpec>(1);
+			Collection<RefSpec> 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<Ref> refs;
-			Map<String, Ref> refmap = new HashMap<String, Ref>();
+			Map<String, Ref> refmap = new HashMap<>();
 			try (FetchConnection fc = transport.openFetch()) {
 				refs = fc.getRefs();
 				if (refSpecs.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index ced1863..b5d9e8a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -50,6 +50,7 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import org.eclipse.jgit.api.MergeResult.MergeStatus;
@@ -101,7 +102,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
 
 	private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
 
-	private List<Ref> commits = new LinkedList<Ref>();
+	private List<Ref> commits = new LinkedList<>();
 
 	private Boolean squash;
 
@@ -133,10 +134,12 @@ public enum FastForwardMode implements ConfigEnum {
 		 */
 		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;
@@ -220,6 +223,7 @@ protected MergeCommand(Repository repo) {
 	 *
 	 * @return the result of the merge
 	 */
+	@Override
 	@SuppressWarnings("boxing")
 	public MergeResult call() throws GitAPIException, NoHeadException,
 			ConcurrentRefUpdateException, CheckoutConflictException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
index 6141e0c..c487ef6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
@@ -185,6 +185,7 @@ public boolean isSuccessful() {
 		 * @since 3.0
 		 **/
 		MERGED_NOT_COMMITTED {
+			@Override
 			public String toString() {
 				return "Merged-not-committed"; //$NON-NLS-1$
 			}
@@ -212,6 +213,7 @@ public boolean isSuccessful() {
 		 * files (i.e. local modifications prevent checkout of files).
 		 */
 		CHECKOUT_CONFLICT {
+			@Override
 			public String toString() {
 				return "Checkout Conflict"; //$NON-NLS-1$
 			}
@@ -414,7 +416,7 @@ public void setConflicts(Map<String, int[][]> conflicts) {
 	 */
 	public void addConflict(String path, int[][] conflictingRanges) {
 		if (conflicts == null)
-			conflicts = new HashMap<String, int[][]>();
+			conflicts = new HashMap<>();
 		conflicts.put(path, conflictingRanges);
 	}
 
@@ -426,7 +428,7 @@ public void addConflict(String path, org.eclipse.jgit.merge.MergeResult<?> lowLe
 		if (!lowLevelResult.containsConflicts())
 			return;
 		if (conflicts == null)
-			conflicts = new HashMap<String, int[][]>();
+			conflicts = new HashMap<>();
 		int nrOfConflicts = 0;
 		// just counting
 		for (MergeChunk mergeChunk : lowLevelResult) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
index fd28d0e..2a86fab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
@@ -124,8 +124,8 @@ public String toString() {
 	protected NameRevCommand(Repository repo) {
 		super(repo);
 		mergeCost = MERGE_COST;
-		prefixes = new ArrayList<String>(2);
-		revs = new ArrayList<ObjectId>(2);
+		prefixes = new ArrayList<>(2);
+		revs = new ArrayList<>(2);
 		walk = new RevWalk(repo) {
 			@Override
 			public NameRevCommit createCommit(AnyObjectId id) {
@@ -137,7 +137,7 @@ public NameRevCommit createCommit(AnyObjectId id) {
 	@Override
 	public Map<ObjectId, String> call() throws GitAPIException {
 		try {
-			Map<ObjectId, String> nonCommits = new HashMap<ObjectId, String>();
+			Map<ObjectId, String> nonCommits = new HashMap<>();
 			FIFORevQueue pending = new FIFORevQueue();
 			if (refs != null) {
 				for (Ref ref : refs)
@@ -170,7 +170,7 @@ public Map<ObjectId, String> call() throws GitAPIException {
 			}
 
 			Map<ObjectId, String> result =
-				new LinkedHashMap<ObjectId, String>(revs.size());
+				new LinkedHashMap<>(revs.size());
 			for (ObjectId id : revs) {
 				RevObject o = walk.parseAny(id);
 				if (o instanceof NameRevCommit) {
@@ -275,7 +275,7 @@ public NameRevCommand addPrefix(String prefix) {
 	public NameRevCommand addAnnotatedTags() {
 		checkCallable();
 		if (refs == null)
-			refs = new ArrayList<Ref>();
+			refs = new ArrayList<>();
 		try {
 			for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_TAGS).values()) {
 				ObjectId id = ref.getObjectId();
@@ -302,7 +302,7 @@ public NameRevCommand addAnnotatedTags() {
 	public NameRevCommand addRef(Ref ref) {
 		checkCallable();
 		if (refs == null)
-			refs = new ArrayList<Ref>();
+			refs = new ArrayList<>();
 		refs.add(ref);
 		return this;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index a4d9ec1..ae822da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -69,8 +69,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
@@ -92,6 +94,10 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
 
 	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
 
+	private TagOpt tagOption;
+
+	private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
+
 	/**
 	 * @param repo
 	 */
@@ -194,6 +200,7 @@ public PullCommand setRebase(BranchRebaseMode rebaseMode) {
 	 * @throws org.eclipse.jgit.api.errors.TransportException
 	 * @throws GitAPIException
 	 */
+	@Override
 	public PullResult call() throws GitAPIException,
 			WrongRepositoryStateException, InvalidConfigurationException,
 			DetachedHeadException, InvalidRemoteException, CanceledException,
@@ -272,9 +279,9 @@ public PullResult call() throws GitAPIException,
 						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();
@@ -412,6 +419,32 @@ public PullCommand setStrategy(MergeStrategy strategy) {
 	}
 
 	/**
+	 * Sets the specification of annotated tag behavior during fetch
+	 *
+	 * @param tagOpt
+	 * @return {@code this}
+	 * @since 4.7
+	 */
+	public PullCommand setTagOpt(TagOpt tagOpt) {
+		checkCallable();
+		this.tagOption = tagOpt;
+		return this;
+	}
+
+	/**
+	 * Set the mode to be used for recursing into submodules.
+	 *
+	 * @param recurse
+	 * @return {@code this}
+	 * @since 4.7
+	 */
+	public PullCommand setRecurseSubmodules(
+			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}.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index bd4521b..bf88842 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -47,9 +47,12 @@
 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;
@@ -65,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;
@@ -85,6 +89,8 @@ public class PushCommand extends
 
 	private final List<RefSpec> refSpecs;
 
+	private final Map<String, RefLeaseSpec> refLeaseSpecs;
+
 	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
 
 	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
@@ -103,7 +109,8 @@ public class PushCommand extends
 	 */
 	protected PushCommand(Repository repo) {
 		super(repo);
-		refSpecs = new ArrayList<RefSpec>(3);
+		refSpecs = new ArrayList<>(3);
+		refLeaseSpecs = new HashMap<>();
 	}
 
 	/**
@@ -119,12 +126,13 @@ protected PushCommand(Repository repo) {
 	 *             when an error occurs with the transport
 	 * @throws GitAPIException
 	 */
+	@Override
 	public Iterable<PushResult> call() throws GitAPIException,
 			InvalidRemoteException,
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
 
-		ArrayList<PushResult> pushResults = new ArrayList<PushResult>(3);
+		ArrayList<PushResult> pushResults = new ArrayList<>(3);
 
 		try {
 			if (refSpecs.isEmpty()) {
@@ -155,7 +163,7 @@ public Iterable<PushResult> call() throws GitAPIException,
 				configure(transport);
 
 				final Collection<RemoteRefUpdate> toPush = transport
-						.findRemoteRefUpdatesFor(refSpecs);
+						.findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs);
 
 				try {
 					PushResult result = transport.push(monitor, toPush, out);
@@ -271,6 +279,43 @@ public PushCommand setProgressMonitor(ProgressMonitor monitor) {
 	}
 
 	/**
+	 * @return the ref lease specs
+	 * @since 4.7
+	 */
+	public List<RefLeaseSpec> 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
+	 * @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
+	 * @return {@code this}
+	 * @since 4.7
+	 */
+	public PushCommand setRefLeaseSpecs(List<RefLeaseSpec> specs) {
+		checkCallable();
+		this.refLeaseSpecs.clear();
+		for (RefLeaseSpec spec : specs) {
+			refLeaseSpecs.put(spec.getRef(), spec);
+		}
+		return this;
+	}
+
+	/**
 	 * @return the ref specs
 	 */
 	public List<RefSpec> getRefSpecs() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index d10cc3d..850ff49 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -258,6 +258,7 @@ protected RebaseCommand(Repository repo) {
 	 * @throws NoHeadException
 	 * @throws RefNotFoundException
 	 */
+	@Override
 	public RebaseResult call() throws GitAPIException, NoHeadException,
 			RefNotFoundException, WrongRepositoryStateException {
 		newHead = null;
@@ -298,7 +299,7 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 					org.eclipse.jgit.api.Status status = Git.wrap(repo)
 							.status().setIgnoreSubmodules(IgnoreSubmoduleMode.ALL).call();
 					if (status.hasUncommittedChanges()) {
-						List<String> list = new ArrayList<String>();
+						List<String> list = new ArrayList<>();
 						list.addAll(status.getUncommittedChanges());
 						return RebaseResult.uncommittedChanges(list);
 					}
@@ -649,7 +650,7 @@ private void writeMergeInfo(RevCommit commitToPick,
 	// Get the rewritten equivalents for the parents of the given commit
 	private List<RevCommit> getNewParents(RevCommit commitToPick)
 			throws IOException {
-		List<RevCommit> newParents = new ArrayList<RevCommit>();
+		List<RevCommit> newParents = new ArrayList<>();
 		for (int p = 0; p < commitToPick.getParentCount(); p++) {
 			String parentHash = commitToPick.getParent(p).getName();
 			if (!new File(rebaseState.getRewrittenDir(), parentHash).exists())
@@ -805,8 +806,12 @@ private static String stripCommentLines(String commitMessage) {
 			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();
 	}
 
@@ -1055,8 +1060,8 @@ String toAuthorScript(PersonIdent author) {
 	private void popSteps(int numSteps) throws IOException {
 		if (numSteps == 0)
 			return;
-		List<RebaseTodoLine> todoLines = new LinkedList<RebaseTodoLine>();
-		List<RebaseTodoLine> poppedLines = new LinkedList<RebaseTodoLine>();
+		List<RebaseTodoLine> todoLines = new LinkedList<>();
+		List<RebaseTodoLine> poppedLines = new LinkedList<>();
 
 		for (RebaseTodoLine line : repo.readRebaseTodo(
 				rebaseState.getPath(GIT_REBASE_TODO), true)) {
@@ -1121,7 +1126,7 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
 		}
 		rebaseState.createFile(QUIET, ""); //$NON-NLS-1$
 
-		ArrayList<RebaseTodoLine> toDoSteps = new ArrayList<RebaseTodoLine>();
+		ArrayList<RebaseTodoLine> 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
@@ -1157,7 +1162,7 @@ private List<RevCommit> calculatePickList(RevCommit headCommit)
 			LogCommand cmd = git.log().addRange(upstreamCommit, headCommit);
 			commitsToUse = cmd.call();
 		}
-		List<RevCommit> cherryPickList = new ArrayList<RevCommit>();
+		List<RevCommit> cherryPickList = new ArrayList<>();
 		for (RevCommit commit : commitsToUse) {
 			if (preserveMerges || commit.getParentCount() == 1)
 				cherryPickList.add(commit);
@@ -1604,7 +1609,7 @@ PersonIdent parseAuthor(byte[] raw) {
 		if (raw.length == 0)
 			return null;
 
-		Map<String, String> keyValueMap = new HashMap<String, String>();
+		Map<String, String> keyValueMap = new HashMap<>();
 		for (int p = 0; p < raw.length;) {
 			int end = RawParseUtils.nextLF(raw, p);
 			if (end == p)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index 4536af1..04caa0f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -92,6 +92,7 @@ public ReflogCommand setRef(String ref) {
 	 * @throws GitAPIException
 	 * @throws InvalidRefNameException
 	 */
+	@Override
 	public Collection<ReflogEntry> call() throws GitAPIException,
 			InvalidRefNameException {
 		checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
index 1c4880c..fd8aac7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
@@ -79,6 +79,7 @@ protected RemoveNoteCommand(Repository repo) {
 		super(repo);
 	}
 
+	@Override
 	public Note call() throws GitAPIException {
 		checkCallable();
 		try (RevWalk walk = new RevWalk(repo);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
index 044a486..ce3a29f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
@@ -96,6 +96,7 @@ protected RenameBranchCommand(Repository repo) {
 	 *             if rename is tried without specifying the old name and HEAD
 	 *             is detached
 	 */
+	@Override
 	public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
 			RefAlreadyExistsException, DetachedHeadException {
 		checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 106988d..c5222c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -121,7 +121,7 @@ public enum ResetType {
 
 	private ResetType mode;
 
-	private Collection<String> filepaths = new LinkedList<String>();
+	private Collection<String> filepaths = new LinkedList<>();
 
 	private boolean isReflogDisabled;
 
@@ -141,6 +141,7 @@ public ResetCommand(Repository repo) {
 	 * @return the Ref after reset
 	 * @throws GitAPIException
 	 */
+	@Override
 	public Ref call() throws GitAPIException, CheckoutConflictException {
 		checkCallable();
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 9d79ed0..c3152a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -85,11 +85,11 @@
  *      >Git documentation about revert</a>
  */
 public class RevertCommand extends GitCommand<RevCommit> {
-	private List<Ref> commits = new LinkedList<Ref>();
+	private List<Ref> commits = new LinkedList<>();
 
 	private String ourCommitName = null;
 
-	private List<Ref> revertedRefs = new LinkedList<Ref>();
+	private List<Ref> revertedRefs = new LinkedList<>();
 
 	private MergeResult failingResult;
 
@@ -120,6 +120,7 @@ protected RevertCommand(Repository repo) {
 	 * @throws UnmergedPathsException
 	 * @throws NoMessageException
 	 */
+	@Override
 	public RevCommit call() throws NoMessageException, UnmergedPathsException,
 			ConcurrentRefUpdateException, WrongRepositoryStateException,
 			GitAPIException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
index fd2cbe0..9e2cf31 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
@@ -99,7 +99,7 @@ public class RmCommand extends GitCommand<DirCache> {
 	 */
 	public RmCommand(Repository repo) {
 		super(repo);
-		filepatterns = new LinkedList<String>();
+		filepatterns = new LinkedList<>();
 	}
 
 	/**
@@ -136,6 +136,7 @@ public RmCommand setCached(boolean cached) {
 	 *
 	 * @return the DirCache after Rm
 	 */
+	@Override
 	public DirCache call() throws GitAPIException,
 			NoFilepatternException {
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
index 168ea51..dbff463 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
@@ -74,6 +74,7 @@ protected ShowNoteCommand(Repository repo) {
 		super(repo);
 	}
 
+	@Override
 	public Note call() throws GitAPIException {
 		checkCallable();
 		NoteMap map = NoteMap.newEmptyMap();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index b8ee1ec..10ec2a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -157,6 +157,7 @@ private ObjectId getStashId() throws GitAPIException {
 	 * @throws NoHeadException
 	 * @throws StashApplyFailureException
 	 */
+	@Override
 	public ObjectId call() throws GitAPIException,
 			WrongRepositoryStateException, NoHeadException,
 			StashApplyFailureException {
@@ -232,19 +233,19 @@ public ObjectId call() throws GitAPIException,
 					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(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index ee9b7fc..681f8e6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -236,6 +236,7 @@ private Ref getHead() throws GitAPIException {
 	 * @return stashed commit or null if no changes to stash
 	 * @throws GitAPIException
 	 */
+	@Override
 	public RevCommit call() throws GitAPIException {
 		checkCallable();
 
@@ -261,9 +262,9 @@ public RevCommit call() throws GitAPIException {
 					return null;
 
 				MutableObjectId id = new MutableObjectId();
-				List<PathEdit> wtEdits = new ArrayList<PathEdit>();
-				List<String> wtDeletes = new ArrayList<String>();
-				List<DirCacheEntry> untracked = new ArrayList<DirCacheEntry>();
+				List<PathEdit> wtEdits = new ArrayList<>();
+				List<String> wtDeletes = new ArrayList<>();
+				List<DirCacheEntry> untracked = new ArrayList<>();
 				boolean hasChanges = false;
 				do {
 					AbstractTreeIterator headIter = treeWalk.getTree(0,
@@ -305,6 +306,7 @@ public RevCommit call() throws GitAPIException {
 							untracked.add(entry);
 						else
 							wtEdits.add(new PathEdit(entry) {
+								@Override
 								public void apply(DirCacheEntry ent) {
 									ent.copyMetaData(entry);
 								}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index 6e1d0f2..e215bdf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -171,6 +171,7 @@ private void updateRef(Ref stashRef, ObjectId newId) {
 	 * @return commit id of stash reference or null if no more stashed changes
 	 * @throws GitAPIException
 	 */
+	@Override
 	public ObjectId call() throws GitAPIException {
 		checkCallable();
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
index aedc9a6..8420dd2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
@@ -76,6 +76,7 @@ public StashListCommand(final Repository repo) {
 		super(repo);
 	}
 
+	@Override
 	public Collection<RevCommit> call() throws GitAPIException,
 			InvalidRefNameException {
 		checkCallable();
@@ -94,7 +95,7 @@ public Collection<RevCommit> call() throws GitAPIException,
 		if (stashEntries.isEmpty())
 			return Collections.emptyList();
 
-		final List<RevCommit> stashCommits = new ArrayList<RevCommit>(
+		final List<RevCommit> stashCommits = new ArrayList<>(
 				stashEntries.size());
 		try (RevWalk walk = new RevWalk(repo)) {
 			for (ReflogEntry entry : stashEntries) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java
index 48759c2..5b7c73b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java
@@ -189,7 +189,7 @@ public Set<String> getIgnoredNotInIndex() {
 	 * @since 3.2
 	 */
 	public Set<String> getUncommittedChanges() {
-		Set<String> uncommittedChanges = new HashSet<String>();
+		Set<String> uncommittedChanges = new HashSet<>();
 		uncommittedChanges.addAll(diff.getAdded());
 		uncommittedChanges.addAll(diff.getChanged());
 		uncommittedChanges.addAll(diff.getRemoved());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
index 9752195..8f7804a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
@@ -109,7 +109,7 @@ public StatusCommand setIgnoreSubmodules(IgnoreSubmoduleMode mode) {
 	 */
 	public StatusCommand addPath(String path) {
 		if (paths == null)
-			paths = new LinkedList<String>();
+			paths = new LinkedList<>();
 		paths.add(path);
 		return this;
 	}
@@ -134,6 +134,7 @@ public List<String> getPaths() {
 	 * @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);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index b0f772e..e3ba894 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -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;
@@ -149,6 +150,7 @@ protected boolean submoduleExists() throws IOException {
 	 * @return the newly created {@link Repository}
 	 * @throws GitAPIException
 	 */
+	@Override
 	public Repository call() throws GitAPIException {
 		checkCallable();
 		if (path == null || path.length() == 0)
@@ -157,6 +159,14 @@ public Repository call() throws GitAPIException {
 			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));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
index 1dbe368..4c5e317 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
@@ -76,7 +76,7 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> {
 	 */
 	public SubmoduleInitCommand(final Repository repo) {
 		super(repo);
-		paths = new ArrayList<String>();
+		paths = new ArrayList<>();
 	}
 
 	/**
@@ -91,6 +91,7 @@ public SubmoduleInitCommand addPath(final String path) {
 		return this;
 	}
 
+	@Override
 	public Collection<String> call() throws GitAPIException {
 		checkCallable();
 
@@ -98,7 +99,7 @@ public Collection<String> call() throws GitAPIException {
 			if (!paths.isEmpty())
 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
 			StoredConfig config = repo.getConfig();
-			List<String> initialized = new ArrayList<String>();
+			List<String> initialized = new ArrayList<>();
 			while (generator.next()) {
 				// Ignore entry if URL is already present in config file
 				if (generator.getConfigUrl() != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
index a1ea790..8b27e4c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
@@ -76,7 +76,7 @@ public class SubmoduleStatusCommand extends
 	 */
 	public SubmoduleStatusCommand(final Repository repo) {
 		super(repo);
-		paths = new ArrayList<String>();
+		paths = new ArrayList<>();
 	}
 
 	/**
@@ -91,13 +91,14 @@ public SubmoduleStatusCommand addPath(final String path) {
 		return this;
 	}
 
+	@Override
 	public Map<String, SubmoduleStatus> call() throws GitAPIException {
 		checkCallable();
 
 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
 			if (!paths.isEmpty())
 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
-			Map<String, SubmoduleStatus> statuses = new HashMap<String, SubmoduleStatus>();
+			Map<String, SubmoduleStatus> statuses = new HashMap<>();
 			while (generator.next()) {
 				SubmoduleStatus status = getStatus(generator);
 				statuses.put(status.getPath(), status);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
index 088eedc..f97dce9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -78,7 +78,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
 	 */
 	public SubmoduleSyncCommand(final Repository repo) {
 		super(repo);
-		paths = new ArrayList<String>();
+		paths = new ArrayList<>();
 	}
 
 	/**
@@ -108,13 +108,14 @@ protected String getHeadBranch(final Repository subRepo) throws IOException {
 			return null;
 	}
 
+	@Override
 	public Map<String, String> call() throws GitAPIException {
 		checkCallable();
 
 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
 			if (!paths.isEmpty())
 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
-			Map<String, String> synced = new HashMap<String, String>();
+			Map<String, String> synced = new HashMap<>();
 			StoredConfig config = repo.getConfig();
 			while (generator.next()) {
 				String remoteUrl = generator.getRemoteUrl();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index 342d7f4..29d5d49 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -94,7 +94,7 @@ public class SubmoduleUpdateCommand extends
 	 */
 	public SubmoduleUpdateCommand(final Repository repo) {
 		super(repo);
-		paths = new ArrayList<String>();
+		paths = new ArrayList<>();
 	}
 
 	/**
@@ -137,6 +137,7 @@ public SubmoduleUpdateCommand addPath(final String path) {
 	 * @throws WrongRepositoryStateException
 	 * @throws GitAPIException
 	 */
+	@Override
 	public Collection<String> call() throws InvalidConfigurationException,
 			NoHeadException, ConcurrentRefUpdateException,
 			CheckoutConflictException, InvalidMergeHeadsException,
@@ -147,7 +148,7 @@ public Collection<String> call() throws InvalidConfigurationException,
 		try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
 			if (!paths.isEmpty())
 				generator.setFilter(PathFilterGroup.createFromStrings(paths));
-			List<String> updated = new ArrayList<String>();
+			List<String> updated = new ArrayList<>();
 			while (generator.next()) {
 				// Skip submodules not registered in .gitmodules file
 				if (generator.getModulesPath() == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
index 39dd42c..bdbb862 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
@@ -121,6 +121,7 @@ protected TagCommand(Repository repo) {
 	 *             when called on a git repo without a HEAD reference
 	 * @since 2.0
 	 */
+	@Override
 	public Ref call() throws GitAPIException, ConcurrentRefUpdateException,
 			InvalidTagNameException, NoHeadException {
 		checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
index e317507..7df35c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
@@ -95,7 +95,7 @@ public List<String> getConflictingPaths() {
 	 */
 	CheckoutConflictException addConflictingPath(String conflictingPath) {
 		if (conflictingPaths == null)
-			conflictingPaths = new LinkedList<String>();
+			conflictingPaths = new LinkedList<>();
 		conflictingPaths.add(conflictingPath);
 		return this;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
index 7196502..13aeaee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
@@ -65,7 +65,7 @@ public class AttributesNode {
 
 	/** Create an empty ignore node with no rules. */
 	public AttributesNode() {
-		rules = new ArrayList<AttributesRule>();
+		rules = new ArrayList<>();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index 0532250..c9c69db 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -71,7 +71,7 @@ public class AttributesRule {
 
 	private static List<Attribute> parseAttributes(String attributesLine) {
 		// the C implementation oddly enough allows \r between attributes too.
-		ArrayList<Attribute> result = new ArrayList<Attribute>();
+		ArrayList<Attribute> result = new ArrayList<>();
 		for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) {
 			attribute = attribute.trim();
 			if (attribute.length() == 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index fa6fe75..4ad58c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -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;
@@ -238,11 +239,13 @@ public BlameGenerator setFollowFileRenames(boolean follow) {
 	}
 
 	/**
-	 * 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;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
index 39421c6..bd6e5c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
@@ -117,7 +117,7 @@ public <S extends Sequence> EditList diff(
 			if (region.getLengthA() == 1 && region.getLengthB() == 1)
 				return EditList.singleton(region);
 
-			SubsequenceComparator<S> cs = new SubsequenceComparator<S>(cmp);
+			SubsequenceComparator<S> cs = new SubsequenceComparator<>(cmp);
 			Subsequence<S> as = Subsequence.a(a, region);
 			Subsequence<S> bs = Subsequence.b(b, region);
 			EditList e = Subsequence.toBase(diffNonCommon(cs, as, bs), as, bs);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
index b1cbb91..324b99e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
@@ -55,6 +55,7 @@
 public class DiffConfig {
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<DiffConfig> KEY = new SectionParser<DiffConfig>() {
+		@Override
 		public DiffConfig parse(final Config cfg) {
 			return new DiffConfig(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
index 24409ee..e1dfcff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -179,7 +179,7 @@ public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees,
 		else
 			treeFilterMarker = null;
 
-		List<DiffEntry> r = new ArrayList<DiffEntry>();
+		List<DiffEntry> r = new ArrayList<>();
 		MutableObjectId idBuf = new MutableObjectId();
 		while (walk.next()) {
 			DiffEntry entry = new DiffEntry();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 819442c..fa7cc0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -682,7 +682,7 @@ public void format(DiffEntry ent) throws IOException {
 	}
 
 	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$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java
index 74bbca1..bf6d967 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequencePair.java
@@ -81,7 +81,7 @@ public HashedSequencePair(SequenceComparator<? super S> cmp, S a, S b) {
 
 	/** @return obtain a comparator that uses the cached hash codes. */
 	public HashedSequenceComparator<S> getComparator() {
-		return new HashedSequenceComparator<S>(cmp);
+		return new HashedSequenceComparator<>(cmp);
 	}
 
 	/** @return wrapper around A that includes cached hash codes. */
@@ -103,6 +103,6 @@ private HashedSequence<S> wrap(S base) {
 		final int[] hashes = new int[end];
 		for (int ptr = 0; ptr < end; ptr++)
 			hashes[ptr] = cmp.hash(base, ptr);
-		return new HashedSequence<S>(base, hashes);
+		return new HashedSequence<>(base, hashes);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
index 2f5c9ea..4ef5845 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
@@ -130,17 +130,18 @@ public void setMaxChainLength(int maxLen) {
 		maxChainLength = maxLen;
 	}
 
+	@Override
 	public <S extends Sequence> void diffNonCommon(EditList edits,
 			HashedSequenceComparator<S> cmp, HashedSequence<S> a,
 			HashedSequence<S> b, Edit region) {
-		new State<S>(edits, cmp, a, b).diffRegion(region);
+		new State<>(edits, cmp, a, b).diffRegion(region);
 	}
 
 	private class State<S extends Sequence> {
 		private final HashedSequenceComparator<S> cmp;
 		private final HashedSequence<S> a;
 		private final HashedSequence<S> b;
-		private final List<Edit> queue = new ArrayList<Edit>();
+		private final List<Edit> queue = new ArrayList<>();
 
 		/** Result edits we have determined that must be made to convert a to b. */
 		final EditList edits;
@@ -160,7 +161,7 @@ void diffRegion(Edit r) {
 		}
 
 		private void diffReplace(Edit r) {
-			Edit lcs = new HistogramDiffIndex<S>(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 +214,7 @@ private void diff(Edit r) {
 		}
 
 		private SubsequenceComparator<HashedSequence<S>> subcmp() {
-			return new SubsequenceComparator<HashedSequence<S>>(cmp);
+			return new SubsequenceComparator<>(cmp);
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
index e3861cd..55ceec8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
@@ -48,7 +48,7 @@ public abstract class LowLevelDiffAlgorithm extends DiffAlgorithm {
 	@Override
 	public <S extends Sequence> EditList diffNonCommon(
 			SequenceComparator<? super S> cmp, S a, S b) {
-		HashedSequencePair<S> p = new HashedSequencePair<S>(cmp, a, b);
+		HashedSequencePair<S> p = new HashedSequencePair<>(cmp, a, b);
 		HashedSequenceComparator<S> hc = p.getComparator();
 		HashedSequence<S> ha = p.getA();
 		HashedSequence<S> hb = p.getB();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
index 9810a6a..e1bda11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
@@ -460,6 +460,7 @@ boolean calculate(int d) {
 		}
 
 		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 @@ final int snake(int k, int x) {
 				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 @@ final void adjustMinMaxK(final int k, final int x) {
 				}
 			}
 
+			@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 @@ final boolean meets(int d, int k, int x, long snake) {
 		}
 
 		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 @@ final int snake(int k, int x) {
 				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 @@ final void adjustMinMaxK(final int k, final int x) {
 				}
 			}
 
+			@Override
 			final boolean meets(int d, int k, int x, long snake) {
 				if (k < forward.beginK || k > forward.endK)
 					return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
index b26e1bc..5bfee75 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
@@ -107,6 +107,7 @@ public RawText(File file) throws IOException {
 	}
 
 	/** @return total number of items in the sequence. */
+	@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
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
index 8865b62..bc52473 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -70,6 +70,7 @@ public class RenameDetector {
 	private static final int EXACT_RENAME_SCORE = 100;
 
 	private static final Comparator<DiffEntry> DIFF_COMPARATOR = new Comparator<DiffEntry>() {
+		@Override
 		public int compare(DiffEntry a, DiffEntry b) {
 			int cmp = nameOf(a).compareTo(nameOf(b));
 			if (cmp == 0)
@@ -392,15 +393,15 @@ public List<DiffEntry> compute(ContentSource.Pair reader, ProgressMonitor pm)
 
 	/** Reset this rename detector for another rename detection pass. */
 	public void reset() {
-		entries = new ArrayList<DiffEntry>();
-		deleted = new ArrayList<DiffEntry>();
-		added = new ArrayList<DiffEntry>();
+		entries = new ArrayList<>();
+		deleted = new ArrayList<>();
+		added = new ArrayList<>();
 		done = false;
 	}
 
 	private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm)
 			throws IOException {
-		ArrayList<DiffEntry> newEntries = new ArrayList<DiffEntry>(entries.size());
+		ArrayList<DiffEntry> newEntries = new ArrayList<>(entries.size());
 
 		pm.beginTask(JGitText.get().renamesBreakingModifies, entries.size());
 
@@ -427,8 +428,8 @@ private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm)
 	}
 
 	private void rejoinModifies(ProgressMonitor pm) {
-		HashMap<String, DiffEntry> nameMap = new HashMap<String, DiffEntry>();
-		ArrayList<DiffEntry> newAdded = new ArrayList<DiffEntry>(added.size());
+		HashMap<String, DiffEntry> nameMap = new HashMap<>();
+		ArrayList<DiffEntry> newAdded = new ArrayList<>(added.size());
 
 		pm.beginTask(JGitText.get().renamesRejoiningModifies, added.size()
 				+ deleted.size());
@@ -455,7 +456,7 @@ private void rejoinModifies(ProgressMonitor pm) {
 		}
 
 		added = newAdded;
-		deleted = new ArrayList<DiffEntry>(nameMap.values());
+		deleted = new ArrayList<>(nameMap.values());
 	}
 
 	private int calculateModifyScore(ContentSource.Pair reader, DiffEntry d)
@@ -507,8 +508,8 @@ private void findExactRenames(ProgressMonitor pm) {
 		HashMap<AbbreviatedObjectId, Object> deletedMap = populateMap(deleted, pm);
 		HashMap<AbbreviatedObjectId, Object> addedMap = populateMap(added, pm);
 
-		ArrayList<DiffEntry> uniqueAdds = new ArrayList<DiffEntry>(added.size());
-		ArrayList<List<DiffEntry>> nonUniqueAdds = new ArrayList<List<DiffEntry>>();
+		ArrayList<DiffEntry> uniqueAdds = new ArrayList<>(added.size());
+		ArrayList<List<DiffEntry>> nonUniqueAdds = new ArrayList<>();
 
 		for (Object o : addedMap.values()) {
 			if (o instanceof DiffEntry)
@@ -517,7 +518,7 @@ private void findExactRenames(ProgressMonitor pm) {
 				nonUniqueAdds.add((List<DiffEntry>) o);
 		}
 
-		ArrayList<DiffEntry> left = new ArrayList<DiffEntry>(added.size());
+		ArrayList<DiffEntry> left = new ArrayList<>(added.size());
 
 		for (DiffEntry a : uniqueAdds) {
 			Object del = deletedMap.get(a.newId);
@@ -626,7 +627,7 @@ private void findExactRenames(ProgressMonitor pm) {
 		}
 		added = left;
 
-		deleted = new ArrayList<DiffEntry>(deletedMap.size());
+		deleted = new ArrayList<>(deletedMap.size());
 		for (Object o : deletedMap.values()) {
 			if (o instanceof DiffEntry) {
 				DiffEntry e = (DiffEntry) o;
@@ -676,11 +677,11 @@ private static DiffEntry bestPathMatch(DiffEntry src, List<DiffEntry> list) {
 	@SuppressWarnings("unchecked")
 	private HashMap<AbbreviatedObjectId, Object> populateMap(
 			List<DiffEntry> diffEntries, ProgressMonitor pm) {
-		HashMap<AbbreviatedObjectId, Object> map = new HashMap<AbbreviatedObjectId, Object>();
+		HashMap<AbbreviatedObjectId, Object> map = new HashMap<>();
 		for (DiffEntry de : diffEntries) {
 			Object old = map.put(id(de), de);
 			if (old instanceof DiffEntry) {
-				ArrayList<DiffEntry> list = new ArrayList<DiffEntry>(2);
+				ArrayList<DiffEntry> list = new ArrayList<>(2);
 				list.add((DiffEntry) old);
 				list.add(de);
 				map.put(id(de), list);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
index 6088d72..653658b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -136,7 +136,7 @@ void compute(ProgressMonitor pm) throws IOException {
 				2 * srcs.size() * dsts.size());
 
 		int mNext = buildMatrix(pm);
-		out = new ArrayList<DiffEntry>(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 @@ boolean isTableOverflow() {
 	}
 
 	private static List<DiffEntry> compactSrcList(List<DiffEntry> in) {
-		ArrayList<DiffEntry> r = new ArrayList<DiffEntry>(in.size());
+		ArrayList<DiffEntry> r = new ArrayList<>(in.size());
 		for (DiffEntry e : in) {
 			if (e.changeType == ChangeType.DELETE)
 				r.add(e);
@@ -201,7 +201,7 @@ private static List<DiffEntry> compactSrcList(List<DiffEntry> in) {
 	}
 
 	private static List<DiffEntry> compactDstList(List<DiffEntry> in) {
-		ArrayList<DiffEntry> r = new ArrayList<DiffEntry>(in.size());
+		ArrayList<DiffEntry> r = new ArrayList<>(in.size());
 		for (DiffEntry e : in) {
 			if (e != null)
 				r.add(e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
index 017fe69..50ca613 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
@@ -66,7 +66,7 @@ public final class Subsequence<S extends Sequence> extends Sequence {
 	 * @return subsequence of {@code base} as described by A in {@code region}.
 	 */
 	public static <S extends Sequence> Subsequence<S> a(S a, Edit region) {
-		return new Subsequence<S>(a, region.beginA, region.endA);
+		return new Subsequence<>(a, region.beginA, region.endA);
 	}
 
 	/**
@@ -81,7 +81,7 @@ public static <S extends Sequence> Subsequence<S> a(S a, Edit region) {
 	 * @return subsequence of {@code base} as described by B in {@code region}.
 	 */
 	public static <S extends Sequence> Subsequence<S> b(S b, Edit region) {
-		return new Subsequence<S>(b, region.beginB, region.endB);
+		return new Subsequence<>(b, region.beginB, region.endB);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index b0cf8be..ce52fed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -111,6 +111,7 @@ public class DirCache {
 	private static final byte[] NO_CHECKSUM = {};
 
 	static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
+		@Override
 		public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
 			final int cr = cmp(o1, o2);
 			if (cr != 0)
@@ -992,7 +993,7 @@ private void registerIndexChangedListener(IndexChangedListener listener) {
 	 * @throws IOException
 	 */
 	private void updateSmudgedEntries() throws IOException {
-		List<String> paths = new ArrayList<String>(128);
+		List<String> paths = new ArrayList<>(128);
 		try (TreeWalk walk = new TreeWalk(repository)) {
 			walk.setOperationType(OperationType.CHECKIN_OP);
 			for (int i = 0; i < entryCnt; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
index cfebe2d..676a6ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -218,6 +218,7 @@ private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
 		return e;
 	}
 
+	@Override
 	public void finish() {
 		if (!sorted)
 			resort();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index c318443..84f0da9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -127,11 +127,11 @@ public CheckoutMetadata(EolStreamType eolStreamType,
 
 	private Repository repo;
 
-	private HashMap<String, CheckoutMetadata> updated = new HashMap<String, CheckoutMetadata>();
+	private HashMap<String, CheckoutMetadata> updated = new HashMap<>();
 
-	private ArrayList<String> conflicts = new ArrayList<String>();
+	private ArrayList<String> conflicts = new ArrayList<>();
 
-	private ArrayList<String> removed = new ArrayList<String>();
+	private ArrayList<String> removed = new ArrayList<>();
 
 	private ObjectId mergeCommitTree;
 
@@ -147,7 +147,7 @@ public CheckoutMetadata(EolStreamType eolStreamType,
 
 	private boolean failOnConflict = true;
 
-	private ArrayList<String> toBeDeleted = new ArrayList<String>();
+	private ArrayList<String> toBeDeleted = new ArrayList<>();
 
 	private boolean emptyDirCache;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index c987c96..22bedcf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -74,6 +74,7 @@
  */
 public class DirCacheEditor extends BaseDirCacheEditor {
 	private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
+		@Override
 		public int compare(final PathEdit o1, final PathEdit o2) {
 			final byte[] a = o1.path;
 			final byte[] b = o2.path;
@@ -95,7 +96,7 @@ public int compare(final PathEdit o1, final PathEdit o2) {
 	 */
 	protected DirCacheEditor(final DirCache dc, final int ecnt) {
 		super(dc, ecnt);
-		edits = new ArrayList<PathEdit>();
+		edits = new ArrayList<>();
 	}
 
 	/**
@@ -123,6 +124,7 @@ public boolean commit() throws IOException {
 		return super.commit();
 	}
 
+	@Override
 	public void finish() {
 		if (!edits.isEmpty()) {
 			applyEdits();
@@ -383,6 +385,7 @@ public DeletePath(final DirCacheEntry ent) {
 			super(ent);
 		}
 
+		@Override
 		public void apply(final DirCacheEntry ent) {
 			throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
 		}
@@ -432,6 +435,7 @@ public DeleteTree(String entryPath) {
 			return path;
 		}
 
+		@Override
 		public void apply(final DirCacheEntry ent) {
 			throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index 8bcf4bf..a06f9d3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -80,6 +80,7 @@ public class DirCacheTree {
 	private static final DirCacheTree[] NO_CHILDREN = {};
 
 	private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
+		@Override
 		public int compare(final DirCacheTree o1, final DirCacheTree o2) {
 			final byte[] a = o1.encodedName;
 			final byte[] b = o2.encodedName;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java
new file mode 100644
index 0000000..c2833a1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CancelledException.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+	/**
+	 * @param message
+	 */
+	public CancelledException(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java
index 55b64ee..3a7b2c6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java
@@ -75,7 +75,7 @@ private static String format(final Collection<Throwable> causes) {
 	 */
 	public CompoundException(final Collection<Throwable> why) {
 		super(format(why));
-		causeList = Collections.unmodifiableList(new ArrayList<Throwable>(why));
+		causeList = Collections.unmodifiableList(new ArrayList<>(why));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
index 6ac4b0f..12ef533 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
@@ -50,7 +50,7 @@
 
 /** Manages a thread-safe list of {@link RepositoryListener}s. */
 public class ListenerList {
-	private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>>();
+	private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<>();
 
 	/**
 	 * Register an IndexChangedListener.
@@ -126,7 +126,7 @@ private void add(ListenerHandle handle) {
 		if (list == null) {
 			CopyOnWriteArrayList<ListenerHandle> newList;
 
-			newList = new CopyOnWriteArrayList<ListenerHandle>();
+			newList = new CopyOnWriteArrayList<>();
 			list = lists.putIfAbsent(handle.type, newList);
 			if (list == null)
 				list = newList;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
index 0aa0075..10c84c4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
@@ -70,6 +70,7 @@ public final void setNewHeads(List<Head> newHeads) {
 		this.newHeads = newHeads;
 	}
 
+	@Override
 	public List<Head> getNextHeads(char c) {
 		if (matches(c))
 			return newHeads;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
index f9c2394..856d74e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
@@ -120,9 +120,9 @@ private FileNameMatcher(final List<Head> headsStartValue) {
 	private FileNameMatcher(final List<Head> headsStartValue,
 			final List<Head> heads) {
 		this.headsStartValue = headsStartValue;
-		this.heads = new ArrayList<Head>(heads.size());
+		this.heads = new ArrayList<>(heads.size());
 		this.heads.addAll(heads);
-		this.listForLocalUseage = new ArrayList<Head>(heads.size());
+		this.listForLocalUseage = new ArrayList<>(heads.size());
 	}
 
 	/**
@@ -158,7 +158,7 @@ private static List<Head> createHeadsStartValues(
 		final List<AbstractHead> allHeads = parseHeads(patternString,
 				invalidWildgetCharacter);
 
-		List<Head> nextHeadsSuggestion = new ArrayList<Head>(2);
+		List<Head> 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 +172,7 @@ private static List<Head> createHeadsStartValues(
 				head.setNewHeads(nextHeadsSuggestion);
 			} else {
 				head.setNewHeads(nextHeadsSuggestion);
-				nextHeadsSuggestion = new ArrayList<Head>(2);
+				nextHeadsSuggestion = new ArrayList<>(2);
 				nextHeadsSuggestion.add(head);
 			}
 		}
@@ -236,7 +236,7 @@ private static List<AbstractHead> parseHeads(final String pattern,
 			throws InvalidPatternException {
 
 		int currentIndex = 0;
-		List<AbstractHead> heads = new ArrayList<AbstractHead>();
+		List<AbstractHead> heads = new ArrayList<>();
 		while (currentIndex < pattern.length()) {
 			final int groupStart = indexOfUnescaped(pattern, '[', currentIndex);
 			if (groupStart == -1) {
@@ -262,7 +262,7 @@ private static List<AbstractHead> parseHeads(final String pattern,
 
 	private static List<AbstractHead> createSimpleHeads(
 			final String patternPart, final Character invalidWildgetCharacter) {
-		final List<AbstractHead> heads = new ArrayList<AbstractHead>(
+		final List<AbstractHead> heads = new ArrayList<>(
 				patternPart.length());
 
 		boolean escaped = false;
@@ -375,7 +375,7 @@ public void reset() {
 	 *         reset and start point.
 	 */
 	public FileNameMatcher createMatcherForSuffix() {
-		final List<Head> copyOfHeads = new ArrayList<Head>(heads.size());
+		final List<Head> copyOfHeads = new ArrayList<>(heads.size());
 		copyOfHeads.addAll(heads);
 		return new FileNameMatcher(copyOfHeads);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
index 8af5228..5c18756 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
@@ -64,7 +64,7 @@ final class GroupHead extends AbstractHead {
 	GroupHead(String pattern, final String wholePattern)
 			throws InvalidPatternException {
 		super(false);
-		this.characterClasses = new ArrayList<CharacterPattern>();
+		this.characterClasses = new ArrayList<>();
 		this.inverse = pattern.startsWith("!"); //$NON-NLS-1$
 		if (inverse) {
 			pattern = pattern.substring(1);
@@ -159,6 +159,7 @@ private static final class CharacterRange implements CharacterPattern {
 			this.end = end;
 		}
 
+		@Override
 		public final boolean matches(char c) {
 			return start <= c && c <= end;
 		}
@@ -167,6 +168,7 @@ public final boolean matches(char c) {
 	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 +177,7 @@ public final boolean matches(char c) {
 	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 +186,7 @@ public final boolean matches(char c) {
 	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 +195,7 @@ public final boolean matches(char c) {
 	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 +204,7 @@ public final boolean matches(char c) {
 	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 +217,7 @@ private static final class OneCharacterPattern implements CharacterPattern {
 			this.expectedCharacter = c;
 		}
 
+		@Override
 		public final boolean matches(char c) {
 			return this.expectedCharacter == c;
 		}
@@ -221,6 +228,7 @@ private static final class PunctPattern implements CharacterPattern {
 
 		private static String punctCharacters = "-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"; //$NON-NLS-1$
 
+		@Override
 		public boolean matches(char c) {
 			return punctCharacters.indexOf(c) != -1;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
index 78a61b9..f9ddd9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
@@ -56,6 +56,7 @@ private LastHead() {
 		// defined because of javadoc and visibility modifier.
 	}
 
+	@Override
 	public List<Head> getNextHeads(char c) {
 		return FileNameMatcher.EMPTY_HEAD_LIST;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 8a35d35..94c8e43 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -57,6 +57,7 @@
 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.internal.RepoText;
@@ -77,7 +78,7 @@
  */
 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<String, Remote> remotes;
@@ -124,15 +125,10 @@ public ManifestParser(IncludedFileReader includedReader, String filename,
 		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<String>();
-		minusGroups = new HashSet<String>();
+		plusGroups = new HashSet<>();
+		minusGroups = new HashSet<>();
 		if (groups == null || groups.length() == 0
 				|| groups.equals("default")) { //$NON-NLS-1$
 			// default means "all,-notdefault"
@@ -146,9 +142,9 @@ public ManifestParser(IncludedFileReader includedReader, String filename,
 			}
 		}
 
-		remotes = new HashMap<String, Remote>();
-		projects = new ArrayList<RepoProject>();
-		filteredProjects = new ArrayList<RepoProject>();
+		remotes = new HashMap<>();
+		projects = new ArrayList<>();
+		filteredProjects = new ArrayList<>();
 	}
 
 	/**
@@ -257,13 +253,7 @@ public void endDocument() throws SAXException {
 			return;
 
 		// Only do the following after we finished reading everything.
-		Map<String, String> remoteUrls = new HashMap<String, String>();
-		URI baseUri;
-		try {
-			baseUri = new URI(baseUrl);
-		} catch (URISyntaxException e) {
-			throw new SAXException(e);
-		}
+		Map<String, URI> remoteUrls = new HashMap<>();
 		if (defaultRevision == null && defaultRemote != null) {
 			Remote remote = remotes.get(defaultRemote);
 			if (remote != null) {
@@ -293,16 +283,18 @@ public void endDocument() throws SAXException {
 					revision = r.revision;
 				}
 			}
-			String remoteUrl = remoteUrls.get(remote);
+			URI remoteUrl = remoteUrls.get(remote);
 			if (remoteUrl == null) {
-				remoteUrl =
-						baseUri.resolve(remotes.get(remote).fetch).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(revision);
+			proj.setUrl(remoteUrl.resolve(proj.getName()).toString())
+				.setDefaultRevision(revision);
 		}
 
 		filteredProjects.addAll(projects);
@@ -310,6 +302,23 @@ public void endDocument() throws SAXException {
 		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.
 	 *
@@ -324,7 +333,7 @@ public List<RepoProject> getProjects() {
 	 *
 	 * @return filtered projects list reference, never null
 	 */
-	public List<RepoProject> getFilteredProjects() {
+	public @NonNull List<RepoProject> getFilteredProjects() {
 		return filteredProjects;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 86dbabc..e105dc0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -96,8 +96,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
- * &quot;foo&quot; and another project's path is &quot;foo/bar&quot;. This won't
+ * repo manifest allows projects overlapping, e.g. one project's manifestPath is
+ * &quot;foo&quot; and another project's manifestPath is &quot;foo/bar&quot;. This won't
  * work in git submodule, so we'll skip all the sub projects
  * (&quot;foo/bar&quot; in the example) while converting.
  *
@@ -105,7 +105,7 @@
  * @since 3.4
  */
 public class RepoCommand extends GitCommand<RevCommit> {
-	private String path;
+	private String manifestPath;
 	private String uri;
 	private String groupsParam;
 	private String branch;
@@ -168,6 +168,7 @@ public interface RemoteReader {
 
 	/** A default implementation of {@link RemoteReader} callback. */
 	public static class DefaultRemoteReader implements RemoteReader {
+		@Override
 		public ObjectId sha1(String uri, String ref) throws GitAPIException {
 			Map<String, Ref> map = Git
 					.lsRemoteRepository()
@@ -177,13 +178,13 @@ public ObjectId sha1(String uri, String ref) throws GitAPIException {
 			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$
 			try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir)
-					.setURI(uri).call();
-					Repository repo = git.getRepository()) {
-				return readFileFromRepo(repo, ref, path);
+					.setURI(uri).call()) {
+				return readFileFromRepo(git.getRepository(), ref, path);
 			} finally {
 				FileUtils.delete(dir, FileUtils.RECURSIVE);
 			}
@@ -243,7 +244,7 @@ public RepoCommand(Repository repo) {
 	 * @return this command
 	 */
 	public RepoCommand setPath(String path) {
-		this.path = path;
+		this.manifestPath = path;
 		return this;
 	}
 
@@ -263,7 +264,11 @@ public RepoCommand setInputStream(InputStream inputStream) {
 	}
 
 	/**
-	 * 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
 	 * @return this command
@@ -447,15 +452,16 @@ public RepoCommand setIncludedFileReader(IncludedFileReader reader) {
 	public RevCommit call() throws GitAPIException {
 		try {
 			checkCallable();
-			if (uri == null || uri.length() == 0)
+			if (uri == null || uri.length() == 0) {
 				throw new IllegalArgumentException(
-						JGitText.get().uriNotConfigured);
+					JGitText.get().uriNotConfigured);
+			}
 			if (inputStream == null) {
-				if (path == null || path.length() == 0)
+				if (manifestPath == null || manifestPath.length() == 0)
 					throw new IllegalArgumentException(
 							JGitText.get().pathNotConfigured);
 				try {
-					inputStream = new FileInputStream(path);
+					inputStream = new FileInputStream(manifestPath);
 				} catch (IOException e) {
 					throw new IllegalArgumentException(
 							JGitText.get().pathNotConfigured);
@@ -463,7 +469,7 @@ public RevCommit call() throws GitAPIException {
 			}
 
 			if (repo.isBare()) {
-				bareProjects = new ArrayList<RepoProject>();
+				bareProjects = new ArrayList<>();
 				if (author == null)
 					author = new PersonIdent(repo);
 				if (callback == null)
@@ -472,7 +478,7 @@ public RevCommit call() throws GitAPIException {
 				git = new Git(repo);
 
 			ManifestParser parser = new ManifestParser(
-					includedReader, path, branch, uri, groupsParam, repo);
+					includedReader, manifestPath, branch, uri, groupsParam, repo);
 			try {
 				parser.read(inputStream);
 				for (RepoProject proj : parser.getFilteredProjects()) {
@@ -503,7 +509,7 @@ public RevCommit call() throws GitAPIException {
 				Config cfg = new Config();
 				StringBuilder attributes = new StringBuilder();
 				for (RepoProject proj : bareProjects) {
-					String name = proj.getPath();
+					String path = proj.getPath();
 					String nameUri = proj.getName();
 					ObjectId objectId;
 					if (ObjectId.isId(proj.getRevision())
@@ -519,7 +525,7 @@ public RevCommit call() throws GitAPIException {
 						}
 						if (recordRemoteBranch) {
 							// can be branch or tag
-							cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
+							cfg.setString("submodule", path, "branch", //$NON-NLS-1$ //$NON-NLS-2$
 									proj.getRevision());
 						}
 
@@ -529,14 +535,14 @@ public RevCommit call() throws GitAPIException {
 							// 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", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
+							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(name);
+						rec.append(path);
 						for (String group : proj.getGroups()) {
 							rec.append(" "); //$NON-NLS-1$
 							rec.append(group);
@@ -544,11 +550,11 @@ public RevCommit call() throws GitAPIException {
 						rec.append("\n"); //$NON-NLS-1$
 						attributes.append(rec.toString());
 					}
-					cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$
-					cfg.setString("submodule", name, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", path, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$
 
 					// create gitlink
-					DirCacheEntry dcEntry = new DirCacheEntry(name);
+					DirCacheEntry dcEntry = new DirCacheEntry(path);
 					dcEntry.setObjectId(objectId);
 					dcEntry.setFileMode(FileMode.GITLINK);
 					builder.add(dcEntry);
@@ -635,17 +641,17 @@ public RevCommit call() throws GitAPIException {
 		}
 	}
 
-	private void addSubmodule(String url, String name, String revision,
+	private void addSubmodule(String url, String path, String revision,
 			List<CopyFile> copyfiles, Set<String> groups, String recommendShallow)
 			throws GitAPIException, IOException {
 		if (repo.isBare()) {
-			RepoProject proj = new RepoProject(url, name, revision, null, groups, recommendShallow);
+			RepoProject proj = new RepoProject(url, path, revision, null, groups, recommendShallow);
 			proj.addCopyFiles(copyfiles);
 			bareProjects.add(proj);
 		} else {
 			SubmoduleAddCommand add = git
 				.submoduleAdd()
-				.setPath(name)
+				.setPath(path)
 				.setURI(url);
 			if (monitor != null)
 				add.setProgressMonitor(monitor);
@@ -657,7 +663,7 @@ private void addSubmodule(String url, String name, String revision,
 							.call();
 				}
 				subRepo.close();
-				git.add().addFilepattern(name).call();
+				git.add().addFilepattern(path).call();
 			}
 			for (CopyFile copyfile : copyfiles) {
 				copyfile.copy();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
index ff4a3ed..700cf11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -155,7 +155,7 @@ public RepoProject(String name, String path, String revision,
 		this.remote = remote;
 		this.groups = groups;
 		this.recommendShallow = recommendShallow;
-		copyfiles = new ArrayList<CopyFile>();
+		copyfiles = new ArrayList<>();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
index 36b6e3a..02a2565 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
@@ -64,6 +64,7 @@ public static RepoText get() {
 	/***/ public String errorIncludeNotImplemented;
 	/***/ public String errorNoDefault;
 	/***/ public String errorNoDefaultFilename;
+	/***/ public String errorNoFetch;
 	/***/ public String errorParsingManifestFile;
 	/***/ public String errorRemoteUnavailable;
 	/***/ public String invalidManifest;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index ad2eeb0..c1aca6a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -95,6 +95,7 @@ protected GitHook(Repository repo, PrintStream outputStream) {
 	 *             If the hook has been run and a returned an exit code
 	 *             different from zero.
 	 */
+	@Override
 	public abstract T call() throws IOException, AbortedByHookException;
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
index 8b1244e..6314c63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -83,7 +83,7 @@ public static enum MatchResult {
 
 	/** Create an empty ignore node with no rules. */
 	public IgnoreNode() {
-		rules = new ArrayList<FastIgnoreRule>();
+		rules = new ArrayList<>();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
index 4e90d8c..64c2a74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
@@ -46,8 +46,6 @@
  * Base class for default methods as {@link #toString()} and such.
  * <p>
  * This class is immutable and thread safe.
- *
- * @since 3.6
  */
 public abstract class AbstractMatcher implements IMatcher {
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
index 8bb4dfb..61f7b83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
@@ -44,8 +44,6 @@
 
 /**
  * Generic string matcher
- *
- * @since 3.6
  */
 public interface IMatcher {
 
@@ -53,10 +51,12 @@ public interface IMatcher {
 	 * Matcher that does not match any pattern.
 	 */
 	public static final IMatcher NO_MATCH = new IMatcher() {
+		@Override
 		public boolean matches(String path, boolean assumeDirectory) {
 			return false;
 		}
 
+		@Override
 		public boolean matches(String segment, int startIncl, int endExcl,
 				boolean assumeDirectory) {
 			return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
index 3d0ad09..cc0fe93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
@@ -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,6 +55,7 @@ public class LeadingAsteriskMatcher extends NameMatcher {
 					"Pattern must have leading asterisk: " + pattern); //$NON-NLS-1$
 	}
 
+	@Override
 	public boolean matches(String segment, int startIncl, int endExcl,
 			boolean assumeDirectory) {
 		// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
index 8beae83..0065123 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
@@ -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 {
 
@@ -72,6 +70,7 @@ public class NameMatcher extends AbstractMatcher {
 			this.subPattern = pattern.substring(1);
 	}
 
+	@Override
 	public boolean matches(String path, boolean assumeDirectory) {
 		int end = 0;
 		int firstChar = 0;
@@ -88,6 +87,7 @@ public boolean matches(String path, boolean assumeDirectory) {
 		return false;
 	}
 
+	@Override
 	public boolean matches(String segment, int startIncl, int endExcl,
 			boolean assumeDirectory) {
 		// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
index c3f6694..65224ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -59,8 +59,6 @@
  * Matcher built by patterns consists of multiple path segments.
  * <p>
  * This class is immutable and thread safe.
- *
- * @since 3.6
  */
 public class PathMatcher extends AbstractMatcher {
 
@@ -92,7 +90,7 @@ private boolean isSimplePathWithSegments(String path) {
 	static private List<IMatcher> createMatchers(List<String> segments,
 			Character pathSeparator, boolean dirOnly)
 			throws InvalidPatternException {
-		List<IMatcher> matchers = new ArrayList<IMatcher>(segments.size());
+		List<IMatcher> matchers = new ArrayList<>(segments.size());
 		for (int i = 0; i < segments.size(); i++) {
 			String segment = segments.get(i);
 			IMatcher matcher = createNameMatcher0(segment, pathSeparator,
@@ -172,6 +170,7 @@ private static IMatcher createNameMatcher0(String segment,
 		}
 	}
 
+	@Override
 	public boolean matches(String path, boolean assumeDirectory) {
 		if (matchers == null)
 			return simpleMatch(path, assumeDirectory);
@@ -211,6 +210,7 @@ private boolean simpleMatch(String path, boolean assumeDirectory) {
 		return false;
 	}
 
+	@Override
 	public boolean matches(String segment, int startIncl, int endExcl,
 			boolean assumeDirectory) {
 		throw new UnsupportedOperationException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index 70c5199..da482fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -58,8 +58,6 @@
 /**
  * Various {@link String} related utility methods, written mostly to avoid
  * generation of new String objects (e.g. via splitting Strings etc).
- *
- * @since 3.6
  */
 public class Strings {
 
@@ -150,7 +148,7 @@ public static List<String> split(String pattern, char slash) {
 		if (count < 1)
 			throw new IllegalStateException(
 					"Pattern must have at least two segments: " + pattern); //$NON-NLS-1$
-		List<String> segments = new ArrayList<String>(count);
+		List<String> segments = new ArrayList<>(count);
 		int right = 0;
 		while (true) {
 			int left = right;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
index b927d27..2e148f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
@@ -44,8 +44,6 @@
 
 /**
  * Matcher for simple patterns ending with an asterisk, e.g. "Makefile.*"
- *
- * @since 3.6
  */
 public class TrailingAsteriskMatcher extends NameMatcher {
 
@@ -57,6 +55,7 @@ public class TrailingAsteriskMatcher extends NameMatcher {
 					"Pattern must have trailing asterisk: " + pattern); //$NON-NLS-1$
 	}
 
+	@Override
 	public boolean matches(String segment, int startIncl, int endExcl,
 			boolean assumeDirectory) {
 		// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
index 8f98152..f64050f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
@@ -53,8 +53,6 @@
  * glob wildcards to Java {@link Pattern}'s.
  * <p>
  * This class is immutable and thread safe.
- *
- * @since 3.6
  */
 public class WildCardMatcher extends NameMatcher {
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
index d578654..93ea13c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
@@ -47,8 +47,6 @@
  * matcher matches any path.
  * <p>
  * This class is immutable and thread safe.
- *
- * @since 3.6
  */
 public final class WildMatcher extends AbstractMatcher {
 
@@ -63,10 +61,12 @@ private WildMatcher() {
 		super(WILDMATCH, false);
 	}
 
+	@Override
 	public final boolean matches(String path, boolean assumeDirectory) {
 		return true;
 	}
 
+	@Override
 	public final boolean matches(String segment, int startIncl, int endExcl,
 			boolean assumeDirectory) {
 		return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index df2cc50..1bf55e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -90,6 +90,7 @@ public static JGitText get() {
 	/***/ public String badObjectType;
 	/***/ public String badRef;
 	/***/ public String badSectionEntry;
+	/***/ public String badShallowLine;
 	/***/ public String bareRepositoryNoWorkdirAndIndex;
 	/***/ public String base64InputNotProperlyPadded;
 	/***/ public String baseLengthIncorrect;
@@ -103,6 +104,7 @@ public static JGitText get() {
 	/***/ public String buildingBitmaps;
 	/***/ public String cachedPacksPreventsIndexCreation;
 	/***/ public String cachedPacksPreventsListingObjects;
+	/***/ public String cannotAccessLastModifiedForSafeDeletion;
 	/***/ public String cannotBeCombined;
 	/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
 	/***/ public String cannotChangeActionOnComment;
@@ -175,6 +177,7 @@ public static JGitText get() {
 	/***/ public String checkoutUnexpectedResult;
 	/***/ public String classCastNotA;
 	/***/ public String cloneNonEmptyDirectory;
+	/***/ public String closeLockTokenFailed;
 	/***/ public String closed;
 	/***/ public String collisionOn;
 	/***/ public String commandRejectedByHook;
@@ -341,6 +344,7 @@ public static JGitText get() {
 	/***/ public String expectedPktLineWithService;
 	/***/ public String expectedReceivedContentType;
 	/***/ public String expectedReportForRefNotReceived;
+	/***/ public String failedAtomicFileCreation;
 	/***/ public String failedToDetermineFilterDefinition;
 	/***/ public String failedUpdatingRefs;
 	/***/ public String failureDueToOneOfTheFollowing;
@@ -358,6 +362,8 @@ public static JGitText get() {
 	/***/ public String flagsAlreadyCreated;
 	/***/ public String funnyRefname;
 	/***/ public String gcFailed;
+	/***/ public String gcLogExists;
+	/***/ public String gcTooManyUnpruned;
 	/***/ public String gitmodulesNotFound;
 	/***/ public String headRequiredToStash;
 	/***/ public String hoursAgo;
@@ -400,6 +406,7 @@ public static JGitText get() {
 	/***/ public String invalidEncryption;
 	/***/ public String invalidExpandWildcard;
 	/***/ public String invalidGitdirRef;
+	/***/ public String invalidGitModules;
 	/***/ public String invalidGitType;
 	/***/ public String invalidId;
 	/***/ public String invalidId0;
@@ -411,6 +418,7 @@ public static JGitText get() {
 	/***/ public String invalidLineInConfigFile;
 	/***/ public String invalidModeFor;
 	/***/ public String invalidModeForPath;
+	/***/ public String invalidNameContainsDotDot;
 	/***/ public String invalidObject;
 	/***/ public String invalidOldIdSent;
 	/***/ public String invalidPacketLineHeader;
@@ -623,6 +631,7 @@ public static JGitText get() {
 	/***/ public String sequenceTooLargeForDiffAlgorithm;
 	/***/ public String serviceNotEnabledNoName;
 	/***/ public String serviceNotPermitted;
+	/***/ public String sha1CollisionDetected1;
 	/***/ public String shallowCommitsAlreadyInitialized;
 	/***/ public String shallowPacksRequireDepthWalk;
 	/***/ public String shortCompressedStreamAt;
@@ -657,8 +666,11 @@ public static JGitText get() {
 	/***/ 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;
@@ -669,6 +681,7 @@ public static JGitText get() {
 	/***/ public String theFactoryMustNotBeNull;
 	/***/ public String timeIsUncertain;
 	/***/ public String timerAlreadyTerminated;
+	/***/ public String tooManyCommands;
 	/***/ public String tooManyIncludeRecursions;
 	/***/ public String topologicalSortRequired;
 	/***/ public String transportExceptionBadRef;
@@ -698,6 +711,7 @@ public static JGitText get() {
 	/***/ public String tSizeMustBeGreaterOrEqual1;
 	/***/ public String unableToCheckConnectivity;
 	/***/ public String unableToCreateNewObject;
+	/***/ public String unableToRemovePath;
 	/***/ public String unableToStore;
 	/***/ public String unableToWrite;
 	/***/ public String unauthorized;
@@ -720,6 +734,7 @@ public static JGitText get() {
 	/***/ public String unknownRepositoryFormat;
 	/***/ public String unknownRepositoryFormat2;
 	/***/ public String unknownZlibError;
+	/***/ public String unlockLockFileFailed;
 	/***/ public String unmergedPath;
 	/***/ public String unmergedPaths;
 	/***/ public String unpackException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
index 35327ea..757c19a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
@@ -86,6 +86,7 @@ private void initRevWalk() {
 		rw.setRetainBody(false);
 	}
 
+	@Override
 	public void close() {
 		if (rw != null) {
 			rw.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java
index ddd7059..1eaa33d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java
@@ -122,6 +122,7 @@ private static boolean canCombine(@Nullable PersonIdent a,
 		return a == null && b == null;
 	}
 
+	@Override
 	void start() throws IOException {
 		for (Proposal p : todo) {
 			p.notifyState(RUNNING);
@@ -268,6 +269,7 @@ void abort() {
 		}
 	}
 
+	@Override
 	void success() {
 		for (Proposal p : todo) {
 			p.success();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
index 396fbdd..a0ac608 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
@@ -240,6 +240,7 @@ private static void abort(List<RemoteCommand> cmds) {
 		ReceiveCommand.abort(tmp);
 	}
 
+	@Override
 	protected void blockingFetch(Repository repo, ReplicaFetchRequest req)
 			throws NotSupportedException, TransportException {
 		try (Transport transport = Transport.open(repo, uri)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java
index 047c86f..9f7f350 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedListener.java
@@ -47,8 +47,6 @@
 
 /**
  * Receives {@link BeforeDfsPackIndexLoadedEvent}s.
- *
- * @since 2.2
  */
 public interface BeforeDfsPackIndexLoadedListener extends RepositoryListener {
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index f7decf1..ef0b80c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -183,7 +183,7 @@ private DfsBlockCache(final DfsBlockCacheConfig cfg) {
 		if (tableSize < 1)
 			throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
 
-		table = new AtomicReferenceArray<HashEntry>(tableSize);
+		table = new AtomicReferenceArray<>(tableSize);
 		loadLocks = new ReentrantLock[cfg.getConcurrencyLevel()];
 		for (int i = 0; i < loadLocks.length; i++)
 			loadLocks[i] = new ReentrantLock(true /* fair */);
@@ -194,10 +194,10 @@ private DfsBlockCache(final DfsBlockCacheConfig cfg) {
 		blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
 
 		clockLock = new ReentrantLock(true /* fair */);
-		clockHand = new Ref<Object>(new DfsPackKey(), -1, 0, null);
+		clockHand = new Ref<>(new DfsPackKey(), -1, 0, null);
 		clockHand.next = clockHand;
 
-		packCache = new ConcurrentHashMap<DfsPackDescription, DfsPackFile>(
+		packCache = new ConcurrentHashMap<>(
 				16, 0.75f, 1);
 		packFiles = Collections.unmodifiableCollection(packCache.values());
 
@@ -357,7 +357,7 @@ DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx)
 			}
 
 			key.cachedSize.addAndGet(v.size());
-			Ref<DfsBlock> ref = new Ref<DfsBlock>(key, position, v.size(), v);
+			Ref<DfsBlock> ref = new Ref<>(key, position, v.size(), v);
 			ref.hot = true;
 			for (;;) {
 				HashEntry n = new HashEntry(clean(e2), ref);
@@ -461,7 +461,7 @@ <T> Ref<T> put(DfsPackKey key, long pos, int size, T v) {
 			}
 
 			key.cachedSize.addAndGet(size);
-			ref = new Ref<T>(key, pos, size, v);
+			ref = new Ref<>(key, pos, size, v);
 			ref.hot = true;
 			for (;;) {
 				HashEntry n = new HashEntry(clean(e2), ref);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index 089bfa4..feadedb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -122,7 +122,6 @@ public DfsBlockCacheConfig setBlockSize(final int newSize) {
 	/**
 	 * @return the estimated number of threads concurrently accessing the cache.
 	 *         <b>Default is 32.</b>
-	 * @since 4.6
 	 */
 	public int getConcurrencyLevel() {
 		return concurrencyLevel;
@@ -133,7 +132,6 @@ public int getConcurrencyLevel() {
 	 *            the estimated number of threads concurrently accessing the
 	 *            cache.
 	 * @return {@code this}
-	 * @since 4.6
 	 */
 	public DfsBlockCacheConfig setConcurrencyLevel(
 			final int newConcurrencyLevel) {
@@ -145,7 +143,6 @@ public DfsBlockCacheConfig setConcurrencyLevel(
 	 * @return highest percentage of {@link #getBlockLimit()} a single pack can
 	 *         occupy while being copied by the pack reuse strategy. <b>Default
 	 *         is 0.30, or 30%</b>.
-	 * @since 4.0
 	 */
 	public double getStreamRatio() {
 		return streamRatio;
@@ -155,7 +152,6 @@ public double getStreamRatio() {
 	 * @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));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index 6f760ca..d380302 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -43,9 +43,12 @@
 
 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.pack.PackExt.BITMAP_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
@@ -53,8 +56,11 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
+import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -63,6 +69,7 @@
 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.reftree.RefTreeNames;
@@ -77,6 +84,7 @@
 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. */
@@ -95,7 +103,8 @@ public class DfsGarbageCollector {
 
 	private PackConfig packConfig;
 
-	// See pack(), below, for how these two variables interact.
+	// See packIsCoalesceableGarbage(), below, for how these two variables
+	// interact.
 	private long coalesceGarbageLimit = 50 << 20;
 	private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);
 
@@ -118,9 +127,9 @@ public DfsGarbageCollector(DfsRepository repository) {
 		repo = repository;
 		refdb = repo.getRefDatabase();
 		objdb = repo.getObjectDatabase();
-		newPackDesc = new ArrayList<DfsPackDescription>(4);
-		newPackStats = new ArrayList<PackStatistics>(4);
-		newPackObj = new ArrayList<ObjectIdSet>(4);
+		newPackDesc = new ArrayList<>(4);
+		newPackStats = new ArrayList<>(4);
+		newPackObj = new ArrayList<>(4);
 
 		packConfig = new PackConfig(repo);
 		packConfig.setIndexVersion(2);
@@ -223,14 +232,8 @@ public boolean pack(ProgressMonitor pm) throws IOException {
 		if (packConfig.getIndexVersion() != 2)
 			throw new IllegalStateException(
 					JGitText.get().supportOnlyPackIndexVersion2);
-		if (garbageTtlMillis > 0) {
-			// We disable coalescing because the coalescing step will keep
-			// refreshing the UNREACHABLE_GARBAGE pack and we wouldn't
-			// actually prune anything.
-			coalesceGarbageLimit = 0;
-		}
 
-		startTimeMillis = System.currentTimeMillis();
+		startTimeMillis = SystemReader.getInstance().getCurrentTime();
 		ctx = (DfsReader) objdb.newReader();
 		try {
 			refdb.refresh();
@@ -246,14 +249,14 @@ public boolean pack(ProgressMonitor pm) throws IOException {
 				return true;
 			}
 
-			allHeads = new HashSet<ObjectId>();
-			nonHeads = new HashSet<ObjectId>();
-			txnHeads = new HashSet<ObjectId>();
-			tagTargets = new HashSet<ObjectId>();
+			allHeads = 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) || isTag(ref))
 					allHeads.add(ref.getObjectId());
 				else if (RefTreeNames.isRefTree(refdb, ref.getName()))
 					txnHeads.add(ref.getObjectId());
@@ -301,18 +304,18 @@ private Collection<Ref> getAllRefs() throws IOException {
 
 	private void readPacksBefore() throws IOException {
 		DfsPackFile[] packs = objdb.getPacks();
-		packsBefore = new ArrayList<DfsPackFile>(packs.length);
-		expiredGarbagePacks = new ArrayList<DfsPackFile>(packs.length);
+		packsBefore = new ArrayList<>(packs.length);
+		expiredGarbagePacks = new ArrayList<>(packs.length);
 
 		long mostRecentGC = mostRecentGC(packs);
-		long now = System.currentTimeMillis();
+		long now = SystemReader.getInstance().getCurrentTime();
 		for (DfsPackFile p : packs) {
 			DfsPackDescription d = p.getPackDescription();
 			if (d.getPackSource() != UNREACHABLE_GARBAGE) {
 				packsBefore.add(p);
 			} else if (packIsExpiredGarbage(d, mostRecentGC, now)) {
 				expiredGarbagePacks.add(p);
-			} else if (d.getFileSize(PackExt.PACK) < coalesceGarbageLimit) {
+			} else if (packIsCoalesceableGarbage(d, now)) {
 				packsBefore.add(p);
 			}
 		}
@@ -355,6 +358,68 @@ private boolean packIsExpiredGarbage(DfsPackDescription d,
 				&& 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.
+		}
+
+		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<DfsPackDescription> getSourcePacks() {
 		return toPrune();
@@ -372,7 +437,7 @@ public List<PackStatistics> getNewPackStatistics() {
 
 	private List<DfsPackDescription> toPrune() {
 		int cnt = packsBefore.size();
-		List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt);
+		List<DfsPackDescription> all = new ArrayList<>(cnt);
 		for (DfsPackFile pack : packsBefore) {
 			all.add(pack.getPackDescription());
 		}
@@ -390,7 +455,8 @@ private void packHeads(ProgressMonitor pm) throws IOException {
 			pw.setTagTargets(tagTargets);
 			pw.preparePack(pm, allHeads, PackWriter.NONE);
 			if (0 < pw.getObjectCount())
-				writePack(GC, pw, pm);
+				writePack(GC, pw, pm,
+						estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC));
 		}
 	}
 
@@ -403,7 +469,8 @@ private void packRest(ProgressMonitor pm) throws IOException {
 				pw.excludeObjects(packedObjs);
 			pw.preparePack(pm, nonHeads, allHeads);
 			if (0 < pw.getObjectCount())
-				writePack(GC_REST, pw, pm);
+				writePack(GC_REST, pw, pm,
+						estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST));
 		}
 	}
 
@@ -416,7 +483,7 @@ private void packRefTreeGraph(ProgressMonitor pm) throws IOException {
 				pw.excludeObjects(packedObjs);
 			pw.preparePack(pm, txnHeads, PackWriter.NONE);
 			if (0 < pw.getObjectCount())
-				writePack(GC_TXN, pw, pm);
+				writePack(GC_TXN, pw, pm, 0 /* unknown pack size */);
 		}
 	}
 
@@ -432,21 +499,29 @@ private void packGarbage(ProgressMonitor pm) throws IOException {
 			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);
 		}
 	}
 
@@ -461,6 +536,10 @@ 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 int objectsBefore() {
 		int cnt = 0;
 		for (DfsPackFile p : packsBefore)
@@ -475,9 +554,24 @@ private PackWriter newPackWriter() {
 		return pw;
 	}
 
+	private long estimateGcPackSize(PackSource first, PackSource... rest) {
+		EnumSet<PackSource> 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 {
-		DfsPackDescription pack = repo.getObjectDatabase().newPack(source);
+			ProgressMonitor pm, long estimatedPackSize) throws IOException {
+		DfsPackDescription pack = repo.getObjectDatabase().newPack(source,
+				estimatedPackSize);
 		newPackDesc.add(pack);
 
 		try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
index c179e77..fd72756 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
@@ -90,6 +90,7 @@
 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. */
 public class DfsInserter extends ObjectInserter {
@@ -168,7 +169,7 @@ public ObjectId insert(int type, long len, InputStream in)
 		}
 
 		long offset = beginObject(type, len);
-		MessageDigest md = digest();
+		SHA1 md = digest();
 		md.update(Constants.encodedTypeString(type));
 		md.update((byte) ' ');
 		md.update(Constants.encodeASCII(len));
@@ -183,7 +184,7 @@ public ObjectId insert(int type, long len, InputStream in)
 			len -= n;
 		}
 		packOut.compress.finish();
-		return endObject(ObjectId.fromRaw(md.digest()), offset);
+		return endObject(md.toObjectId(), offset);
 	}
 
 	private byte[] insertBuffer(long len) {
@@ -274,8 +275,8 @@ private ObjectId endObject(ObjectId id, long offset) {
 	}
 
 	private void beginPack() throws IOException {
-		objectList = new BlockList<PackedObjectInfo>();
-		objectMap = new ObjectIdOwnerMap<PackedObjectInfo>();
+		objectList = new BlockList<>();
+		objectMap = new ObjectIdOwnerMap<>();
 		cache = DfsBlockCache.getInstance();
 
 		rollback = true;
@@ -543,7 +544,7 @@ public Collection<ObjectId> resolve(AbbreviatedObjectId id)
 			if (objectList == null)
 				return stored;
 
-			Set<ObjectId> r = new HashSet<ObjectId>(stored.size() + 2);
+			Set<ObjectId> r = new HashSet<>(stored.size() + 2);
 			r.addAll(stored);
 			for (PackedObjectInfo obj : objectList) {
 				if (id.prefixCompare(obj) == 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
index b1d6c0d..b1cb72d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
@@ -160,7 +160,7 @@ public static enum PackSource {
 	protected DfsObjDatabase(DfsRepository repository,
 			DfsReaderOptions options) {
 		this.repository = repository;
-		this.packList = new AtomicReference<PackList>(NO_PACKS);
+		this.packList = new AtomicReference<>(NO_PACKS);
 		this.readerOptions = options;
 	}
 
@@ -266,6 +266,32 @@ protected abstract DfsPackDescription newPack(PackSource source)
 			throws IOException;
 
 	/**
+	 * Generate a new unique name for a pack file.
+	 *
+	 * <p>
+	 * 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 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 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.
 	 * <p>
 	 * Committing the pack/index pair makes them visible to readers. The JGit
@@ -432,7 +458,7 @@ private PackList scanPacksImpl(PackList old) throws IOException {
 		List<DfsPackDescription> scanned = listPacks();
 		Collections.sort(scanned);
 
-		List<DfsPackFile> list = new ArrayList<DfsPackFile>(scanned.size());
+		List<DfsPackFile> list = new ArrayList<>(scanned.size());
 		boolean foundNew = false;
 		for (DfsPackDescription dsc : scanned) {
 			DfsPackFile oldPack = forReuse.remove(dsc);
@@ -457,7 +483,7 @@ private PackList scanPacksImpl(PackList old) throws IOException {
 
 	private static Map<DfsPackDescription, DfsPackFile> reuseMap(PackList old) {
 		Map<DfsPackDescription, DfsPackFile> forReuse
-			= new HashMap<DfsPackDescription, DfsPackFile>();
+			= new HashMap<>();
 		for (DfsPackFile p : old.packs) {
 			if (p.invalid()) {
 				// The pack instance is corrupted, and cannot be safely used
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
index 7edae88..ddcfff6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
@@ -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;
 
@@ -76,6 +74,13 @@ public ObjectId getDeltaBase() {
 
 	@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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index 11aef7f..0a29ac5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -113,10 +113,10 @@ public class DfsPackCompactor {
 	public DfsPackCompactor(DfsRepository repository) {
 		repo = repository;
 		autoAddSize = 5 * 1024 * 1024; // 5 MiB
-		srcPacks = new ArrayList<DfsPackFile>();
-		exclude = new ArrayList<ObjectIdSet>(4);
-		newPacks = new ArrayList<DfsPackDescription>(1);
-		newStats = new ArrayList<PackStatistics>(1);
+		srcPacks = new ArrayList<>();
+		exclude = new ArrayList<>(4);
+		newPacks = new ArrayList<>(1);
+		newStats = new ArrayList<>(1);
 	}
 
 	/**
@@ -224,7 +224,8 @@ public void compact(ProgressMonitor pm) throws IOException {
 				}
 
 				boolean rollback = true;
-				DfsPackDescription pack = objdb.newPack(COMPACT);
+				DfsPackDescription pack = objdb.newPack(COMPACT,
+						estimatePackSize());
 				try {
 					writePack(objdb, pack, pw, pm);
 					writeIndex(objdb, pack, pw);
@@ -251,6 +252,17 @@ public void compact(ProgressMonitor pm) throws IOException {
 		}
 	}
 
+	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;
+	}
+
 	/** @return all of the source packs that fed into this compaction. */
 	public List<DfsPackDescription> getSourcePacks() {
 		return toPrune();
@@ -268,7 +280,7 @@ public List<PackStatistics> getNewPackStatistics() {
 
 	private List<DfsPackDescription> toPrune() {
 		int cnt = srcPacks.size();
-		List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt);
+		List<DfsPackDescription> all = new ArrayList<>(cnt);
 		for (DfsPackFile pack : srcPacks)
 			all.add(pack.getPackDescription());
 		return all;
@@ -281,6 +293,7 @@ private void addObjectsToPack(PackWriter pw, DfsReader ctx,
 		// older packs, allowing the PackWriter to be handed newer objects
 		// first and older objects last.
 		Collections.sort(srcPacks, new Comparator<DfsPackFile>() {
+			@Override
 			public int compare(DfsPackFile a, DfsPackFile b) {
 				return a.getPackDescription().compareTo(b.getPackDescription());
 			}
@@ -289,7 +302,7 @@ public int compare(DfsPackFile a, DfsPackFile b) {
 		rw = new RevWalk(ctx);
 		added = rw.newFlag("ADDED"); //$NON-NLS-1$
 		isBase = rw.newFlag("IS_BASE"); //$NON-NLS-1$
-		List<RevObject> baseObjects = new BlockList<RevObject>();
+		List<RevObject> baseObjects = new BlockList<>();
 
 		pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
 		for (DfsPackFile src : srcPacks) {
@@ -333,7 +346,7 @@ public int compare(DfsPackFile a, DfsPackFile b) {
 	private List<ObjectIdWithOffset> toInclude(DfsPackFile src, DfsReader ctx)
 			throws IOException {
 		PackIndex srcIdx = src.getPackIndex(ctx);
-		List<ObjectIdWithOffset> want = new BlockList<ObjectIdWithOffset>(
+		List<ObjectIdWithOffset> want = new BlockList<>(
 				(int) srcIdx.getObjectCount());
 		SCAN: for (PackIndex.MutableEntry ent : srcIdx) {
 			ObjectId id = ent.toObjectId();
@@ -346,6 +359,7 @@ private List<ObjectIdWithOffset> toInclude(DfsPackFile src, DfsReader ctx)
 			want.add(new ObjectIdWithOffset(id, ent.getOffset()));
 		}
 		Collections.sort(want, new Comparator<ObjectIdWithOffset>() {
+			@Override
 			public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) {
 				return Long.signum(a.offset - b.offset);
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
index 2b9d0e5..e825f1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
@@ -81,6 +81,8 @@ public class DfsPackDescription implements Comparable<DfsPackDescription> {
 
 	private int indexVersion;
 
+	private long estimatedPackSize;
+
 	/**
 	 * Initialize a description by pack name and repository.
 	 * <p>
@@ -100,7 +102,7 @@ public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) {
 		this.repoDesc = repoDesc;
 		int dot = name.lastIndexOf('.');
 		this.packName = (dot < 0) ? name : name.substring(0, dot);
-		this.sizeMap = new HashMap<PackExt, Long>(PackExt.values().length * 2);
+		this.sizeMap = new HashMap<>(PackExt.values().length * 2);
 	}
 
 	/** @return description of the repository. */
@@ -189,6 +191,25 @@ public long getFileSize(PackExt ext) {
 		return size == null ? 0 : size.longValue();
 	}
 
+	/**
+	 * @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;
+	}
+
+	/**
+	 * @return estimated size of the .pack file in bytes. If 0 the pack file
+	 *         size is unknown.
+	 */
+	public long getEstimatedPackSize() {
+		return estimatedPackSize;
+	}
+
 	/** @return number of objects in the pack. */
 	public long getObjectCount() {
 		return objectCount;
@@ -288,6 +309,7 @@ public boolean equals(Object b) {
 	 * @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 +320,17 @@ public int compareTo(DfsPackDescription b) {
 				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 +342,17 @@ public int compareTo(DfsPackDescription b) {
 		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;
+		}
+	}
+
 	@Override
 	public String toString() {
 		return getFileName(PackExt.PACK);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index 4eabb03..f15d427 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -187,7 +187,6 @@ public DfsPackDescription getPackDescription() {
 
 	/**
 	 * @return whether the pack index file is loaded and cached in memory.
-	 * @since 2.2
 	 */
 	public boolean isIndexLoaded() {
 		DfsBlockCache.Ref<PackIndex> idxref = index;
@@ -499,6 +498,7 @@ private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
 				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 +508,7 @@ private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
 					position += n;
 					remaining -= n;
 					rc.position(position);
+					packHeadSkipped = true;
 					continue;
 				}
 
@@ -517,7 +518,14 @@ private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
 					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;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index 8c93295..755b163 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -44,10 +44,12 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -64,6 +66,7 @@
 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;
@@ -146,6 +149,7 @@ public BitmapIndex getBitmapIndex() throws IOException {
 		return null;
 	}
 
+	@Override
 	public Collection<CachedPack> getCachedPacksAndUpdate(
 		BitmapBuilder needBitmap) throws IOException {
 		for (DfsPackFile pack : db.getPacks()) {
@@ -162,20 +166,19 @@ public Collection<ObjectId> resolve(AbbreviatedObjectId id)
 			throws IOException {
 		if (id.isComplete())
 			return Collections.singleton(id.toObjectId());
-		boolean noGarbage = avoidUnreachable;
-		HashSet<ObjectId> matches = new HashSet<ObjectId>(4);
+		HashSet<ObjectId> matches = new HashSet<>(4);
 		PackList packList = db.getPackList();
-		resolveImpl(packList, id, noGarbage, matches);
+		resolveImpl(packList, id, matches);
 		if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) {
-			resolveImpl(db.scanPacks(packList), id, noGarbage, matches);
+			resolveImpl(db.scanPacks(packList), id, matches);
 		}
 		return matches;
 	}
 
 	private void resolveImpl(PackList packList, AbbreviatedObjectId id,
-			boolean noGarbage, HashSet<ObjectId> matches) throws IOException {
+			HashSet<ObjectId> matches) throws IOException {
 		for (DfsPackFile pack : packList.packs) {
-			if (noGarbage && pack.isGarbage()) {
+			if (skipGarbagePack(pack)) {
 				continue;
 			}
 			pack.resolve(this, matches, id, MAX_RESOLVE_MATCHES);
@@ -187,22 +190,23 @@ private void resolveImpl(PackList packList, AbbreviatedObjectId id,
 
 	@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;
 		PackList packList = db.getPackList();
-		if (hasImpl(packList, objectId, noGarbage)) {
+		if (hasImpl(packList, objectId)) {
 			return true;
 		} else if (packList.dirty()) {
-			return hasImpl(db.scanPacks(packList), objectId, noGarbage);
+			return hasImpl(db.scanPacks(packList), objectId);
 		}
 		return false;
 	}
 
-	private boolean hasImpl(PackList packList, AnyObjectId objectId,
-			boolean noGarbage) throws IOException {
+	private boolean hasImpl(PackList packList, AnyObjectId objectId)
+			throws IOException {
 		for (DfsPackFile pack : packList.packs) {
-			if (pack == last || (noGarbage && pack.isGarbage()))
+			if (pack == last || skipGarbagePack(pack))
 				continue;
 			if (pack.hasObject(this, objectId)) {
 				last = pack;
@@ -217,7 +221,7 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
 		ObjectLoader ldr;
-		if (last != null) {
+		if (last != null && !skipGarbagePack(last)) {
 			ldr = last.get(this, objectId);
 			if (ldr != null) {
 				return checkType(ldr, objectId, typeHint);
@@ -225,13 +229,12 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
 		}
 
 		PackList packList = db.getPackList();
-		boolean noGarbage = avoidUnreachable;
-		ldr = openImpl(packList, objectId, noGarbage);
+		ldr = openImpl(packList, objectId);
 		if (ldr != null) {
 			return checkType(ldr, objectId, typeHint);
 		}
 		if (packList.dirty()) {
-			ldr = openImpl(db.scanPacks(packList), objectId, noGarbage);
+			ldr = openImpl(db.scanPacks(packList), objectId);
 			if (ldr != null) {
 				return checkType(ldr, objectId, typeHint);
 			}
@@ -251,10 +254,10 @@ private static ObjectLoader checkType(ObjectLoader ldr, AnyObjectId id,
 		return ldr;
 	}
 
-	private ObjectLoader openImpl(PackList packList, AnyObjectId objectId,
-			boolean noGarbage) throws IOException {
+	private ObjectLoader openImpl(PackList packList, AnyObjectId objectId)
+			throws IOException {
 		for (DfsPackFile pack : packList.packs) {
-			if (pack == last || (noGarbage && pack.isGarbage())) {
+			if (pack == last || skipGarbagePack(pack)) {
 				continue;
 			}
 			ObjectLoader ldr = pack.get(this, objectId);
@@ -272,6 +275,7 @@ public Set<ObjectId> getShallowCommits() {
 	}
 
 	private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = new Comparator<FoundObject<?>>() {
+		@Override
 		public int compare(FoundObject<?> a, FoundObject<?> b) {
 			int cmp = a.packIndex - b.packIndex;
 			if (cmp == 0)
@@ -315,7 +319,7 @@ private <T extends ObjectId> Iterable<FoundObject<T>> findAll(
 			findAllImpl(db.scanPacks(packList), pending, r);
 		}
 		for (T t : pending) {
-			r.add(new FoundObject<T>(t));
+			r.add(new FoundObject<>(t));
 		}
 		Collections.sort(r, FOUND_OBJECT_SORT);
 		return r;
@@ -329,31 +333,32 @@ private <T extends ObjectId> void findAllImpl(PackList packList,
 		}
 		int lastIdx = 0;
 		DfsPackFile lastPack = packs[lastIdx];
-		boolean noGarbage = avoidUnreachable;
 
 		OBJECT_SCAN: for (Iterator<T> it = pending.iterator(); it.hasNext();) {
 			T t = it.next();
-			try {
-				long p = lastPack.findOffset(this, t);
-				if (0 < p) {
-					r.add(new FoundObject<T>(t, lastIdx, lastPack, p));
-					it.remove();
-					continue;
+			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 < packs.length; i++) {
 				if (i == lastIdx)
 					continue;
 				DfsPackFile pack = packs[i];
-				if (noGarbage && pack.isGarbage())
+				if (skipGarbagePack(pack))
 					continue;
 				try {
 					long p = pack.findOffset(this, t);
 					if (0 < p) {
-						r.add(new FoundObject<T>(t, i, pack, p));
+						r.add(new FoundObject<>(t, i, pack, p));
 						it.remove();
 						lastIdx = i;
 						lastPack = pack;
@@ -368,6 +373,10 @@ private <T extends ObjectId> void findAllImpl(PackList packList,
 		last = lastPack;
 	}
 
+	private boolean skipGarbagePack(DfsPackFile pack) {
+		return avoidUnreachable && pack.isGarbage();
+	}
+
 	@Override
 	public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
 			Iterable<T> objectIds, final boolean reportMissing) {
@@ -385,6 +394,7 @@ public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
 		return new AsyncObjectLoaderQueue<T>() {
 			private FoundObject<T> cur;
 
+			@Override
 			public boolean next() throws MissingObjectException, IOException {
 				if (idItr.hasNext()) {
 					cur = idItr.next();
@@ -396,14 +406,17 @@ public boolean next() throws MissingObjectException, IOException {
 				}
 			}
 
+			@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,
@@ -411,10 +424,12 @@ public ObjectLoader open() throws IOException {
 				return cur.pack.load(DfsReader.this, cur.offset);
 			}
 
+			@Override
 			public boolean cancel(boolean mayInterruptIfRunning) {
 				return true;
 			}
 
+			@Override
 			public void release() {
 				// Nothing to clean up.
 			}
@@ -440,6 +455,7 @@ public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize(
 
 			private long sz;
 
+			@Override
 			public boolean next() throws MissingObjectException, IOException {
 				if (idItr.hasNext()) {
 					cur = idItr.next();
@@ -455,22 +471,27 @@ public boolean next() throws MissingObjectException, IOException {
 				}
 			}
 
+			@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.
 			}
@@ -481,7 +502,7 @@ public void release() {
 	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) {
 				return sz;
@@ -510,7 +531,7 @@ public long getObjectSize(AnyObjectId objectId, int typeHint)
 	private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
 			throws IOException {
 		for (DfsPackFile pack : packList.packs) {
-			if (pack == last) {
+			if (pack == last || skipGarbagePack(pack)) {
 				continue;
 			}
 			long sz = pack.getObjectSize(this, objectId);
@@ -522,22 +543,25 @@ private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
 		return -1;
 	}
 
+	@Override
 	public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
 		return new DfsObjectToPack(objectId, type);
 	}
 
 	private static final Comparator<DfsObjectToPack> OFFSET_SORT = new Comparator<DfsObjectToPack>() {
+		@Override
 		public int compare(DfsObjectToPack a, DfsObjectToPack b) {
 			return Long.signum(a.getOffset() - b.getOffset());
 		}
 	};
 
+	@Override
 	public void selectObjectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
 			throws IOException, MissingObjectException {
 		// Don't check dirty bit on PackList; assume ObjectToPacks all came from the
 		// current list.
-		for (DfsPackFile pack : db.getPacks()) {
+		for (DfsPackFile pack : sortPacksForSelectRepresentation()) {
 			List<DfsObjectToPack> tmp = findAllFromPack(pack, objects);
 			if (tmp.isEmpty())
 				continue;
@@ -556,9 +580,39 @@ public void selectObjectRepresentation(PackWriter packer,
 		}
 	}
 
+	private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE = new Comparator<DfsPackFile>() {
+		@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 DfsPackFile[] sortPacksForSelectRepresentation()
+			throws IOException {
+		DfsPackFile[] packs = db.getPacks();
+		DfsPackFile[] sorted = new DfsPackFile[packs.length];
+		System.arraycopy(packs, 0, sorted, 0, packs.length);
+		Arrays.sort(sorted, PACK_SORT_FOR_REUSE);
+		return sorted;
+	}
+
 	private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
 			Iterable<ObjectToPack> objects) throws IOException {
-		List<DfsObjectToPack> tmp = new BlockList<DfsObjectToPack>();
+		List<DfsObjectToPack> tmp = new BlockList<>();
 		PackIndex idx = pack.getPackIndex(this);
 		for (ObjectToPack otp : objects) {
 			long p = idx.findOffset(otp);
@@ -570,6 +624,7 @@ private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
 		return tmp;
 	}
 
+	@Override
 	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 			boolean validate) throws IOException,
 			StoredObjectRepresentationNotAvailableException {
@@ -577,12 +632,14 @@ public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 		src.pack.copyAsIs(out, src, validate, this);
 	}
 
+	@Override
 	public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
 			throws IOException {
 		for (ObjectToPack otp : list)
 			out.writeObject(otp);
 	}
 
+	@Override
 	public void copyPackAsIs(PackOutputStream out, CachedPack pack)
 			throws IOException {
 		((DfsCachedPack) pack).copyAsIs(out, this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 8419807..d07c13d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -109,7 +109,6 @@ public DfsReaderOptions setStreamFileThreshold(final int newLimit) {
 	/**
 	 * @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;
@@ -120,7 +119,6 @@ public int getStreamPackBufferSize() {
 	 *            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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
index 4ddcec1..b41c18b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
@@ -78,7 +78,7 @@ public abstract class DfsRefDatabase extends RefDatabase {
 	 */
 	protected DfsRefDatabase(DfsRepository repository) {
 		this.repository = repository;
-		this.cache = new AtomicReference<RefCache>();
+		this.cache = new AtomicReference<>();
 	}
 
 	/** @return the repository the database holds the references of. */
@@ -120,7 +120,7 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
 		RefCache curr = read();
 		RefList<Ref> packed = RefList.emptyList();
 		RefList<Ref> loose = curr.ids;
-		RefList.Builder<Ref> sym = new RefList.Builder<Ref>(curr.sym.size());
+		RefList.Builder<Ref> sym = new RefList.Builder<>(curr.sym.size());
 
 		for (int idx = 0; idx < curr.sym.size(); idx++) {
 			Ref ref = curr.sym.get(idx);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index ef88450..a5dd514 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -143,10 +143,12 @@ 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;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index fd21397..527e46b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -63,7 +63,6 @@ public InMemoryRepository build() throws IOException {
 	 *
 	 * @param repoDesc
 	 *            description of the repository.
-	 * @since 2.0
 	 */
 	public InMemoryRepository(DfsRepositoryDescription repoDesc) {
 		this(new Builder().setRepositoryDescription(repoDesc));
@@ -108,7 +107,7 @@ public void setGitwebDescription(@Nullable String d) {
 	}
 
 	private class MemObjDatabase extends DfsObjDatabase {
-		private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>();
+		private List<DfsPackDescription> packs = new ArrayList<>();
 
 		MemObjDatabase(DfsRepository repo) {
 			super(repo, new DfsReaderOptions());
@@ -133,7 +132,7 @@ protected synchronized void commitPackImpl(
 				Collection<DfsPackDescription> desc,
 				Collection<DfsPackDescription> replace) {
 			List<DfsPackDescription> n;
-			n = new ArrayList<DfsPackDescription>(desc.size() + packs.size());
+			n = new ArrayList<>(desc.size() + packs.size());
 			n.addAll(desc);
 			n.addAll(packs);
 			if (replace != null)
@@ -171,7 +170,7 @@ public void flush() {
 
 	private static class MemPack extends DfsPackDescription {
 		final Map<PackExt, byte[]>
-				fileMap = new HashMap<PackExt, byte[]>();
+				fileMap = new HashMap<>();
 
 		MemPack(String name, DfsRepositoryDescription repoDesc) {
 			super(repoDesc, name);
@@ -226,6 +225,7 @@ private static class ByteArrayReadableChannel implements ReadableChannel {
 			data = buf;
 		}
 
+		@Override
 		public int read(ByteBuffer dst) {
 			int n = Math.min(dst.remaining(), data.length - position);
 			if (n == 0)
@@ -235,30 +235,37 @@ public int read(ByteBuffer dst) {
 			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;
 		}
 
+		@Override
 		public void setReadAheadBytes(int b) {
 			// Unnecessary on a byte array.
 		}
@@ -271,7 +278,7 @@ public void setReadAheadBytes(int b) {
 	 * subclasses of InMemoryRepository.
 	 */
     protected class MemRefDatabase extends DfsRefDatabase {
-		private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>();
+		private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<>();
 		private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */);
 
 		/**
@@ -308,8 +315,8 @@ public void execute(RevWalk walk, ProgressMonitor monitor)
 
 		@Override
 		protected RefCache scanAllRefs() throws IOException {
-			RefList.Builder<Ref> ids = new RefList.Builder<Ref>();
-			RefList.Builder<Ref> sym = new RefList.Builder<Ref>();
+			RefList.Builder<Ref> ids = new RefList.Builder<>();
+			RefList.Builder<Ref> sym = new RefList.Builder<>();
 			try {
 				lock.readLock().lock();
 				for (Ref ref : refs.values()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
index 30e973e..b78ff2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
@@ -58,6 +58,7 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex {
 		this.bitmaps = bitmaps;
 	}
 
+	@Override
 	public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) {
 		StoredBitmap sb = bitmaps.get(objectId);
 		return sb != null ? sb.getBitmap() : null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
index b18a06f..88eef4c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
@@ -370,6 +370,7 @@ public Iterator<BitmapObject> iterator() {
 				private int type;
 				private IntIterator cached = dynamic;
 
+				@Override
 				public boolean hasNext() {
 					if (!cached.hasNext()) {
 						if (commits.hasNext()) {
@@ -391,6 +392,7 @@ public boolean hasNext() {
 					return true;
 				}
 
+				@Override
 				public BitmapObject next() {
 					if (!hasNext())
 						throw new NoSuchElementException();
@@ -408,6 +410,7 @@ public BitmapObject next() {
 					return out;
 				}
 
+				@Override
 				public void remove() {
 					throw new UnsupportedOperationException();
 				}
@@ -439,10 +442,10 @@ private EWAHCompressedBitmap ewahBitmap(Bitmap other) {
 
 	private static final class MutableBitmapIndex {
 		private final ObjectIdOwnerMap<MutableEntry>
-				revMap = new ObjectIdOwnerMap<MutableEntry>();
+				revMap = new ObjectIdOwnerMap<>();
 
 		private final BlockList<MutableEntry>
-				revList = new BlockList<MutableEntry>();
+				revList = new BlockList<>();
 
 		int findPosition(AnyObjectId objectId) {
 			MutableEntry entry = revMap.get(objectId);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
index a81d8ec..d47b304 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
@@ -90,7 +90,7 @@ class CachedObjectDirectory extends FileObjectDatabase {
 	}
 
 	private ObjectIdOwnerMap<UnpackedObjectId> scanLoose() {
-		ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<UnpackedObjectId>();
+		ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<>();
 		File objects = wrapped.getDirectory();
 		String[] fanout = objects.list();
 		if (fanout == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
index e968119..4b4337d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
@@ -65,10 +65,12 @@ public class CheckoutEntryImpl implements CheckoutEntry {
 		to = comment.substring(p2 + " to ".length(), p3); //$NON-NLS-1$
 	}
 
+	@Override
 	public String getFromBranch() {
 		return from;
 	}
 
+	@Override
 	public String getToBranch() {
 		return to;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
index a95dea7..b397989 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
@@ -69,7 +69,7 @@ private static int hash(final long position) {
 	private int openByteCount;
 
 	static {
-		DEAD = new SoftReference<Entry>(null);
+		DEAD = new SoftReference<>(null);
 		reconfigure(new WindowCacheConfig());
 	}
 
@@ -115,7 +115,7 @@ void store(final PackFile pack, final long position,
 		e.provider = pack;
 		e.position = position;
 		e.sz = data.length;
-		e.data = new SoftReference<Entry>(new Entry(data, objectType));
+		e.data = new SoftReference<>(new Entry(data, objectType));
 		moveToHead(e);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 0388acb..ef9aa37 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -55,6 +55,7 @@
 import java.text.MessageFormat;
 import java.text.ParseException;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 
@@ -342,7 +343,7 @@ && getDirectory().getName().startsWith(".")) //$NON-NLS-1$
 		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,
@@ -489,7 +490,7 @@ private File descriptionFile() {
 	 */
 	@Override
 	public Set<ObjectId> getAdditionalHaves() {
-		HashSet<ObjectId> r = new HashSet<ObjectId>();
+		HashSet<ObjectId> r = new HashSet<>();
 		for (AlternateHandle d : objectDatabase.myAlternates()) {
 			if (d instanceof AlternateRepository) {
 				Repository repo;
@@ -618,12 +619,18 @@ static void loadRulesFromFile(AttributesNode r, File attrs)
 
 	}
 
+	private boolean shouldAutoDetach() {
+		return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
+				ConfigConstants.CONFIG_KEY_AUTODETACH, true);
+	}
+
 	@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) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index b608416..a4a2baa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -50,8 +50,11 @@
 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;
@@ -59,6 +62,8 @@
 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;
@@ -73,12 +78,16 @@
 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.concurrent.Executors;
 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;
@@ -93,6 +102,8 @@
 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;
@@ -140,6 +151,8 @@ public class GC {
 
 	private static final int DEFAULT_AUTOLIMIT = 6700;
 
+	private static ExecutorService executor = Executors.newFixedThreadPool(1);
+
 	private final FileRepository repo;
 
 	private ProgressMonitor pm;
@@ -175,6 +188,11 @@ public class GC {
 	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 <code>null</code> as progress monitor will be used.
 	 *
@@ -199,13 +217,59 @@ public GC(FileRepository repo) {
 	 * 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 PackFile}'s which are newly created
 	 * @throws IOException
 	 * @throws ParseException
 	 *             If the configuration parameter "gc.pruneexpire" couldn't be
 	 *             parsed
 	 */
+	// TODO(ms): in 5.0 change signature and return Future<Collection<PackFile>>
 	public Collection<PackFile> 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<Collection<PackFile>> gcTask = () -> {
+			try {
+				Collection<PackFile> 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 Collection<PackFile> doGc() throws IOException, ParseException {
 		if (automatic && !needGc()) {
 			return Collections.emptyList();
 		}
@@ -219,11 +283,41 @@ public Collection<PackFile> gc() throws IOException, ParseException {
 	}
 
 	/**
+	 * 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<ObjectId> 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.
+	 * <p>
+	 * 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
@@ -232,8 +326,22 @@ public Collection<PackFile> gc() throws IOException, ParseException {
 	 */
 	private void deleteOldPacks(Collection<PackFile> oldPacks,
 			Collection<PackFile> newPacks) throws ParseException, IOException {
+		HashSet<ObjectId> 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.
@@ -245,15 +353,57 @@ private void deleteOldPacks(Collection<PackFile> oldPacks,
 					&& 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
@@ -272,7 +422,7 @@ private void prunePack(String packName) {
 			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
@@ -281,7 +431,7 @@ private void prunePack(String packName) {
 			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) {
@@ -306,6 +456,7 @@ public void prunePacked() throws IOException {
 			pm.beginTask(JGitText.get().pruneLoosePackedObjects, fanout.length);
 			try {
 				for (String d : fanout) {
+					checkCancelled();
 					pm.update(1);
 					if (d.length() != 2)
 						continue;
@@ -313,6 +464,7 @@ public void prunePacked() throws IOException {
 					if (entries == null)
 						continue;
 					for (String e : entries) {
+						checkCancelled();
 						if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
 							continue;
 						ObjectId id;
@@ -324,11 +476,13 @@ public void prunePacked() throws IOException {
 							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
@@ -360,7 +514,7 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 
 		// Collect all loose objects which are old enough, not referenced from
 		// the index and not in objectsToKeep
-		Map<ObjectId, File> deletionCandidates = new HashMap<ObjectId, File>();
+		Map<ObjectId, File> deletionCandidates = new HashMap<>();
 		Set<ObjectId> indexObjects = null;
 		File objects = repo.getObjectsDirectory();
 		String[] fanout = objects.list();
@@ -371,6 +525,7 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 				fanout.length);
 		try {
 			for (String d : fanout) {
+				checkCancelled();
 				pm.update(1);
 				if (d.length() != 2)
 					continue;
@@ -378,6 +533,7 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 				if (entries == null)
 					continue;
 				for (File f : entries) {
+					checkCancelled();
 					String fName = f.getName();
 					if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
 						continue;
@@ -407,6 +563,8 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 			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
@@ -436,11 +594,14 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 			// leave this method.
 			ObjectWalk w = new ObjectWalk(repo);
 			try {
-				for (Ref cr : newRefs)
+				for (Ref cr : newRefs) {
+					checkCancelled();
 					w.markStart(w.parseAny(cr.getObjectId()));
+				}
 				if (lastPackedRefs != null)
-					for (Ref lpr : lastPackedRefs)
+					for (Ref lpr : lastPackedRefs) {
 						w.markUninteresting(w.parseAny(lpr.getObjectId()));
+					}
 				removeReferenced(deletionCandidates, w);
 			} finally {
 				w.dispose();
@@ -458,11 +619,15 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 		ObjectWalk w = new ObjectWalk(repo);
 		try {
 			for (Ref ar : getAllRefs())
-				for (ObjectId id : listRefLogObjects(ar, lastRepackTime))
+				for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) {
+					checkCancelled();
 					w.markStart(w.parseAny(id));
+				}
 			if (lastPackedRefs != null)
-				for (Ref lpr : lastPackedRefs)
+				for (Ref lpr : lastPackedRefs) {
+					checkCancelled();
 					w.markUninteresting(w.parseAny(lpr.getObjectId()));
+				}
 			removeReferenced(deletionCandidates, w);
 		} finally {
 			w.dispose();
@@ -471,6 +636,8 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 		if (deletionCandidates.isEmpty())
 			return;
 
+		checkCancelled();
+
 		// delete all candidates which have survived: these are unreferenced
 		// loose objects. Make a last check, though, to avoid deleting objects
 		// that could have been referenced while the candidates list was being
@@ -495,9 +662,7 @@ private long getExpireDate() throws ParseException {
 		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
@@ -511,6 +676,12 @@ private long getExpireDate() throws ParseException {
 		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;
 
@@ -546,6 +717,7 @@ private void removeReferenced(Map<ObjectId, File> id2File,
 			IncorrectObjectTypeException, IOException {
 		RevObject ro = w.next();
 		while (ro != null) {
+			checkCancelled();
 			if (id2File.remove(ro.getId()) != null)
 				if (id2File.isEmpty())
 					return;
@@ -553,6 +725,7 @@ private void removeReferenced(Map<ObjectId, File> id2File,
 		}
 		ro = w.nextObject();
 		while (ro != null) {
+			checkCancelled();
 			if (id2File.remove(ro.getId()) != null)
 				if (id2File.isEmpty())
 					return;
@@ -582,10 +755,11 @@ private static boolean equals(Ref r1, Ref r2) {
 	 */
 	public void packRefs() throws IOException {
 		Collection<Ref> refs = repo.getRefDatabase().getRefs(Constants.R_REFS).values();
-		List<String> refsToBePacked = new ArrayList<String>(refs.size());
+		List<String> 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);
@@ -616,18 +790,19 @@ public Collection<PackFile> repack() throws IOException {
 		long time = System.currentTimeMillis();
 		Collection<Ref> refsBefore = getAllRefs();
 
-		Set<ObjectId> allHeads = new HashSet<ObjectId>();
-		Set<ObjectId> nonHeads = new HashSet<ObjectId>();
-		Set<ObjectId> txnHeads = new HashSet<ObjectId>();
-		Set<ObjectId> tagTargets = new HashSet<ObjectId>();
+		Set<ObjectId> allHeads = new HashSet<>();
+		Set<ObjectId> nonHeads = new HashSet<>();
+		Set<ObjectId> txnHeads = new HashSet<>();
+		Set<ObjectId> tagTargets = new HashSet<>();
 		Set<ObjectId> indexObjects = listNonHEADIndexObjects();
 		RefDatabase refdb = repo.getRefDatabase();
 
 		for (Ref ref : refsBefore) {
+			checkCancelled();
 			nonHeads.addAll(listRefLogObjects(ref, 0));
 			if (ref.isSymbolic() || ref.getObjectId() == null)
 				continue;
-			if (ref.getName().startsWith(Constants.R_HEADS))
+			if (isHead(ref) || isTag(ref))
 				allHeads.add(ref.getObjectId());
 			else if (RefTreeNames.isRefTree(refdb, ref.getName()))
 				txnHeads.add(ref.getObjectId());
@@ -637,15 +812,17 @@ else if (RefTreeNames.isRefTree(refdb, ref.getName()))
 				tagTargets.add(ref.getPeeledObjectId());
 		}
 
-		List<ObjectIdSet> excluded = new LinkedList<ObjectIdSet>();
-		for (final PackFile f : repo.getObjectDatabase().getPacks())
+		List<ObjectIdSet> excluded = new LinkedList<>();
+		for (final PackFile f : repo.getObjectDatabase().getPacks()) {
+			checkCancelled();
 			if (f.shouldBeKept())
 				excluded.add(f.getIndex());
+		}
 
 		tagTargets.addAll(allHeads);
 		nonHeads.addAll(indexObjects);
 
-		List<PackFile> ret = new ArrayList<PackFile>(2);
+		List<PackFile> ret = new ArrayList<>(2);
 		PackFile heads = null;
 		if (!allHeads.isEmpty()) {
 			heads = writePack(allHeads, Collections.<ObjectId> emptySet(),
@@ -674,6 +851,7 @@ else if (RefTreeNames.isRefTree(refdb, ref.getName()))
 			throw new IOException(e);
 		}
 		prunePacked();
+		deleteEmptyRefsFolders();
 		deleteOrphans();
 
 		lastPackedRefs = refsBefore;
@@ -681,6 +859,65 @@ else if (RefTreeNames.isRefTree(refdb, ref.getName()))
 		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<Path> entries = Files.list(refs)) {
+			Iterator<Path> iterator = entries.iterator();
+			while (iterator.hasNext()) {
+				try (Stream<Path> 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<Path> 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
 	 * <p>
@@ -739,7 +976,7 @@ private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOExceptio
 				.getReverseEntries();
 		if (rlEntries == null || rlEntries.isEmpty())
 			return Collections.<ObjectId> emptySet();
-		Set<ObjectId> ret = new HashSet<ObjectId>();
+		Set<ObjectId> ret = new HashSet<>();
 		for (ReflogEntry e : rlEntries) {
 			if (e.getWho().getWhen().getTime() < minTime)
 				break;
@@ -773,6 +1010,7 @@ private Collection<Ref> getAllRefs() throws IOException {
 			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);
 				}
@@ -807,9 +1045,10 @@ private Set<ObjectId> listNonHEADIndexObjects()
 
 			treeWalk.setFilter(TreeFilter.ANY_DIFF);
 			treeWalk.setRecursive(true);
-			Set<ObjectId> ret = new HashSet<ObjectId>();
+			Set<ObjectId> ret = new HashSet<>();
 
 			while (treeWalk.next()) {
+				checkCancelled();
 				ObjectId objectId = treeWalk.getObjectId(0);
 				switch (treeWalk.getRawMode(0) & FileMode.TYPE_MASK) {
 				case FileMode.TYPE_MISSING:
@@ -837,9 +1076,11 @@ private Set<ObjectId> listNonHEADIndexObjects()
 	private PackFile writePack(@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
 			List<ObjectIdSet> excludeObjects) throws IOException {
+		checkCancelled();
 		File tmpPack = null;
-		Map<PackExt, File> tmpExts = new TreeMap<PackExt, File>(
+		Map<PackExt, File> tmpExts = new TreeMap<>(
 				new Comparator<PackExt>() {
+					@Override
 					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
@@ -868,6 +1109,7 @@ public int compare(PackExt o1, PackExt o2) {
 			pw.preparePack(pm, want, have);
 			if (pw.getObjectCount() == 0)
 				return null;
+			checkCancelled();
 
 			// create temporary files
 			String id = pw.computeName().getName();
@@ -984,6 +1226,12 @@ private File nameFor(String name, String ext) {
 		return new File(packdir, "pack-" + name + ext); //$NON-NLS-1$
 	}
 
+	private void checkCancelled() throws CancelledException {
+		if (pm.isCancelled()) {
+			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
@@ -1031,6 +1279,7 @@ public static class RepoStatistics {
 		 */
 		public long numberOfBitmaps;
 
+		@Override
 		public String toString() {
 			final StringBuilder b = new StringBuilder();
 			b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$
@@ -1135,7 +1384,6 @@ public void setPackExpireAgeMillis(long packExpireAgeMillis) {
 	 * 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
 	 */
@@ -1208,12 +1456,19 @@ public void setPackExpire(Date packExpire) {
 	 *
 	 * @param auto
 	 *            defines whether gc should do automatic housekeeping
-	 * @since 4.5
 	 */
 	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();
@@ -1252,8 +1507,7 @@ boolean tooManyPacks() {
 	 * @return {@code true} if number of loose objects > gc.auto (default 6700)
 	 */
 	boolean tooManyLooseObjects() {
-		int auto = repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
-				ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT);
+		int auto = getLooseObjectLimit();
 		if (auto <= 0) {
 			return false;
 		}
@@ -1266,10 +1520,12 @@ boolean tooManyLooseObjects() {
 		try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir,
 				new DirectoryStream.Filter<Path>() {
 
+					@Override
 					public boolean accept(Path file) throws IOException {
-						return Files.isRegularFile(file) && PATTERN_LOOSE_OBJECT
-								.matcher(file.getFileName().toString())
-								.matches();
+						Path fileName = file.getFileName();
+						return Files.isRegularFile(file) && fileName != null
+								&& PATTERN_LOOSE_OBJECT
+										.matcher(fileName.toString()).matches();
 					}
 				})) {
 			for (Iterator<Path> iter = stream.iterator(); iter.hasNext();
@@ -1283,4 +1539,9 @@ public boolean accept(Path file) throws IOException {
 		}
 		return false;
 	}
+
+	private int getLooseObjectLimit() {
+		return repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
+				ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
new file mode 100644
index 0000000..35049d4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -0,0 +1,175 @@
+/*
+ * 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 org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.util.GitDateParser;
+import org.eclipse.jgit.util.SystemReader;
+
+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;
+
+/**
+ * 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(logFile.toPath());
+			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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 51af67e..15c5280 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -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;
@@ -62,7 +64,10 @@
 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.
@@ -75,7 +80,7 @@
  * 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.
@@ -105,13 +110,15 @@ public static boolean unlock(final File file) {
 	 * @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);
 		}
 	};
 
@@ -129,6 +136,8 @@ public boolean accept(File dir, String name) {
 
 	private FileSnapshot commitSnapshot;
 
+	private LockToken token;
+
 	/**
 	 * Create a new lock for any file.
 	 *
@@ -150,7 +159,6 @@ public LockFile(final File f, final FS fs) {
 	 *
 	 * @param f
 	 *            the file that will be locked.
-	 * @since 4.2
 	 */
 	public LockFile(final File f) {
 		ref = f;
@@ -168,7 +176,8 @@ public LockFile(final File f) {
 	 */
 	public boolean lock() throws IOException {
 		FileUtils.mkdirs(lck.getParentFile(), true);
-		if (FS.DETECTED.createNewFile(lck)) {
+		token = FS.DETECTED.createNewFileAtomic(lck);
+		if (token.isCreated()) {
 			haveLck = true;
 			try {
 				os = new FileOutputStream(lck);
@@ -176,6 +185,8 @@ public boolean lock() throws IOException {
 				unlock();
 				throw ioe;
 			}
+		} else {
+			closeToken();
 		}
 		return haveLck;
 	}
@@ -456,6 +467,7 @@ public boolean commit() {
 		try {
 			FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
 			haveLck = false;
+			closeToken();
 			return true;
 		} catch (IOException e) {
 			unlock();
@@ -463,6 +475,13 @@ public boolean commit() {
 		}
 	}
 
+	private void closeToken() {
+		if (token != null) {
+			token.close();
+			token = null;
+		}
+	}
+
 	private void saveStatInformation() {
 		if (needSnapshot)
 			commitSnapshot = FileSnapshot.save(lck);
@@ -501,8 +520,9 @@ public void unlock() {
 		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;
 		}
@@ -512,7 +532,10 @@ public void unlock() {
 			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();
 			}
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 44ad99b..e1934a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -125,6 +125,8 @@ public class ObjectDirectory extends FileObjectDatabase {
 
 	private final File packDirectory;
 
+	private final File preservedDirectory;
+
 	private final File alternatesFile;
 
 	private final AtomicReference<PackList> packList;
@@ -165,13 +167,14 @@ public ObjectDirectory(final Config cfg, final File dir,
 		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<PackList>(NO_PACKS);
+		packList = new AtomicReference<>(NO_PACKS);
 		unpackedObjectCache = new UnpackedObjectCache();
 		this.fs = fs;
 		this.shallowFile = shallowFile;
 
-		alternates = new AtomicReference<AlternateHandle[]>();
+		alternates = new AtomicReference<>();
 		if (alternatePaths != null) {
 			AlternateHandle[] alt;
 
@@ -185,10 +188,18 @@ public ObjectDirectory(final Config cfg, final File dir,
 	/**
 	 * @return the location of the <code>objects</code> directory.
 	 */
+	@Override
 	public final File getDirectory() {
 		return objects;
 	}
 
+	/**
+	 * @return the location of the <code>preserved</code> directory.
+	 */
+	public final File getPreservedDirectory() {
+		return preservedDirectory;
+	}
+
 	@Override
 	public boolean exists() {
 		return fs.exists(objects);
@@ -250,6 +261,7 @@ public Collection<PackFile> getPacks() {
 	 *             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();
@@ -436,6 +448,7 @@ ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
 		return null;
 	}
 
+	@Override
 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
 			throws IOException {
 		File path = fileFor(id);
@@ -451,6 +464,7 @@ ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
 		}
 	}
 
+	@Override
 	long getObjectSize(WindowCursor curs, AnyObjectId id)
 			throws IOException {
 		if (unpackedObjectCache.isUnpacked(id)) {
@@ -574,7 +588,7 @@ private void handlePackError(IOException e, PackFile p) {
 				warnTmpl = JGitText.get().packWasDeleted;
 				removePack(p);
 			}
-		} else if (FileUtils.isStaleFileHandle(e)) {
+		} else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
 			warnTmpl = JGitText.get().packHandleIsStale;
 			removePack(p);
 		} else {
@@ -689,6 +703,7 @@ private boolean searchPacksAgain(PackList old) {
 				&& old != scanPacks(old);
 	}
 
+	@Override
 	Config getConfig() {
 		return config;
 	}
@@ -705,13 +720,19 @@ Set<ObjectId> getShallowCommits() throws IOException {
 
 		if (shallowFileSnapshot == null
 				|| shallowFileSnapshot.isModified(shallowFile)) {
-			shallowCommitsIds = new HashSet<ObjectId>();
+			shallowCommitsIds = new HashSet<>();
 
 			final BufferedReader reader = open(shallowFile);
 			try {
 				String line;
-				while ((line = reader.readLine()) != null)
-					shallowCommitsIds.add(ObjectId.fromString(line));
+				while ((line = reader.readLine()) != null) {
+					try {
+						shallowCommitsIds.add(ObjectId.fromString(line));
+					} catch (IllegalArgumentException ex) {
+						throw new IOException(MessageFormat
+								.format(JGitText.get().badShallowLine, line));
+					}
+				}
 			} finally {
 				reader.close();
 			}
@@ -796,7 +817,7 @@ private PackList scanPacksImpl(final PackList old) {
 		final Map<String, PackFile> forReuse = reuseMap(old);
 		final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
 		final Set<String> names = listPackDirectory();
-		final List<PackFile> list = new ArrayList<PackFile>(names.size() >> 2);
+		final List<PackFile> 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.
@@ -854,7 +875,7 @@ private PackList scanPacksImpl(final PackList old) {
 	}
 
 	private static Map<String, PackFile> reuseMap(final PackList old) {
-		final Map<String, PackFile> forReuse = new HashMap<String, PackFile>();
+		final Map<String, PackFile> forReuse = new HashMap<>();
 		for (final PackFile p : old.packs) {
 			if (p.invalid()) {
 				// The pack instance is corrupted, and cannot be safely used
@@ -883,7 +904,7 @@ private Set<String> listPackDirectory() {
 		final String[] nameList = packDirectory.list();
 		if (nameList == null)
 			return Collections.emptySet();
-		final Set<String> nameSet = new HashSet<String>(nameList.length << 1);
+		final Set<String> nameSet = new HashSet<>(nameList.length << 1);
 		for (final String name : nameList) {
 			if (name.startsWith("pack-")) //$NON-NLS-1$
 				nameSet.add(name);
@@ -910,7 +931,7 @@ private Set<String> listPackDirectory() {
 	}
 
 	private AlternateHandle[] loadAlternates() throws IOException {
-		final List<AlternateHandle> l = new ArrayList<AlternateHandle>(4);
+		final List<AlternateHandle> l = new ArrayList<>(4);
 		final BufferedReader br = open(alternatesFile);
 		try {
 			String line;
@@ -953,6 +974,7 @@ private AlternateHandle openAlternate(File objdir) throws IOException {
 	 *            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);
@@ -993,6 +1015,7 @@ static class AlternateRepository extends AlternateHandle {
 			repository = r;
 		}
 
+		@Override
 		void close() {
 			repository.close();
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
index 9820e0e..aa435bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
@@ -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 {
@@ -86,34 +86,71 @@ class ObjectDirectoryInserter extends ObjectInserter {
 	@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);
 		}
 	}
 
 	@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:
@@ -156,7 +193,7 @@ public void close() {
 	}
 
 	@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 +205,7 @@ private File toTemp(final MessageDigest md, final int type, long len,
 				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 +285,25 @@ private static EOFException shortInput(long missing) {
 		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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
index 956e8de..bfd60fc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
@@ -75,9 +75,9 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
 	private final EWAHCompressedBitmap tags;
 	private final BlockList<PositionEntry> byOffset;
 	final BlockList<StoredBitmap>
-			byAddOrder = new BlockList<StoredBitmap>();
+			byAddOrder = new BlockList<>();
 	final ObjectIdOwnerMap<PositionEntry>
-			positionEntries = new ObjectIdOwnerMap<PositionEntry>();
+			positionEntries = new ObjectIdOwnerMap<>();
 
 	/**
 	 * Creates a PackBitmapIndex used for building the contents of an index
@@ -133,6 +133,7 @@ private static void sortByOffsetAndIndex(BlockList<PositionEntry> byOffset,
 			positionEntries.add(new PositionEntry(entries.get(i), i));
 		}
 		Collections.sort(entries, new Comparator<ObjectToPack>() {
+			@Override
 			public int compare(ObjectToPack a, ObjectToPack b) {
 				return Long.signum(a.getOffset() - b.getOffset());
 			}
@@ -274,14 +275,17 @@ public Iterable<StoredEntry> getCompressedBitmaps() {
 		// Add order is from oldest to newest. The reverse add order is the
 		// output order.
 		return new Iterable<StoredEntry>() {
+			@Override
 			public Iterator<StoredEntry> iterator() {
 				return new Iterator<StoredEntry>() {
 					private int index = byAddOrder.size() - 1;
 
+					@Override
 					public boolean hasNext() {
 						return index >= 0;
 					}
 
+					@Override
 					public StoredEntry next() {
 						if (!hasNext())
 							throw new NoSuchElementException();
@@ -315,6 +319,7 @@ public StoredEntry next() {
 								bestXorOffset, item.getFlags());
 					}
 
+					@Override
 					public void remove() {
 						throw new UnsupportedOperationException();
 					}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
index 2c462a7..9a8c275 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
@@ -107,7 +107,7 @@ private PackBitmapIndexRemapper(
 			BasePackBitmapIndex oldPackIndex, PackBitmapIndex newPackIndex) {
 		this.oldPackIndex = oldPackIndex;
 		this.newPackIndex = newPackIndex;
-		convertedBitmaps = new ObjectIdOwnerMap<StoredBitmap>();
+		convertedBitmaps = new ObjectIdOwnerMap<>();
 		inflated = new BitSet(newPackIndex.getObjectCount());
 
 		prevToNewMapping = new int[oldPackIndex.getObjectCount()];
@@ -137,6 +137,7 @@ public EWAHCompressedBitmap ofObjectType(
 		return newPackIndex.ofObjectType(bitmap, type);
 	}
 
+	@Override
 	public Iterator<Entry> iterator() {
 		if (oldPackIndex == null)
 			return Collections.<Entry> emptyList().iterator();
@@ -145,6 +146,7 @@ public Iterator<Entry> iterator() {
 		return new Iterator<Entry>() {
 			private Entry entry;
 
+			@Override
 			public boolean hasNext() {
 				while (entry == null && it.hasNext()) {
 					StoredBitmap sb = it.next();
@@ -154,6 +156,7 @@ public boolean hasNext() {
 				return entry != null;
 			}
 
+			@Override
 			public Entry next() {
 				if (!hasNext())
 					throw new NoSuchElementException();
@@ -163,6 +166,7 @@ public Entry next() {
 				return res;
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index 7a51a5a..e7db11d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -47,6 +47,7 @@
 
 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;
@@ -101,6 +102,7 @@
 public class PackFile implements Iterable<PackIndex.MutableEntry> {
 	/** Sorts PackFiles to be most recently created to least recently created. */
 	public static final Comparator<PackFile> SORT = new Comparator<PackFile>() {
+		@Override
 		public int compare(final PackFile a, final PackFile b) {
 			return b.packLastModified - a.packLastModified;
 		}
@@ -254,7 +256,7 @@ public boolean hasObject(final AnyObjectId id) throws IOException {
 	 */
 	public boolean shouldBeKept() {
 		if (keepFile == null)
-			keepFile = new File(packFile.getPath() + ".keep"); //$NON-NLS-1$
+			keepFile = extFile(KEEP);
 		return keepFile.exists();
 	}
 
@@ -304,6 +306,7 @@ public void close() {
 	 *
 	 * @see PackIndex#iterator()
 	 */
+	@Override
 	public Iterator<PackIndex.MutableEntry> iterator() {
 		try {
 			return idx().iterator();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
index 5d4a30f..2252778 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
@@ -183,6 +183,7 @@ public boolean contains(AnyObjectId id) {
 	 *
 	 * @return iterator over pack index entries
 	 */
+	@Override
 	public abstract Iterator<MutableEntry> iterator();
 
 	/**
@@ -365,6 +366,7 @@ abstract class EntriesIterator implements Iterator<MutableEntry> {
 
 		protected abstract MutableEntry initEntry();
 
+		@Override
 		public boolean hasNext() {
 			return returnedNumber < getObjectCount();
 		}
@@ -373,8 +375,10 @@ public boolean hasNext() {
 		 * Implementation must update {@link #returnedNumber} before returning
 		 * element.
 		 */
+		@Override
 		public abstract MutableEntry next();
 
+		@Override
 		public void remove() {
 			throw new UnsupportedOperationException();
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index e5a729d..8a08456 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -240,6 +240,7 @@ private class IndexV1Iterator extends EntriesIterator {
 		@Override
 		protected MutableEntry initEntry() {
 			return new MutableEntry() {
+				@Override
 				protected void ensureId() {
 					idBuffer.fromRaw(idxdata[levelOne], levelTwo
 							- Constants.OBJECT_ID_LENGTH);
@@ -247,6 +248,7 @@ protected void ensureId() {
 			};
 		}
 
+		@Override
 		public MutableEntry next() {
 			for (; levelOne < idxdata.length; levelOne++) {
 				if (idxdata[levelOne] == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
index d87336f..5c2986a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
@@ -311,6 +311,7 @@ private class EntriesIteratorV2 extends EntriesIterator {
 		@Override
 		protected MutableEntry initEntry() {
 			return new MutableEntry() {
+				@Override
 				protected void ensureId() {
 					idBuffer.fromRaw(names[levelOne], levelTwo
 							- Constants.OBJECT_ID_LENGTH / 4);
@@ -318,6 +319,7 @@ protected void ensureId() {
 			};
 		}
 
+		@Override
 		public MutableEntry next() {
 			for (; levelOne < names.length; levelOne++) {
 				if (levelTwo < names[levelOne].length) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index a5d380d..8338b2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -63,6 +63,8 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.Files;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.text.MessageFormat;
@@ -154,10 +156,10 @@ public class RefDirectory extends RefDatabase {
 	 * converted into resolved references during a get operation, ensuring the
 	 * live value is always returned.
 	 */
-	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
+	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();
 
 	/** Immutable sorted list of packed references. */
-	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
+	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
 
 	/**
 	 * Number of modifications made to this database.
@@ -195,6 +197,7 @@ ReflogWriter getLogWriter() {
 		return logWriter;
 	}
 
+	@Override
 	public void create() throws IOException {
 		FileUtils.mkdir(refsDir);
 		FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
@@ -352,7 +355,7 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
 
 	@Override
 	public List<Ref> getAdditionalRefs() throws IOException {
-		List<Ref> ret = new LinkedList<Ref>();
+		List<Ref> ret = new LinkedList<>();
 		for (String name : additionalRefsNames) {
 			Ref r = getRef(name);
 			if (r != null)
@@ -371,7 +374,7 @@ private class LooseScanner {
 
 		private int curIdx;
 
-		final RefList.Builder<Ref> symbolic = new RefList.Builder<Ref>(4);
+		final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);
 
 		RefList.Builder<LooseRef> newLoose;
 
@@ -535,6 +538,7 @@ void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
 		fireRefsChanged();
 	}
 
+	@Override
 	public RefDirectoryUpdate newUpdate(String name, boolean detach)
 			throws IOException {
 		boolean detachingSymbolicRef = false;
@@ -805,7 +809,8 @@ private PackedRefList readPackedRefs() throws IOException {
 				return new PackedRefList(parsePackedRefs(br), snapshot,
 						ObjectId.fromRaw(digest.digest()));
 			} catch (IOException e) {
-				if (FileUtils.isStaleFileHandle(e) && retries < maxStaleRetries) {
+				if (FileUtils.isStaleFileHandleInCausalChain(e)
+						&& retries < maxStaleRetries) {
 					if (LOG.isDebugEnabled()) {
 						LOG.debug(MessageFormat.format(
 								JGitText.get().packedRefsHandleIsStale,
@@ -823,7 +828,7 @@ private PackedRefList readPackedRefs() throws IOException {
 
 	private RefList<Ref> parsePackedRefs(final BufferedReader br)
 			throws IOException {
-		RefList.Builder<Ref> all = new RefList.Builder<Ref>();
+		RefList.Builder<Ref> all = new RefList.Builder<>();
 		Ref last = null;
 		boolean peeled = false;
 		boolean needSort = false;
@@ -1087,8 +1092,16 @@ private static void delete(final File file, final int depth, LockFile rLck)
 		}
 		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();
 		}
@@ -1132,10 +1145,12 @@ private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
 			this.snapShot = snapshot;
 		}
 
+		@Override
 		public FileSnapshot getSnapShot() {
 			return snapShot;
 		}
 
+		@Override
 		public LooseRef peel(ObjectIdRef newLeaf) {
 			return this;
 		}
@@ -1151,10 +1166,12 @@ private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
 			this.snapShot = snapshot;
 		}
 
+		@Override
 		public FileSnapshot getSnapShot() {
 			return snapShot;
 		}
 
+		@Override
 		public LooseRef peel(ObjectIdRef newLeaf) {
 			return this;
 		}
@@ -1170,6 +1187,7 @@ private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
 			this.snapShot = snapShot;
 		}
 
+		@Override
 		public FileSnapshot getSnapShot() {
 			return snapShot;
 		}
@@ -1182,6 +1200,7 @@ public ObjectId getObjectId() {
 			return id;
 		}
 
+		@Override
 		public LooseRef peel(ObjectIdRef newLeaf) {
 			ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
 			ObjectId objectId = getObjectId();
@@ -1205,10 +1224,12 @@ private final static class LooseSymbolicRef extends SymbolicRef implements
 			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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
index 60f04b8..16b2a46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
@@ -93,6 +93,7 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getOldId()
 	 */
+	@Override
 	public ObjectId getOldId() {
 		return oldId;
 	}
@@ -100,6 +101,7 @@ public ObjectId getOldId() {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getNewId()
 	 */
+	@Override
 	public ObjectId getNewId() {
 		return newId;
 	}
@@ -107,6 +109,7 @@ public ObjectId getNewId() {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getWho()
 	 */
+	@Override
 	public PersonIdent getWho() {
 		return who;
 	}
@@ -114,6 +117,7 @@ public PersonIdent getWho() {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getComment()
 	 */
+	@Override
 	public String getComment() {
 		return comment;
 	}
@@ -128,6 +132,7 @@ public String toString() {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogEntry#parseCheckout()
 	 */
+	@Override
 	public CheckoutEntry parseCheckout() {
 		if (getComment().startsWith(CheckoutEntryImpl.CHECKOUT_MOVING_FROM))
 			return new CheckoutEntryImpl(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
index 2f583b2..c3702fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
@@ -74,6 +74,7 @@ class ReflogReaderImpl implements ReflogReader {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getLastEntry()
 	 */
+	@Override
 	public ReflogEntry getLastEntry() throws IOException {
 		return getReverseEntry(0);
 	}
@@ -81,6 +82,7 @@ public ReflogEntry getLastEntry() throws IOException {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries()
 	 */
+	@Override
 	public List<ReflogEntry> getReverseEntries() throws IOException {
 		return getReverseEntries(Integer.MAX_VALUE);
 	}
@@ -88,6 +90,7 @@ public List<ReflogEntry> getReverseEntries() throws IOException {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntry(int)
 	 */
+	@Override
 	public ReflogEntry getReverseEntry(int number) throws IOException {
 		if (number < 0)
 			throw new IllegalArgumentException();
@@ -116,6 +119,7 @@ public ReflogEntry getReverseEntry(int number) throws IOException {
 	/* (non-Javadoc)
 	 * @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries(int)
 	 */
+	@Override
 	public List<ReflogEntry> getReverseEntries(int max) throws IOException {
 		final byte[] log;
 		try {
@@ -128,7 +132,7 @@ public List<ReflogEntry> getReverseEntries(int max) throws IOException {
 		}
 
 		int rs = RawParseUtils.prevLF(log, log.length);
-		List<ReflogEntry> ret = new ArrayList<ReflogEntry>();
+		List<ReflogEntry> ret = new ArrayList<>();
 		while (rs >= 0 && max-- > 0) {
 			rs = RawParseUtils.prevLF(log, rs);
 			ReflogEntry entry = new ReflogEntryImpl(log, rs < 0 ? 0 : rs + 2);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
index fc80266..892c1c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
@@ -47,6 +47,7 @@
 
 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_REFS;
 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
@@ -74,8 +75,6 @@
 
 /**
  * Utility for writing reflog entries
- *
- * @since 2.0
  */
 public class ReflogWriter {
 
@@ -88,7 +87,7 @@ public class ReflogWriter {
 	 * @return the name of the ref's lock ref
 	 */
 	public static String refLockFor(final String name) {
-		return name + LockFile.SUFFIX;
+		return name + LOCK_SUFFIX;
 	}
 
 	private final Repository parent;
@@ -289,4 +288,4 @@ private boolean shouldAutoCreateLog(final String refName) {
 				|| refName.startsWith(R_REMOTES) //
 				|| refName.equals(R_STASH);
 	}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
index a733068..452636d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
@@ -64,11 +64,13 @@ class SimpleDataInput implements DataInput {
 		this.fd = fd;
 	}
 
+	@Override
 	public int readInt() throws IOException {
 		readFully(buf, 0, 4);
 		return NB.decodeInt32(buf, 0);
 	}
 
+	@Override
 	public long readLong() throws IOException {
 		readFully(buf, 0, 8);
 		return NB.decodeInt64(buf, 0);
@@ -79,54 +81,67 @@ public long readUnsignedInt() throws IOException {
 		return NB.decodeUInt32(buf, 0);
 	}
 
+	@Override
 	public void readFully(byte[] b) throws IOException {
 		readFully(b, 0, b.length);
 	}
 
+	@Override
 	public void readFully(byte[] b, int off, int len) throws IOException {
 		IO.readFully(fd, b, off, len);
 	}
 
+	@Override
 	public int skipBytes(int n) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public boolean readBoolean() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public byte readByte() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public int readUnsignedByte() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public short readShort() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public int readUnsignedShort() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public char readChar() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public float readFloat() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public double readDouble() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public String readLine() throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public String readUTF() throws IOException {
 		throw new UnsupportedOperationException();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
index d9c899a..373a494 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
@@ -64,61 +64,75 @@ class SimpleDataOutput implements DataOutput {
 		this.fd = fd;
 	}
 
+	@Override
 	public void writeShort(int v) throws IOException {
 		NB.encodeInt16(buf, 0, v);
 		fd.write(buf, 0, 2);
 	}
 
+	@Override
 	public void writeInt(int v) throws IOException {
 		NB.encodeInt32(buf, 0, v);
 		fd.write(buf, 0, 4);
 	}
 
+	@Override
 	public void writeLong(long v) throws IOException {
 		NB.encodeInt64(buf, 0, v);
 		fd.write(buf, 0, 8);
 	}
 
+	@Override
 	public void write(int b) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void write(byte[] b) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void write(byte[] b, int off, int len) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeBoolean(boolean v) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeByte(int v) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeChar(int v) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeFloat(float v) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeDouble(double v) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeBytes(String s) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeChars(String s) throws IOException {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
 	public void writeUTF(String s) throws IOException {
 		throw new UnsupportedOperationException();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
index ce67ae0..967754a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
@@ -100,7 +100,7 @@ private static class Table {
 		final int bits;
 
 		Table(int bits) {
-			this.ids = new AtomicReferenceArray<ObjectId>(1 << bits);
+			this.ids = new AtomicReferenceArray<>(1 << bits);
 			this.shift = 32 - bits;
 			this.bits = bits;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index e1b7606..a525c85 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
@@ -235,9 +235,9 @@ private WindowCache(final WindowCacheConfig cfg) {
 		if (lockCount < 1)
 			throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
 
-		queue = new ReferenceQueue<ByteWindow>();
+		queue = new ReferenceQueue<>();
 		clock = new AtomicLong(1);
-		table = new AtomicReferenceArray<Entry>(tableSize);
+		table = new AtomicReferenceArray<>(tableSize);
 		locks = new Lock[lockCount];
 		for (int i = 0; i < locks.length; i++)
 			locks[i] = new Lock();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
index a742d17..83b236e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
@@ -125,6 +125,7 @@ public BitmapIndex getBitmapIndex() throws IOException {
 		return null;
 	}
 
+	@Override
 	public Collection<CachedPack> getCachedPacksAndUpdate(
 			BitmapBuilder needBitmap) throws IOException {
 		for (PackFile pack : db.getPacks()) {
@@ -141,15 +142,17 @@ public Collection<ObjectId> resolve(AbbreviatedObjectId id)
 			throws IOException {
 		if (id.isComplete())
 			return Collections.singleton(id.toObjectId());
-		HashSet<ObjectId> matches = new HashSet<ObjectId>(4);
+		HashSet<ObjectId> matches = new HashSet<>(4);
 		db.resolve(matches, id);
 		return matches;
 	}
 
+	@Override
 	public boolean has(AnyObjectId objectId) throws IOException {
 		return db.has(objectId);
 	}
 
+	@Override
 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
@@ -170,6 +173,7 @@ public Set<ObjectId> getShallowCommits() throws IOException {
 		return db.getShallowCommits();
 	}
 
+	@Override
 	public long getObjectSize(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
@@ -183,10 +187,12 @@ public long getObjectSize(AnyObjectId objectId, int typeHint)
 		return sz;
 	}
 
+	@Override
 	public LocalObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
 		return new LocalObjectToPack(objectId, type);
 	}
 
+	@Override
 	public void selectObjectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
 			throws IOException, MissingObjectException {
@@ -196,6 +202,7 @@ public void selectObjectRepresentation(PackWriter packer,
 		}
 	}
 
+	@Override
 	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 			boolean validate) throws IOException,
 			StoredObjectRepresentationNotAvailableException {
@@ -203,6 +210,7 @@ public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 		src.pack.copyAsIs(out, src, validate, this);
 	}
 
+	@Override
 	public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
 			throws IOException {
 		for (ObjectToPack otp : list)
@@ -245,6 +253,7 @@ int copy(final PackFile pack, long position, final byte[] dstbuf,
 		return cnt - need;
 	}
 
+	@Override
 	public void copyPackAsIs(PackOutputStream out, CachedPack pack)
 			throws IOException {
 		((LocalCachedPack) pack).copyAsIs(out, this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
index 4f79ea9..1e2b239 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
@@ -50,6 +50,7 @@
 class WriteConfig {
 	/** Key for {@link Config#get(SectionParser)}. */
 	static final Config.SectionParser<WriteConfig> KEY = new SectionParser<WriteConfig>() {
+		@Override
 		public WriteConfig parse(final Config cfg) {
 			return new WriteConfig(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
index b6af0b0..d231ccb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
@@ -96,7 +96,7 @@ class BaseSearch {
 		edgeObjects = edges;
 
 		alreadyProcessed = new IntSet();
-		treeCache = new ObjectIdOwnerMap<TreeWithData>();
+		treeCache = new ObjectIdOwnerMap<>();
 		parser = new CanonicalTreeParser();
 		idBuf = new MutableObjectId();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java
index 91917b2..973dd1d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaCache.java
@@ -60,7 +60,7 @@ class DeltaCache {
 	DeltaCache(PackConfig pc) {
 		size = pc.getDeltaCacheSize();
 		entryLimit = pc.getDeltaCacheLimit();
-		queue = new ReferenceQueue<byte[]>();
+		queue = new ReferenceQueue<>();
 	}
 
 	boolean canCache(int length, ObjectToPack src, ObjectToPack res) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
index 2a2a463..0f22de0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
@@ -421,6 +421,7 @@ private static int negmatch(byte[] res, int resPtr, byte[] src, int srcPtr,
 		return start - resPtr;
 	}
 
+	@Override
 	@SuppressWarnings("nls")
 	public String toString() {
 		String[] units = { "bytes", "KiB", "MiB", "GiB" };
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
index 4292742..0c4e444 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
@@ -78,7 +78,7 @@ static final class Block {
 		Block(int threads, PackConfig config, ObjectReader reader,
 				DeltaCache dc, ThreadSafeProgressMonitor pm,
 				ObjectToPack[] list, int begin, int end) {
-			this.tasks = new ArrayList<DeltaTask>(threads);
+			this.tasks = new ArrayList<>(threads);
 			this.threads = threads;
 			this.config = config;
 			this.templateReader = reader;
@@ -176,7 +176,7 @@ void partitionTasks() {
 		}
 
 		private ArrayList<WeightedPath> computeTopPaths() {
-			ArrayList<WeightedPath> topPaths = new ArrayList<WeightedPath>(
+			ArrayList<WeightedPath> topPaths = new ArrayList<>(
 					threads);
 			int cp = beginIndex;
 			int ch = list[cp].getPathHash();
@@ -213,6 +213,7 @@ private ArrayList<WeightedPath> computeTopPaths() {
 
 			// Sort by starting index to identify gaps later.
 			Collections.sort(topPaths, new Comparator<WeightedPath>() {
+				@Override
 				public int compare(WeightedPath a, WeightedPath b) {
 					return a.slice.beginIndex - b.slice.beginIndex;
 				}
@@ -244,6 +245,7 @@ static final class WeightedPath implements Comparable<WeightedPath> {
 			this.slice = s;
 		}
 
+		@Override
 		public int compareTo(WeightedPath o) {
 			int cmp = Long.signum(weight - o.weight);
 			if (cmp != 0) {
@@ -275,7 +277,7 @@ final int size() {
 
 	DeltaTask(Block b) {
 		this.block = b;
-		this.slices = new LinkedList<Slice>();
+		this.slices = new LinkedList<>();
 	}
 
 	void add(Slice s) {
@@ -290,6 +292,7 @@ void add(Slice s) {
 		slices.add(s);
 	}
 
+	@Override
 	public Object call() throws Exception {
 		or = block.templateReader.newReader();
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
index 19d06a2..73b285a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
@@ -160,6 +160,7 @@ void search() throws IOException {
 						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 @@ private static long estimateIndexSize(DeltaWindowEntry ent) {
 		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 void searchInWindow() throws IOException {
 
 	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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
index 4ee27cc..248692f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
@@ -53,6 +53,9 @@ public class PackExt {
 	/** 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$
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index 8b4d2e6..93dbee3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -164,18 +164,20 @@ public class PackWriter implements AutoCloseable {
 	private static final int PACK_VERSION_GENERATED = 2;
 
 	/** Empty set of objects for {@code preparePack()}. */
-	public static Set<ObjectId> NONE = Collections.emptySet();
+	public static final Set<ObjectId> NONE = Collections.emptySet();
 
 	private static final Map<WeakReference<PackWriter>, Boolean> instances =
-			new ConcurrentHashMap<WeakReference<PackWriter>, Boolean>();
+			new ConcurrentHashMap<>();
 
 	private static final Iterable<PackWriter> instancesIterable = new Iterable<PackWriter>() {
+		@Override
 		public Iterator<PackWriter> iterator() {
 			return new Iterator<PackWriter>() {
 				private final Iterator<WeakReference<PackWriter>> it =
 						instances.keySet().iterator();
 				private PackWriter next;
 
+				@Override
 				public boolean hasNext() {
 					if (next != null)
 						return true;
@@ -189,6 +191,7 @@ public boolean hasNext() {
 					return false;
 				}
 
+				@Override
 				public PackWriter next() {
 					if (hasNext()) {
 						PackWriter result = next;
@@ -198,6 +201,7 @@ public PackWriter next() {
 					throw new NoSuchElementException();
 				}
 
+				@Override
 				public void remove() {
 					throw new UnsupportedOperationException();
 				}
@@ -213,21 +217,21 @@ public static Iterable<PackWriter> getInstances() {
 	@SuppressWarnings("unchecked")
 	BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1];
 	{
-		objectsLists[OBJ_COMMIT] = new BlockList<ObjectToPack>();
-		objectsLists[OBJ_TREE] = new BlockList<ObjectToPack>();
-		objectsLists[OBJ_BLOB] = new BlockList<ObjectToPack>();
-		objectsLists[OBJ_TAG] = new BlockList<ObjectToPack>();
+		objectsLists[OBJ_COMMIT] = new BlockList<>();
+		objectsLists[OBJ_TREE] = new BlockList<>();
+		objectsLists[OBJ_BLOB] = new BlockList<>();
+		objectsLists[OBJ_TAG] = new BlockList<>();
 	}
 
-	private ObjectIdOwnerMap<ObjectToPack> objectsMap = new ObjectIdOwnerMap<ObjectToPack>();
+	private ObjectIdOwnerMap<ObjectToPack> objectsMap = new ObjectIdOwnerMap<>();
 
 	// edge objects for thin packs
-	private List<ObjectToPack> edgeObjects = new BlockList<ObjectToPack>();
+	private List<ObjectToPack> edgeObjects = new BlockList<>();
 
 	// Objects the client is known to have already.
 	private BitmapBuilder haveObjects;
 
-	private List<CachedPack> cachedPacks = new ArrayList<CachedPack>(2);
+	private List<CachedPack> cachedPacks = new ArrayList<>(2);
 
 	private Set<ObjectId> tagTargets = Collections.emptySet();
 
@@ -355,7 +359,7 @@ public PackWriter(final PackConfig config, final ObjectReader reader) {
 		reuseValidate = true; // be paranoid by default
 		stats = new PackStatistics.Accumulator();
 		state = new MutableState();
-		selfRef = new WeakReference<PackWriter>(this);
+		selfRef = new WeakReference<>(this);
 		instances.put(selfRef, Boolean.TRUE);
 	}
 
@@ -369,7 +373,6 @@ public PackWriter(final PackConfig config, final ObjectReader reader) {
 	 *            the callback to set
 	 *
 	 * @return this object for chaining.
-	 * @since 4.1
 	 */
 	public PackWriter setObjectCountCallback(ObjectCountCallback callback) {
 		this.callback = callback;
@@ -381,11 +384,10 @@ public PackWriter setObjectCountCallback(ObjectCountCallback callback) {
 	 *
 	 * @param clientShallowCommits
 	 *            the shallow commits in the client
-	 * @since 4.1
 	 */
 	public void setClientShallowCommits(Set<ObjectId> clientShallowCommits) {
 		stats.clientShallowCommits = Collections
-				.unmodifiableSet(new HashSet<ObjectId>(clientShallowCommits));
+				.unmodifiableSet(new HashSet<>(clientShallowCommits));
 	}
 
 	/**
@@ -742,21 +744,20 @@ public void preparePack(ProgressMonitor countingMonitor,
 	 *            Must not be {@code null}.
 	 * @throws IOException
 	 *            an I/O problem occured while reading objects.
-	 *
-	 * @since 4.5
 	 */
 	public void preparePack(ProgressMonitor countingMonitor,
 			@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have,
 			@NonNull Set<? extends ObjectId> shallow) throws IOException {
-		ObjectWalk ow;
-		if (shallowPack) {
-			ow = new DepthWalk.ObjectWalk(reader, depth - 1);
-		} else {
-			ow = new ObjectWalk(reader);
+		try (ObjectWalk ow = getObjectWalk()) {
+			ow.assumeShallow(shallow);
+			preparePack(countingMonitor, ow, want, have);
 		}
-		ow.assumeShallow(shallow);
-		preparePack(countingMonitor, ow, want, have);
+	}
+
+	private ObjectWalk getObjectWalk() {
+		return shallowPack ? new DepthWalk.ObjectWalk(reader, depth - 1)
+				: new ObjectWalk(reader);
 	}
 
 	/**
@@ -917,7 +918,7 @@ private List<ObjectToPack> sortByName() {
 			cnt += objectsLists[OBJ_BLOB].size();
 			cnt += objectsLists[OBJ_TAG].size();
 
-			sortedByName = new BlockList<ObjectToPack>(cnt);
+			sortedByName = new BlockList<>(cnt);
 			sortedByName.addAll(objectsLists[OBJ_COMMIT]);
 			sortedByName.addAll(objectsLists[OBJ_TREE]);
 			sortedByName.addAll(objectsLists[OBJ_BLOB]);
@@ -1089,8 +1090,6 @@ public State getState() {
 
 	/**
 	 * Release all resources used by this writer.
-	 *
-	 * @since 4.0
 	 */
 	@Override
 	public void close() {
@@ -1113,7 +1112,7 @@ private void searchForReuse(ProgressMonitor monitor) throws IOException {
 		beginPhase(PackingPhase.FINDING_SOURCES, monitor, cnt);
 		if (cnt <= 4096) {
 			// For small object counts, do everything as one list.
-			BlockList<ObjectToPack> tmp = new BlockList<ObjectToPack>((int) cnt);
+			BlockList<ObjectToPack> tmp = new BlockList<>((int) cnt);
 			tmp.addAll(objectsLists[OBJ_TAG]);
 			tmp.addAll(objectsLists[OBJ_COMMIT]);
 			tmp.addAll(objectsLists[OBJ_TREE]);
@@ -1259,6 +1258,7 @@ private void searchForDeltas(ProgressMonitor monitor)
 		// bigger ones, because source files grow and hardly ever shrink.
 		//
 		Arrays.sort(list, 0, cnt, new Comparator<ObjectToPack>() {
+			@Override
 			public int compare(ObjectToPack a, ObjectToPack b) {
 				int cmp = (a.isDoNotDelta() ? 1 : 0)
 						- (b.isDoNotDelta() ? 1 : 0);
@@ -1404,6 +1404,7 @@ private void parallelDeltaSearch(ProgressMonitor monitor,
 			// can schedule these for us.
 			for (final DeltaTask task : taskBlock.tasks) {
 				executor.execute(new Runnable() {
+					@Override
 					public void run() {
 						try {
 							task.call();
@@ -1446,7 +1447,7 @@ public void run() {
 	private static void runTasks(ExecutorService pool,
 			ThreadSafeProgressMonitor pm,
 			DeltaTask.Block tb, List<Throwable> errors) throws IOException {
-		List<Future<?>> futures = new ArrayList<Future<?>>(tb.tasks.size());
+		List<Future<?>> futures = new ArrayList<>(tb.tasks.size());
 		for (DeltaTask task : tb.tasks)
 			futures.add(pool.submit(task));
 
@@ -1675,7 +1676,7 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 			}
 		}
 
-		List<ObjectId> all = new ArrayList<ObjectId>(want.size() + have.size());
+		List<ObjectId> all = new ArrayList<>(want.size() + have.size());
 		all.addAll(want);
 		all.addAll(have);
 
@@ -1693,9 +1694,9 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 				walker.sort(RevSort.BOUNDARY, true);
 		}
 
-		List<RevObject> wantObjs = new ArrayList<RevObject>(want.size());
-		List<RevObject> haveObjs = new ArrayList<RevObject>(haveEst);
-		List<RevTag> wantTags = new ArrayList<RevTag>(want.size());
+		List<RevObject> wantObjs = new ArrayList<>(want.size());
+		List<RevObject> haveObjs = new ArrayList<>(haveEst);
+		List<RevTag> wantTags = new ArrayList<>(want.size());
 
 		// Retrieve the RevWalk's versions of "want" and "have" objects to
 		// maintain any state previously set in the RevWalk.
@@ -1726,7 +1727,7 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 		}
 
 		if (!wantTags.isEmpty()) {
-			all = new ArrayList<ObjectId>(wantTags.size());
+			all = new ArrayList<>(wantTags.size());
 			for (RevTag tag : wantTags)
 				all.add(tag.getObject());
 			q = walker.parseAny(all, true);
@@ -1769,8 +1770,8 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 			walker.markUninteresting(obj);
 
 		final int maxBases = config.getDeltaSearchWindowSize();
-		Set<RevTree> baseTrees = new HashSet<RevTree>();
-		BlockList<RevCommit> commits = new BlockList<RevCommit>();
+		Set<RevTree> baseTrees = new HashSet<>();
+		BlockList<RevCommit> commits = new BlockList<>();
 		Set<ObjectId> roots = new HashSet<>();
 		RevCommit c;
 		while ((c = walker.next()) != null) {
@@ -2258,8 +2259,6 @@ public long getTotalObjects() {
 		 * @return the count of objects that needed to be discovered through an
 		 *         object walk because they were not found in bitmap indices.
 		 *         Returns -1 if no bitmap indices were found.
-		 *
-		 * @since 4.0
 		 */
 		public long getBitmapIndexMisses() {
 			return statistics.getBitmapIndexMisses();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index 77311ab..07a03b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -91,6 +91,7 @@ class PackWriterBitmapPreparer {
 	private static final int DAY_IN_SECONDS = 24 * 60 * 60;
 
 	private static final Comparator<BitmapBuilderEntry> ORDER_BY_CARDINALITY = new Comparator<BitmapBuilderEntry>() {
+		@Override
 		public int compare(BitmapBuilderEntry a, BitmapBuilderEntry b) {
 			return Integer.signum(a.getBuilder().cardinality()
 					- b.getBuilder().cardinality());
@@ -167,7 +168,7 @@ Collection<BitmapCommit> selectCommits(int expectedCommitCount)
 		pm.endTask();
 
 		int totCommits = selectionHelper.getCommitCount();
-		BlockList<BitmapCommit> selections = new BlockList<BitmapCommit>(
+		BlockList<BitmapCommit> selections = new BlockList<>(
 				totCommits / recentCommitSpan + 1);
 		for (BitmapCommit reuse : selectionHelper.reusedCommits) {
 			selections.add(reuse);
@@ -194,7 +195,7 @@ Collection<BitmapCommit> selectCommits(int expectedCommitCount)
 			// better compression/on the run-length encoding of the XORs between
 			// them.
 			List<List<BitmapCommit>> chains =
-					new ArrayList<List<BitmapCommit>>();
+					new ArrayList<>();
 
 			// Mark the current branch as inactive if its tip commit isn't
 			// recent and there are an excessive number of branches, to
@@ -286,7 +287,7 @@ Collection<BitmapCommit> selectCommits(int expectedCommitCount)
 				}
 
 				if (longestAncestorChain == null) {
-					longestAncestorChain = new ArrayList<BitmapCommit>();
+					longestAncestorChain = new ArrayList<>();
 					chains.add(longestAncestorChain);
 				}
 				longestAncestorChain.add(new BitmapCommit(
@@ -375,7 +376,7 @@ private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
 			int expectedCommitCount) throws IncorrectObjectTypeException,
 					IOException, MissingObjectException {
 		BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
-		List<BitmapCommit> reuseCommits = new ArrayList<BitmapCommit>();
+		List<BitmapCommit> reuseCommits = new ArrayList<>();
 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
 			// More recent commits did not have the reuse flag set, so skip them
 			if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) {
@@ -397,9 +398,9 @@ private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
 
 		// 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<BitmapBuilderEntry> tipCommitBitmaps = new ArrayList<BitmapBuilderEntry>(
+		List<BitmapBuilderEntry> tipCommitBitmaps = new ArrayList<>(
 				want.size());
-		Set<RevCommit> peeledWant = new HashSet<RevCommit>(want.size());
+		Set<RevCommit> peeledWant = new HashSet<>(want.size());
 		for (AnyObjectId objectId : want) {
 			RevObject ro = rw.peel(rw.parseAny(objectId));
 			if (!(ro instanceof RevCommit) || reuse.contains(ro)) {
@@ -579,20 +580,24 @@ private static final class CommitSelectionHelper implements Iterable<RevCommit>
 			this.reusedCommits = reuse;
 		}
 
+		@Override
 		public Iterator<RevCommit> iterator() {
 			// Member variables referenced by this iterator will have synthetic
 			// accessors generated for them if they are made private.
 			return new Iterator<RevCommit>() {
 				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();
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
new file mode 100644
index 0000000..3651631
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
@@ -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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
index 11081d5..f964bf2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -159,6 +159,7 @@ public final int getByte(int index) {
 	 * @return &lt; 0 if this id comes before other; 0 if this id is equal to
 	 *         other; &gt; 0 if this id comes after other.
 	 */
+	@Override
 	public final int compareTo(final AnyObjectId other) {
 		if (this == other)
 			return 0;
@@ -261,6 +262,7 @@ public boolean startsWith(final AbbreviatedObjectId abbr) {
 		return abbr.prefixCompare(this) == 0;
 	}
 
+	@Override
 	public final int hashCode() {
 		return w2;
 	}
@@ -276,6 +278,7 @@ public final boolean equals(final AnyObjectId other) {
 		return other != null ? equals(this, other) : false;
 	}
 
+	@Override
 	public final boolean equals(final Object o) {
 		if (o instanceof AnyObjectId)
 			return equals((AnyObjectId) o);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index 670f9a9..de1003b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -218,7 +218,7 @@ public File getObjectDirectory() {
 	public B addAlternateObjectDirectory(File other) {
 		if (other != null) {
 			if (alternateObjectDirectories == null)
-				alternateObjectDirectories = new LinkedList<File>();
+				alternateObjectDirectories = new LinkedList<>();
 			alternateObjectDirectories.add(other);
 		}
 		return self();
@@ -429,7 +429,7 @@ public B readEnvironment(SystemReader sr) {
 	public B addCeilingDirectory(File root) {
 		if (root != null) {
 			if (ceilingDirectories == null)
-				ceilingDirectories = new LinkedList<File>();
+				ceilingDirectories = new LinkedList<>();
 			ceilingDirectories.add(root);
 		}
 		return self();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index 653c9f6..3f6995d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -120,7 +120,7 @@ public class BatchRefUpdate {
 	 */
 	protected BatchRefUpdate(RefDatabase refdb) {
 		this.refdb = refdb;
-		this.commands = new ArrayList<ReceiveCommand>();
+		this.commands = new ArrayList<>();
 		this.atomic = refdb.performsAtomicTransactions();
 	}
 
@@ -400,7 +400,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
 		}
 
 		monitor.beginTask(JGitText.get().updatingReferences, commands.size());
-		List<ReceiveCommand> commands2 = new ArrayList<ReceiveCommand>(
+		List<ReceiveCommand> commands2 = new ArrayList<>(
 				commands.size());
 		// First delete refs. This may free the name space for some of the
 		// updates.
@@ -431,7 +431,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
 		}
 		if (!commands2.isEmpty()) {
 			// What part of the name space is already taken
-			Collection<String> takenNames = new HashSet<String>(refdb.getRefs(
+			Collection<String> takenNames = new HashSet<>(refdb.getRefs(
 					RefDatabase.ALL).keySet());
 			Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
 
@@ -525,7 +525,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor)
 
 	private static Collection<String> getTakenPrefixes(
 			final Collection<String> names) {
-		Collection<String> ref = new HashSet<String>();
+		Collection<String> ref = new HashSet<>();
 		for (String name : names)
 			ref.addAll(getPrefixes(name));
 		return ref;
@@ -539,7 +539,7 @@ private static void addRefToPrefixes(Collection<String> prefixes,
 	}
 
 	static Collection<String> getPrefixes(String s) {
-		Collection<String> ret = new HashSet<String>();
+		Collection<String> ret = new HashSet<>();
 		int p1 = s.indexOf('/');
 		while (p1 > 0) {
 			ret.add(s.substring(0, p1));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
index a3859ab..54c8052 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -68,10 +68,12 @@ public void setDelayStart(long time, TimeUnit unit) {
 		delayStartUnit = unit;
 	}
 
+	@Override
 	public void start(int totalTasks) {
 		// Ignore the number of tasks.
 	}
 
+	@Override
 	public void beginTask(String title, int work) {
 		endTask();
 		task = new Task(title, work);
@@ -79,11 +81,13 @@ public void beginTask(String title, int work) {
 			task.delay(delayStartTime, delayStartUnit);
 	}
 
+	@Override
 	public void update(int completed) {
 		if (task != null)
 			task.update(this, completed);
 	}
 
+	@Override
 	public void endTask() {
 		if (task != null) {
 			task.end(this);
@@ -91,6 +95,7 @@ public void endTask() {
 		}
 	}
 
+	@Override
 	public boolean isCancelled() {
 		return false;
 	}
@@ -178,6 +183,7 @@ void delay(long time, TimeUnit unit) {
 			timerFuture = WorkQueue.getExecutor().schedule(this, time, unit);
 		}
 
+		@Override
 		public void run() {
 			display = true;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
index 9ddff25..00f42a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
@@ -108,6 +108,7 @@ public interface Bitmap extends Iterable<BitmapObject> {
 		 *
 		 * @return an Iterator.
 		 */
+		@Override
 		Iterator<BitmapObject> iterator();
 	}
 
@@ -166,6 +167,7 @@ public interface BitmapBuilder extends Bitmap {
 		 *            the other bitmap
 		 * @return the current builder.
 		 */
+		@Override
 		BitmapBuilder or(Bitmap other);
 
 		/**
@@ -176,6 +178,7 @@ public interface BitmapBuilder extends Bitmap {
 		 *            the other bitmap
 		 * @return the current builder.
 		 */
+		@Override
 		BitmapBuilder andNot(Bitmap other);
 
 		/**
@@ -185,6 +188,7 @@ public interface BitmapBuilder extends Bitmap {
 		 *            the other bitmap
 		 * @return the current builder.
 		 */
+		@Override
 		BitmapBuilder xor(Bitmap other);
 
 		/** @return the fully built immutable bitmap */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index 8fe8a96..f45c71c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -58,6 +58,7 @@
 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;
@@ -121,7 +122,7 @@ public Config() {
 	 */
 	public Config(Config defaultConfig) {
 		baseConfig = defaultConfig;
-		state = new AtomicReference<ConfigSnapshot>(newState());
+		state = new AtomicReference<>(newState());
 	}
 
 	/**
@@ -895,7 +896,7 @@ public <T extends Enum<?>> void setEnum(final String section,
 		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);
 	}
 
@@ -959,7 +960,7 @@ private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
 			final String section,
 			final String subsection) {
 		final int max = srcState.entryList.size();
-		final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max);
+		final ArrayList<ConfigLine> r = new ArrayList<>(max);
 
 		boolean lastWasMatch = false;
 		for (ConfigLine e : srcState.entryList) {
@@ -1074,7 +1075,7 @@ private static List<ConfigLine> copy(final ConfigSnapshot src,
 		// for a new section header. Assume that and allocate the space.
 		//
 		final int max = src.entryList.size() + values.size() + 1;
-		final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max);
+		final ArrayList<ConfigLine> r = new ArrayList<>(max);
 		r.addAll(src.entryList);
 		return r;
 	}
@@ -1162,7 +1163,7 @@ private List<ConfigLine> fromTextRecurse(final String text, int depth)
 			throw new ConfigInvalidException(
 					JGitText.get().tooManyIncludeRecursions);
 		}
-		final List<ConfigLine> newEntries = new ArrayList<ConfigLine>();
+		final List<ConfigLine> newEntries = new ArrayList<>();
 		final StringReader in = new StringReader(text);
 		ConfigLine last = null;
 		ConfigLine e = new ConfigLine();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 87a95b9..2618180 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -291,6 +291,20 @@ public class ConfigConstants {
 	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
 	 */
@@ -381,4 +395,16 @@ public class ConfigConstants {
 	 * @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";
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java
index 5ed129e..f5ada13 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigSnapshot.java
@@ -78,7 +78,7 @@ class ConfigSnapshot {
 
 	ConfigSnapshot(List<ConfigLine> entries, ConfigSnapshot base) {
 		entryList = entries;
-		cache = new ConcurrentHashMap<Object, Object>(16, 0.75f, 1);
+		cache = new ConcurrentHashMap<>(16, 0.75f, 1);
 		baseState = base;
 	}
 
@@ -112,7 +112,7 @@ private Map<String, String> getNamesInternal(String section,
 		if (idx < 0)
 			idx = -(idx + 1);
 
-		Map<String, String> m = new LinkedHashMap<String, String>();
+		Map<String, String> m = new LinkedHashMap<>();
 		while (idx < s.size()) {
 			ConfigLine e = s.get(idx++);
 			if (!e.match(section, subsection))
@@ -187,7 +187,7 @@ private List<ConfigLine> sorted() {
 	}
 
 	private static List<ConfigLine> sort(List<ConfigLine> in) {
-		List<ConfigLine> sorted = new ArrayList<ConfigLine>(in.size());
+		List<ConfigLine> 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 int compare2(
 	}
 
 	private static class LineComparator implements Comparator<ConfigLine> {
+		@Override
 		public int compare(ConfigLine a, ConfigLine b) {
 			return compare2(
 					a.section, a.subsection, a.name,
@@ -236,8 +237,8 @@ private static class SectionNames {
 		final Map<String, Set<String>> subsections;
 
 		SectionNames(ConfigSnapshot cfg) {
-			Map<String, String> sec = new LinkedHashMap<String, String>();
-			Map<String, Set<String>> sub = new HashMap<String, Set<String>>();
+			Map<String, String> sec = new LinkedHashMap<>();
+			Map<String, Set<String>> sub = new HashMap<>();
 			while (cfg != null) {
 				for (ConfigLine e : cfg.entryList) {
 					if (e.section == null)
@@ -252,7 +253,7 @@ private static class SectionNames {
 
 					Set<String> m = sub.get(l1);
 					if (m == null) {
-						m = new LinkedHashSet<String>();
+						m = new LinkedHashSet<>();
 						sub.put(l1, m);
 					}
 					m.add(e.subsection);
@@ -286,14 +287,17 @@ public boolean contains(Object needle) {
 		public Iterator<String> iterator() {
 			final Iterator<String> i = names.values().iterator();
 			return new Iterator<String>() {
+				@Override
 				public boolean hasNext() {
 					return i.hasNext();
 				}
 
+				@Override
 				public String next() {
 					return i.next();
 				}
 
+				@Override
 				public void remove() {
 					throw new UnsupportedOperationException();
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index ff80672..bda1a27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -665,6 +665,13 @@ public static int decodeTypeString(final AnyObjectId id,
 	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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 83efd43..40aba63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -58,6 +58,7 @@
 public class CoreConfig {
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<CoreConfig> KEY = new SectionParser<CoreConfig>() {
+		@Override
 		public CoreConfig parse(final Config cfg) {
 			return new CoreConfig(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
index eabcbbf..c236c35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
@@ -51,22 +51,27 @@
  */
 public abstract class EmptyProgressMonitor implements ProgressMonitor {
 
+	@Override
 	public void start(int totalTasks) {
 		// empty
 	}
 
+	@Override
 	public void beginTask(String title, int totalWork) {
 		// empty
 	}
 
+	@Override
 	public void update(int completed) {
 		// empty
 	}
 
+	@Override
 	public void endTask() {
 		// empty
 	}
 
+	@Override
 	public boolean isCancelled() {
 		return false;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
index f295f5b..a489461 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
@@ -86,6 +86,7 @@ public abstract class FileMode {
 	@SuppressWarnings("synthetic-access")
 	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;
 		}
@@ -95,6 +96,7 @@ public boolean equals(final int modeBits) {
 	@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;
 		}
@@ -104,6 +106,7 @@ public boolean equals(final int modeBits) {
 	@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;
 		}
@@ -113,6 +116,7 @@ public boolean equals(final int modeBits) {
 	@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;
 		}
@@ -122,6 +126,7 @@ public boolean equals(final int modeBits) {
 	@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;
 		}
@@ -131,6 +136,7 @@ public boolean equals(final int modeBits) {
 	@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;
 		}
@@ -258,6 +264,7 @@ public int getObjectType() {
 	}
 
 	/** Format this mode as an octal string (for debugging only). */
+	@Override
 	public String toString() {
 		return Integer.toOctalString(modeBits);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java
new file mode 100644
index 0000000..bded527
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java
@@ -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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index af6a4fb..e544b72 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -253,19 +253,19 @@ public TreeFilter clone() {
 
 	private final WorkingTreeIterator initialWorkingTreeIterator;
 
-	private Set<String> added = new HashSet<String>();
+	private Set<String> added = new HashSet<>();
 
-	private Set<String> changed = new HashSet<String>();
+	private Set<String> changed = new HashSet<>();
 
-	private Set<String> removed = new HashSet<String>();
+	private Set<String> removed = new HashSet<>();
 
-	private Set<String> missing = new HashSet<String>();
+	private Set<String> missing = new HashSet<>();
 
-	private Set<String> modified = new HashSet<String>();
+	private Set<String> modified = new HashSet<>();
 
-	private Set<String> untracked = new HashSet<String>();
+	private Set<String> untracked = new HashSet<>();
 
-	private Map<String, StageState> conflicts = new HashMap<String, StageState>();
+	private Map<String, StageState> conflicts = new HashMap<>();
 
 	private Set<String> ignored;
 
@@ -275,11 +275,11 @@ public TreeFilter clone() {
 
 	private IndexDiffFilter indexDiffFilter;
 
-	private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<String, IndexDiff>();
+	private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<>();
 
 	private IgnoreSubmoduleMode ignoreSubmoduleMode = null;
 
-	private Map<FileMode, Set<String>> fileModes = new HashMap<FileMode, Set<String>>();
+	private Map<FileMode, Set<String>> fileModes = new HashMap<>();
 
 	/**
 	 * Construct an IndexDiff
@@ -342,6 +342,7 @@ public interface WorkingTreeIteratorFactory {
 	}
 
 	private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() {
+		@Override
 		public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
 			return new FileTreeIterator(repo);
 		}
@@ -416,7 +417,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
 			treeWalk.addTree(new DirCacheIterator(dirCache));
 			treeWalk.addTree(initialWorkingTreeIterator);
 			initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
-			Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
+			Collection<TreeFilter> filters = new ArrayList<>(4);
 
 			if (monitor != null) {
 				// Get the maximum size of the work tree and index
@@ -517,7 +518,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
 					String path = treeWalk.getPathString();
 					if (path != null) {
 						if (values == null)
-							values = new HashSet<String>();
+							values = new HashSet<>();
 						values.add(path);
 						fileModes.put(treeWalk.getFileMode(i), values);
 					}
@@ -686,7 +687,7 @@ public Set<String> getIgnoredNotInIndex() {
 	 */
 	public Set<String> getAssumeUnchanged() {
 		if (assumeUnchanged == null) {
-			HashSet<String> unchanged = new HashSet<String>();
+			HashSet<String> unchanged = new HashSet<>();
 			for (int i = 0; i < dirCache.getEntryCount(); i++)
 				if (dirCache.getEntry(i).isAssumeValid())
 					unchanged.add(dirCache.getEntry(i).getPathString());
@@ -700,7 +701,7 @@ public Set<String> getAssumeUnchanged() {
 	 */
 	public Set<String> getUntrackedFolders() {
 		return ((indexDiffFilter == null) ? Collections.<String> emptySet()
-				: new HashSet<String>(indexDiffFilter.getUntrackedFolders()));
+				: new HashSet<>(indexDiffFilter.getUntrackedFolders()));
 	}
 
 	/**
@@ -726,7 +727,7 @@ public FileMode getIndexMode(final String path) {
 	public Set<String> getPathsWithIndexMode(final FileMode mode) {
 		Set<String> paths = fileModes.get(mode);
 		if (paths == null)
-			paths = new HashSet<String>();
+			paths = new HashSet<>();
 		return paths;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
index 1a49ae9..4b14d12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
@@ -209,6 +209,24 @@ public void fromRaw(final int[] ints, final int p) {
 	}
 
 	/**
+	 * Convert an ObjectId from binary representation expressed in integers.
+	 *
+	 * @param a
+	 * @param b
+	 * @param c
+	 * @param d
+	 * @param e
+	 * @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
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
index d05c8c6..497beb0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
@@ -56,22 +56,27 @@ private NullProgressMonitor() {
 		// Do not let others instantiate
 	}
 
+	@Override
 	public void start(int totalTasks) {
 		// Do not report.
 	}
 
+	@Override
 	public void beginTask(String title, int totalWork) {
 		// Do not report.
 	}
 
+	@Override
 	public void update(int completed) {
 		// Do not report.
 	}
 
+	@Override
 	public boolean isCancelled() {
 		return false;
 	}
 
+	@Override
 	public void endTask() {
 		// Do not report.
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index feecbd8..d8ddcd5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.lib;
 
+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;
@@ -84,8 +85,10 @@
 
 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;
 
@@ -136,6 +139,9 @@ public class ObjectChecker {
 	/** Header "tagger " */
 	public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
 
+	/** Path ".gitmodules" */
+	private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES);
+
 	/**
 	 * Potential issues identified by the checker.
 	 *
@@ -199,6 +205,8 @@ public String getMessageId() {
 	private boolean windows;
 	private boolean macosx;
 
+	private final List<GitmoduleEntry> gitsubmodules = new ArrayList<>();
+
 	/**
 	 * Enable accepting specific malformed (but not horribly broken) objects.
 	 *
@@ -611,7 +619,7 @@ public void checkTree(@Nullable AnyObjectId id, byte[] raw)
 		int ptr = 0;
 		int lastNameB = 0, lastNameE = 0, lastMode = 0;
 		Set<String> normalized = windows || macosx
-				? new HashSet<String>()
+				? new HashSet<>()
 				: null;
 
 		while (ptr < sz) {
@@ -678,9 +686,15 @@ public void checkTree(@Nullable AnyObjectId id, byte[] raw)
 				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));
+			}
 		}
 	}
 
@@ -845,10 +859,9 @@ private void checkPathSegment2(byte[] raw, int ptr, int end,
 
 	// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
 	// to ".git" therefore we should prevent such names
-	private boolean isMacHFSGit(byte[] raw, int ptr, int end,
+	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]) {
@@ -904,17 +917,31 @@ private boolean isMacHFSGit(byte[] raw, int ptr, int end,
 				}
 				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 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) {
@@ -1021,6 +1048,104 @@ && toLower(buf[p + 1]) == 'i'
 				&& 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;
@@ -1082,4 +1207,17 @@ private String normalize(byte[] raw, int ptr, int end) {
 		String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
 		return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
 	}
+
+	/**
+	 * 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 <tree, blob>}.
+	 *
+	 * @since 4.7.5
+	 */
+	public List<GitmoduleEntry> getGitsubmodules() {
+		return gitsubmodules;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
index 2a2d67d..991f03f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
@@ -248,8 +248,17 @@ private static final ObjectId fromHexString(final byte[] bs, int p) {
 		}
 	}
 
-	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
+	 * @param new_2
+	 * @param new_3
+	 * @param new_4
+	 * @param new_5
+	 * @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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
index c286f5e..636716b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -67,11 +67,13 @@ public Unpeeled(@NonNull Storage st, @NonNull String name,
 			super(st, name, id);
 		}
 
+		@Override
 		@Nullable
 		public ObjectId getPeeledObjectId() {
 			return null;
 		}
 
+		@Override
 		public boolean isPeeled() {
 			return false;
 		}
@@ -99,11 +101,13 @@ public PeeledTag(@NonNull Storage st, @NonNull String name,
 			peeledObjectId = p;
 		}
 
+		@Override
 		@NonNull
 		public ObjectId getPeeledObjectId() {
 			return peeledObjectId;
 		}
 
+		@Override
 		public boolean isPeeled() {
 			return true;
 		}
@@ -127,11 +131,13 @@ public PeeledNonTag(@NonNull Storage st, @NonNull String name,
 			super(st, name, id);
 		}
 
+		@Override
 		@Nullable
 		public ObjectId getPeeledObjectId() {
 			return null;
 		}
 
+		@Override
 		public boolean isPeeled() {
 			return true;
 		}
@@ -161,30 +167,36 @@ protected ObjectIdRef(@NonNull Storage st, @NonNull String name,
 		this.objectId = id;
 	}
 
+	@Override
 	@NonNull
 	public String getName() {
 		return name;
 	}
 
+	@Override
 	public boolean isSymbolic() {
 		return false;
 	}
 
+	@Override
 	@NonNull
 	public Ref getLeaf() {
 		return this;
 	}
 
+	@Override
 	@NonNull
 	public Ref getTarget() {
 		return this;
 	}
 
+	@Override
 	@Nullable
 	public ObjectId getObjectId() {
 		return objectId;
 	}
 
+	@Override
 	@NonNull
 	public Storage getStorage() {
 		return storage;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
index faed64b..43fc7bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -111,6 +111,7 @@ public V get(final AnyObjectId 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;
 	}
@@ -187,16 +188,19 @@ public boolean isEmpty() {
 		return size == 0;
 	}
 
+	@Override
 	public Iterator<V> iterator() {
 		return new Iterator<V>() {
 			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++];
@@ -208,6 +212,7 @@ public V next() {
 				throw new NoSuchElementException();
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
index 4c51279..857ec9b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -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,41 +107,50 @@ public static abstract class Filter extends ObjectInserter {
 			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() {
 			final ObjectReader dr = delegate().newReader();
 			return new ObjectReader.Filter() {
@@ -157,24 +166,24 @@ public ObjectInserter getCreatedFromInserter() {
 			};
 		}
 
+		@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. */
 	protected ObjectInserter() {
-		digest = Constants.newMessageDigest();
 	}
 
 	/**
@@ -208,10 +217,12 @@ protected ObjectInserter() {
 		return b;
 	}
 
-	/** @return digest to help compute an ObjectId */
-	protected MessageDigest digest() {
-		digest.reset();
-		return digest;
+	/**
+	 * @return digest to help compute an ObjectId
+	 * @since 4.7
+	 */
+	protected SHA1 digest() {
+		return hasher.reset();
 	}
 
 	/**
@@ -241,13 +252,13 @@ public ObjectId idFor(int type, byte[] data) {
 	 * @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();
 	}
 
 	/**
@@ -266,7 +277,7 @@ public ObjectId idFor(int type, byte[] data, int off, int len) {
 	 */
 	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));
@@ -279,7 +290,7 @@ public ObjectId idFor(int objectType, long length, InputStream in)
 			md.update(buf, 0, n);
 			length -= n;
 		}
-		return ObjectId.fromRaw(md.digest());
+		return md.toObjectId();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
index 372da98..f39f291 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -134,7 +134,7 @@ public AbbreviatedObjectId abbreviate(AnyObjectId objectId, int len)
 		Collection<ObjectId> matches = resolve(abbrev);
 		while (1 < matches.size() && len < Constants.OBJECT_ID_STRING_LENGTH) {
 			abbrev = objectId.abbreviate(++len);
-			List<ObjectId> n = new ArrayList<ObjectId>(8);
+			List<ObjectId> n = new ArrayList<>(8);
 			for (ObjectId candidate : matches) {
 				if (abbrev.prefixCompare(candidate) == 0)
 					n.add(candidate);
@@ -286,6 +286,7 @@ public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
 		return new AsyncObjectLoaderQueue<T>() {
 			private T cur;
 
+			@Override
 			public boolean next() throws MissingObjectException, IOException {
 				if (idItr.hasNext()) {
 					cur = idItr.next();
@@ -295,22 +296,27 @@ public boolean next() throws MissingObjectException, IOException {
 				}
 			}
 
+			@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.
@@ -370,6 +376,7 @@ public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize(
 
 			private long sz;
 
+			@Override
 			public boolean next() throws MissingObjectException, IOException {
 				if (idItr.hasNext()) {
 					cur = idItr.next();
@@ -380,22 +387,27 @@ public boolean next() throws MissingObjectException, IOException {
 				}
 			}
 
+			@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.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
index 627ccaa..45757e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -336,6 +336,7 @@ public int getTimeZoneOffset() {
 	/**
 	 * Hashcode is based only on the email address and timestamp.
 	 */
+	@Override
 	public int hashCode() {
 		int hc = getEmailAddress().hashCode();
 		hc *= 31;
@@ -343,6 +344,7 @@ public int hashCode() {
 		return hc;
 	}
 
+	@Override
 	public boolean equals(final Object o) {
 		if (o instanceof PersonIdent) {
 			final PersonIdent p = (PersonIdent) o;
@@ -370,6 +372,7 @@ public String toExternalString() {
 		return r.toString();
 	}
 
+	@Override
 	@SuppressWarnings("nls")
 	public String toString() {
 		final StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
index 75a3592..1047a6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
@@ -89,7 +89,7 @@ public List<RebaseTodoLine> readRebaseTodo(String path,
 		byte[] buf = IO.readFully(new File(repo.getDirectory(), path));
 		int ptr = 0;
 		int tokenBegin = 0;
-		List<RebaseTodoLine> r = new LinkedList<RebaseTodoLine>();
+		List<RebaseTodoLine> r = new LinkedList<>();
 		while (ptr < buf.length) {
 			tokenBegin = ptr;
 			ptr = RawParseUtils.nextLF(buf, ptr);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
index c6e1029..95e3386 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
@@ -60,6 +60,7 @@ public class RefComparator implements Comparator<Ref> {
 	/** Singleton instance of RefComparator */
 	public static final RefComparator INSTANCE = new RefComparator();
 
+	@Override
 	public int compare(final Ref o1, final Ref o2) {
 		return compareTo(o1, o2);
 	}
@@ -72,7 +73,7 @@ public int compare(final Ref o1, final Ref o2) {
 	 * @return sorted collection of refs
 	 */
 	public static Collection<Ref> sort(final Collection<Ref> refs) {
-		final List<Ref> r = new ArrayList<Ref>(refs);
+		final List<Ref> r = new ArrayList<>(refs);
 		Collections.sort(r, INSTANCE);
 		return r;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 517c8aa..59a104b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -150,7 +150,7 @@ public Collection<String> getConflictingNames(String name)
 			lastSlash = name.lastIndexOf('/', lastSlash - 1);
 		}
 
-		List<String> conflicting = new ArrayList<String>();
+		List<String> conflicting = new ArrayList<>();
 		// Cannot be the container of an existing reference.
 		String prefix = name + '/';
 		for (String existing : allRefs.keySet())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index c5b2ef8..aa70f42 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -4,6 +4,7 @@
  * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2006-2012, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com>
+ * Copyright (C) 2017, Wim Jongman <wim.jongman@remainsoftware.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -47,6 +48,8 @@
 
 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;
@@ -65,6 +68,7 @@
 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;
@@ -110,6 +114,17 @@ public abstract class Repository implements AutoCloseable {
 	private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
 	private static final ListenerList globalListeners = new ListenerList();
 
+	/**
+	 * 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);
+
 	/** @return the global listener list observing all events in this JVM. */
 	public static ListenerList getGlobalListenerList() {
 		return globalListeners;
@@ -888,11 +903,12 @@ public void close() {
 		} 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()) {
-				IllegalStateException e = new IllegalStateException();
-				LOG.debug(JGitText.get().corruptUseCnt, e);
+				LOG.debug(message, new IllegalStateException());
 			} else {
-				LOG.warn(JGitText.get().corruptUseCnt);
+				LOG.warn(message);
 			}
 			if (RepositoryCache.isCached(this)) {
 				closedAt.set(System.currentTimeMillis());
@@ -910,7 +926,6 @@ protected void doClose() {
 		getRefDatabase().close();
 	}
 
-	@SuppressWarnings("nls")
 	@Override
 	@NonNull
 	public String toString() {
@@ -921,7 +936,7 @@ public String toString() {
 		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$
 	}
 
 	/**
@@ -1051,7 +1066,7 @@ public Map<String, Ref> getAllRefs() {
 		try {
 			return getRefDatabase().getRefs(RefDatabase.ALL);
 		} catch (IOException e) {
-			return new HashMap<String, Ref>();
+			return new HashMap<>();
 		}
 	}
 
@@ -1065,7 +1080,7 @@ public Map<String, Ref> getTags() {
 		try {
 			return getRefDatabase().getRefs(Constants.R_TAGS);
 		} catch (IOException e) {
-			return new HashMap<String, Ref>();
+			return new HashMap<>();
 		}
 	}
 
@@ -1100,7 +1115,7 @@ public Ref peel(final Ref ref) {
 	@NonNull
 	public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 		Map<String, Ref> allRefs = getAllRefs();
-		Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
+		Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
 		for (Ref ref : allRefs.values()) {
 			ref = peel(ref);
 			AnyObjectId target = ref.getPeeledObjectId();
@@ -1112,7 +1127,7 @@ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 				// 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<Ref>(oset);
+					oset = new HashSet<>(oset);
 				}
 				ret.put(target, oset);
 				oset.add(ref);
@@ -1280,10 +1295,12 @@ public RepositoryState getRepositoryState() {
 	 */
 	public static boolean isValidRefName(final String refName) {
 		final int len = refName.length();
-		if (len == 0)
+		if (len == 0) {
 			return false;
-		if (refName.endsWith(".lock")) //$NON-NLS-1$
+		}
+		if (refName.endsWith(LOCK_SUFFIX)) {
 			return false;
+		}
 
 		// Refs may be stored as loose files so invalid paths
 		// on the local system must also be invalid refs.
@@ -1331,12 +1348,105 @@ public static boolean isValidRefName(final String refName) {
 	}
 
 	/**
+	 * Normalizes the passed branch name into a possible valid branch name. The
+	 * validity of the returned name should be checked by a subsequent call to
+	 * {@link #isValidRefName(String)}.
+	 * <p/>
+	 * Future implementations of this method could be more restrictive or more
+	 * lenient about the validity of specific characters in the returned name.
+	 * <p/>
+	 * 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) {
@@ -1574,7 +1684,7 @@ public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
 		if (raw == null)
 			return null;
 
-		LinkedList<ObjectId> heads = new LinkedList<ObjectId>();
+		LinkedList<ObjectId> heads = new LinkedList<>();
 		for (int p = 0; p < raw.length;) {
 			heads.add(ObjectId.fromString(raw, p));
 			p = RawParseUtils
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 2f1a9e1..baa5286 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -300,7 +300,7 @@ private void unregisterAndCloseRepository(final Key location) {
 	}
 
 	private Collection<Key> getKeys() {
-		return new ArrayList<Key>(cacheMap.keySet());
+		return new ArrayList<>(cacheMap.keySet());
 	}
 
 	private void clearAllExpired() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
new file mode 100644
index 0000000..3126160
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@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.lib;
+
+/**
+ * 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) {
+			return configValue.equals(s);
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
index eeab921..71d5cd7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -70,15 +70,18 @@ public SymbolicRef(@NonNull String refName, @NonNull Ref target) {
 		this.target = target;
 	}
 
+	@Override
 	@NonNull
 	public String getName() {
 		return name;
 	}
 
+	@Override
 	public boolean isSymbolic() {
 		return true;
 	}
 
+	@Override
 	@NonNull
 	public Ref getLeaf() {
 		Ref dst = getTarget();
@@ -87,26 +90,31 @@ public Ref getLeaf() {
 		return dst;
 	}
 
+	@Override
 	@NonNull
 	public Ref getTarget() {
 		return target;
 	}
 
+	@Override
 	@Nullable
 	public ObjectId getObjectId() {
 		return getLeaf().getObjectId();
 	}
 
+	@Override
 	@NonNull
 	public Storage getStorage() {
 		return Storage.LOOSE;
 	}
 
+	@Override
 	@Nullable
 	public ObjectId getPeeledObjectId() {
 		return getLeaf().getPeeledObjectId();
 	}
 
+	@Override
 	public boolean isPeeled() {
 		return getLeaf().isPeeled();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
index ff85f9b..5824a55 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
@@ -87,12 +87,14 @@ public ThreadSafeProgressMonitor(ProgressMonitor pm) {
 		this.process = new Semaphore(0);
 	}
 
+	@Override
 	public void start(int totalTasks) {
 		if (!isMainThread())
 			throw new IllegalStateException();
 		pm.start(totalTasks);
 	}
 
+	@Override
 	public void beginTask(String title, int totalWork) {
 		if (!isMainThread())
 			throw new IllegalStateException();
@@ -156,11 +158,13 @@ private void doUpdates() {
 			pm.update(cnt);
 	}
 
+	@Override
 	public void update(int completed) {
 		if (0 == pendingUpdates.getAndAdd(completed))
 			process.release();
 	}
 
+	@Override
 	public boolean isCancelled() {
 		lock.lock();
 		try {
@@ -170,6 +174,7 @@ public boolean isCancelled() {
 		}
 	}
 
+	@Override
 	public void endTask() {
 		if (!isMainThread())
 			throw new IllegalStateException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
index b8d236c..bd393dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
@@ -52,6 +52,7 @@
 public class UserConfig {
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<UserConfig> KEY = new SectionParser<UserConfig>() {
+		@Override
 		public UserConfig parse(final Config cfg) {
 			return new UserConfig(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
index 9735d19..07b87f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
@@ -67,6 +67,7 @@ class WorkQueue {
 					private final ThreadFactory baseFactory = Executors
 							.defaultThreadFactory();
 
+					@Override
 					public Thread newThread(Runnable taskBody) {
 						Thread thr = baseFactory.newThread(taskBody);
 						thr.setName("JGit-WorkQueue"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
index 112550a..04c65ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
@@ -98,11 +98,11 @@ public MergeAlgorithm(DiffAlgorithm diff) {
 	 */
 	public <S extends Sequence> MergeResult<S> merge(
 			SequenceComparator<S> cmp, S base, S ours, S theirs) {
-		List<S> sequences = new ArrayList<S>(3);
+		List<S> sequences = new ArrayList<>(3);
 		sequences.add(base);
 		sequences.add(ours);
 		sequences.add(theirs);
-		MergeResult<S> result = new MergeResult<S>(sequences);
+		MergeResult<S> result = new MergeResult<>(sequences);
 
 		if (ours.size() == 0) {
 			if (theirs.size() != 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
index 83b143b..d059391 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
@@ -167,6 +167,7 @@ public MergeConfigSectionParser(String branch) {
 			this.branch = branch;
 		}
 
+		@Override
 		public MergeConfig parse(Config cfg) {
 			return new MergeConfig(branch, cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index ee6095a..43876a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -105,7 +105,7 @@ public void formatMerge(OutputStream out, MergeResult<RawText> res,
 	@SuppressWarnings("unchecked")
 	public void formatMerge(OutputStream out, MergeResult res, String baseName,
 			String oursName, String theirsName, String charsetName) throws IOException {
-		List<String> names = new ArrayList<String>(3);
+		List<String> names = new ArrayList<>(3);
 		names.add(baseName);
 		names.add(oursName);
 		names.add(theirsName);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index 82cbf36..ca0e18a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -71,11 +71,11 @@ public String format(List<Ref> refsToMerge, Ref target) {
 		StringBuilder sb = new StringBuilder();
 		sb.append("Merge "); //$NON-NLS-1$
 
-		List<String> branches = new ArrayList<String>();
-		List<String> remoteBranches = new ArrayList<String>();
-		List<String> tags = new ArrayList<String>();
-		List<String> commits = new ArrayList<String>();
-		List<String> others = new ArrayList<String>();
+		List<String> branches = new ArrayList<>();
+		List<String> remoteBranches = new ArrayList<>();
+		List<String> tags = new ArrayList<>();
+		List<String> commits = new ArrayList<>();
+		List<String> others = new ArrayList<>();
 		for (Ref ref : refsToMerge) {
 			if (ref.getName().startsWith(Constants.R_HEADS)) {
 				branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$
@@ -95,7 +95,7 @@ public String format(List<Ref> refsToMerge, Ref target) {
 			}
 		}
 
-		List<String> listings = new ArrayList<String>();
+		List<String> listings = new ArrayList<>();
 
 		if (!branches.isEmpty())
 			listings.add(joinNames(branches, "branch", "branches")); //$NON-NLS-1$//$NON-NLS-2$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
index 106f9c7..ff3c8ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
@@ -133,14 +133,17 @@ public List<S> getSequences() {
 	 * @return an iterator over the MergeChunks. The iterator does not support
 	 * the remove operation
 	 */
+	@Override
 	public Iterator<MergeChunk> iterator() {
 		return new Iterator<MergeChunk>() {
 			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 +152,7 @@ public MergeChunk next() {
 				return new MergeChunk(srcIdx, begin, end, state);
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
index c5e615e..656480e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
@@ -80,7 +80,7 @@ public abstract class MergeStrategy {
 	 */
 	public static final ThreeWayMergeStrategy RECURSIVE = new StrategyRecursive();
 
-	private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<String, MergeStrategy>();
+	private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<>();
 
 	static {
 		register(OURS);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index e055644..f8e1998 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -147,7 +147,7 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
 	 */
 	protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
 			throws IOException {
-		ArrayList<RevCommit> baseCommits = new ArrayList<RevCommit>();
+		ArrayList<RevCommit> baseCommits = new ArrayList<>();
 		walk.reset();
 		walk.setRevFilter(RevFilter.MERGE_BASE);
 		walk.markStart(a);
@@ -181,7 +181,7 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
 			dircache = DirCache.read(reader, currentBase.getTree());
 			inCore = true;
 
-			List<RevCommit> parents = new ArrayList<RevCommit>();
+			List<RevCommit> parents = new ArrayList<>();
 			parents.add(currentBase);
 			for (int commitIdx = 1; commitIdx < baseCommits.size(); commitIdx++) {
 				RevCommit nextBase = baseCommits.get(commitIdx);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index fd5e7ef..90107be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -180,14 +180,14 @@ public enum MergeFailureReason {
 	 *
 	 * @since 3.4
 	 */
-	protected List<String> unmergedPaths = new ArrayList<String>();
+	protected List<String> unmergedPaths = new ArrayList<>();
 
 	/**
 	 * Files modified during this merge operation.
 	 *
 	 * @since 3.4
 	 */
-	protected List<String> modifiedFiles = new LinkedList<String>();
+	protected List<String> modifiedFiles = new LinkedList<>();
 
 	/**
 	 * If the merger has nothing to do for a file but check it out at the end of
@@ -195,7 +195,7 @@ public enum MergeFailureReason {
 	 *
 	 * @since 3.4
 	 */
-	protected Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<String, DirCacheEntry>();
+	protected Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<>();
 
 	/**
 	 * Paths in this list will be deleted from the local copy at the end of the
@@ -203,7 +203,7 @@ public enum MergeFailureReason {
 	 *
 	 * @since 3.4
 	 */
-	protected List<String> toBeDeleted = new ArrayList<String>();
+	protected List<String> toBeDeleted = new ArrayList<>();
 
 	/**
 	 * Low-level textual merge results. Will be passed on to the callers in case
@@ -211,14 +211,14 @@ public enum MergeFailureReason {
 	 *
 	 * @since 3.4
 	 */
-	protected Map<String, MergeResult<? extends Sequence>> mergeResults = new HashMap<String, MergeResult<? extends Sequence>>();
+	protected Map<String, MergeResult<? extends Sequence>> mergeResults = new HashMap<>();
 
 	/**
 	 * Paths for which the merge failed altogether.
 	 *
 	 * @since 3.4
 	 */
-	protected Map<String, MergeFailureReason> failingPaths = new HashMap<String, MergeFailureReason>();
+	protected Map<String, MergeFailureReason> failingPaths = new HashMap<>();
 
 	/**
 	 * Updated as we merge entries of the tree walk. Tells us whether we should
@@ -393,7 +393,7 @@ private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
 	 * @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());
@@ -518,7 +518,7 @@ protected boolean processEntry(CanonicalTreeParser base,
 					unmergedPaths.add(tw.getPathString());
 					mergeResults.put(
 							tw.getPathString(),
-							new MergeResult<RawText>(Collections
+							new MergeResult<>(Collections
 									.<RawText> emptyList()));
 				}
 				return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
index d880d9b..fd425ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
@@ -65,7 +65,7 @@
  */
 class GlobalBundleCache {
 	private static final Map<Locale, Map<Class, TranslationBundle>> cachedBundles
-		= new HashMap<Locale, Map<Class, TranslationBundle>>();
+		= new HashMap<>();
 
 	/**
 	 * Looks up for a translation bundle in the global cache. If found returns
@@ -87,7 +87,7 @@ static synchronized <T extends TranslationBundle> T lookupBundle(Locale locale,
 		try {
 			Map<Class, TranslationBundle> bundles = cachedBundles.get(locale);
 			if (bundles == null) {
-				bundles = new HashMap<Class, TranslationBundle>();
+				bundles = new HashMap<>();
 				cachedBundles.put(locale, bundles);
 			}
 			TranslationBundle bundle = bundles.get(type);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
index a768c25..5e7beed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
@@ -72,6 +72,7 @@ public class NLS {
 	public static final Locale ROOT_LOCALE = new Locale("", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 
 	private static final InheritableThreadLocal<NLS> local = new InheritableThreadLocal<NLS>() {
+		@Override
 		protected NLS initialValue() {
 			return new NLS(Locale.getDefault());
 		}
@@ -119,7 +120,7 @@ public static <T extends TranslationBundle> T getBundleFor(Class<T> type) {
 	}
 
 	final private Locale locale;
-	final private ConcurrentHashMap<Class, TranslationBundle> map = new ConcurrentHashMap<Class, TranslationBundle>();
+	final private ConcurrentHashMap<Class, TranslationBundle> map = new ConcurrentHashMap<>();
 
 	private NLS(Locale locale) {
 		this.locale = locale;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
index db49448..2e7327c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
@@ -67,6 +67,7 @@
  */
 public class DefaultNoteMerger implements NoteMerger {
 
+	@Override
 	public Note merge(Note base, Note ours, Note theirs, ObjectReader reader,
 			ObjectInserter inserter) throws IOException {
 		if (ours == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
index 79fbb09..7827a9a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
@@ -140,6 +140,7 @@ Iterator<Note> iterator(AnyObjectId objId, final ObjectReader reader)
 
 			private Iterator<Note> itr;
 
+			@Override
 			public boolean hasNext() {
 				if (itr != null && itr.hasNext())
 					return true;
@@ -164,6 +165,7 @@ public boolean hasNext() {
 				return false;
 			}
 
+			@Override
 			public Note next() {
 				if (hasNext())
 					return itr.next();
@@ -171,6 +173,7 @@ public Note next() {
 					throw new NoSuchElementException();
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
@@ -259,6 +262,7 @@ ObjectId writeTree(ObjectInserter inserter) throws IOException {
 		return inserter.insert(build(true, inserter));
 	}
 
+	@Override
 	ObjectId getTreeId() {
 		try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) {
 			return f.idFor(build(false, null));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
index 41f7501..1be5251 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
@@ -122,10 +122,12 @@ Iterator<Note> iterator(AnyObjectId objId, ObjectReader reader) {
 		return new Iterator<Note>() {
 			private int idx;
 
+			@Override
 			public boolean hasNext() {
 				return idx < cnt;
 			}
 
+			@Override
 			public Note next() {
 				if (hasNext())
 					return notes[idx++];
@@ -133,6 +135,7 @@ public Note next() {
 					throw new NoSuchElementException();
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
@@ -144,6 +147,7 @@ int estimateSize(AnyObjectId noteOn, ObjectReader or) throws IOException {
 		return cnt;
 	}
 
+	@Override
 	InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
 			ObjectReader or) throws IOException {
 		int p = search(noteOn);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
index 25b2ae8..44c5926 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
@@ -201,6 +201,7 @@ private NoteMap(ObjectReader reader) {
 	 * @return an iterator that iterates over notes of this NoteMap. Non note
 	 *         entries are ignored by this iterator.
 	 */
+	@Override
 	public Iterator<Note> iterator() {
 		try {
 			return root.iterator(new MutableObjectId(), reader);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
index b104a49..2c8f34e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
@@ -183,7 +183,7 @@ int parseGitHeaders(int ptr, final int end) {
 	protected void parseIndexLine(int ptr, final int eol) {
 		// "index $asha1,$bsha1..$csha1"
 		//
-		final List<AbbreviatedObjectId> ids = new ArrayList<AbbreviatedObjectId>();
+		final List<AbbreviatedObjectId> ids = new ArrayList<>();
 		while (ptr < eol) {
 			final int comma = nextLF(buf, ptr, ',');
 			if (eol <= comma)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
index eb2bfac..ed79787 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
@@ -266,6 +266,7 @@ void extractFileLines(final OutputStream[] out) throws IOException {
 		}
 	}
 
+	@Override
 	void extractFileLines(final StringBuilder sb, final String[] text,
 			final int[] offsets) {
 		final byte[] buf = file.buf;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
index 534c827..eb28a0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -305,7 +305,7 @@ void addHunk(final HunkHeader h) {
 		if (h.getFileHeader() != this)
 			throw new IllegalArgumentException(JGitText.get().hunkBelongsToAnotherFile);
 		if (hunks == null)
-			hunks = new ArrayList<HunkHeader>();
+			hunks = new ArrayList<>();
 		hunks.add(h);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
index 245b3ee..767cb24 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.patch;
 
+import java.util.Locale;
+
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -102,7 +104,7 @@ public String getLineText() {
 	@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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java
index 40ea77e..10ac449d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java
@@ -83,8 +83,8 @@ public class Patch {
 
 	/** Create an empty patch. */
 	public Patch() {
-		files = new ArrayList<FileHeader>();
-		errors = new ArrayList<FormatError>(0);
+		files = new ArrayList<>();
+		errors = new ArrayList<>(0);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
index 6102a81..a8eb86e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
@@ -74,12 +74,12 @@ public class PlotCommitList<L extends PlotLane> extends
 
 	private int positionsAllocated;
 
-	private final TreeSet<Integer> freePositions = new TreeSet<Integer>();
+	private final TreeSet<Integer> freePositions = new TreeSet<>();
 
-	private final HashSet<PlotLane> activeLanes = new HashSet<PlotLane>(32);
+	private final HashSet<PlotLane> activeLanes = new HashSet<>(32);
 
 	/** number of (child) commits on a lane */
-	private final HashMap<PlotLane, Integer> laneLength = new HashMap<PlotLane, Integer>(
+	private final HashMap<PlotLane, Integer> laneLength = new HashMap<>(
 			32);
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
index 55cf235..be1f07a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -107,7 +107,7 @@ public void addAdditionalRefs(Iterable<Ref> refs) throws IOException {
 			if (set == null)
 				set = Collections.singleton(ref);
 			else {
-				set = new HashSet<Ref>(set);
+				set = new HashSet<>(set);
 				set.add(ref);
 			}
 			reverseRefMap.put(ref.getObjectId(), set);
@@ -147,6 +147,7 @@ public RevCommit next() throws MissingObjectException,
 	}
 
 	class PlotRefComparator implements Comparator<Ref> {
+		@Override
 		public int compare(Ref o1, Ref o2) {
 			try {
 				RevObject obj1 = parseAny(o1.getObjectId());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
index 843c2af..4923d0f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
@@ -107,6 +107,7 @@ public final void addParents(final RevCommit c, final RevFlag queueControl) {
 	 *
 	 * @return the first commit of this queue.
 	 */
+	@Override
 	public abstract RevCommit next();
 
 	/** Remove all entries from this queue. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
index 30d140a..db5379e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
@@ -83,6 +83,7 @@ protected BlockRevQueue() {
 	 * @param q
 	 *            the other queue we will steal entries from.
 	 */
+	@Override
 	public void shareFreeList(final BlockRevQueue q) {
 		free = q.free;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
index 0802bfa..cd7c074 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
@@ -82,6 +82,7 @@ public DateRevQueue() {
 		}
 	}
 
+	@Override
 	public void add(final RevCommit c) {
 		sinceLastIndex++;
 		if (++inQueue > REBUILD_INDEX_COUNT
@@ -126,6 +127,7 @@ else if (t > when)
 		}
 	}
 
+	@Override
 	public RevCommit next() {
 		final Entry q = head;
 		if (q == null)
@@ -161,6 +163,7 @@ public RevCommit peek() {
 		return head != null ? head.commit : null;
 	}
 
+	@Override
 	public void clear() {
 		head = null;
 		free = null;
@@ -170,6 +173,7 @@ public void clear() {
 		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 +182,7 @@ boolean everbodyHasFlag(final int f) {
 		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 +196,7 @@ int outputType() {
 		return outputType | SORT_COMMIT_TIME_DESC;
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder s = new StringBuilder();
 		for (Entry q = head; q != null; q = q.next)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
index 59a360f..f932593 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
@@ -138,14 +138,17 @@ protected RevCommit createCommit(AnyObjectId id) {
 			return new Commit(id);
 		}
 
+		@Override
 		public int getDepth() {
 			return depth;
 		}
 
+		@Override
 		public RevFlag getUnshallowFlag() {
 			return UNSHALLOW;
 		}
 
+		@Override
 		public RevFlag getReinterestingFlag() {
 			return REINTERESTING;
 		}
@@ -239,14 +242,17 @@ protected RevCommit createCommit(AnyObjectId id) {
 			return new Commit(id);
 		}
 
+		@Override
 		public int getDepth() {
 			return depth;
 		}
 
+		@Override
 		public RevFlag getUnshallowFlag() {
 			return UNSHALLOW;
 		}
 
+		@Override
 		public RevFlag getReinterestingFlag() {
 			return REINTERESTING;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
index b8f63aa..1415604 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
@@ -64,6 +64,7 @@ public FIFORevQueue() {
 		super(s);
 	}
 
+	@Override
 	public void add(final RevCommit c) {
 		Block b = tail;
 		if (b == null) {
@@ -107,6 +108,7 @@ public void unpop(final RevCommit c) {
 		head = b;
 	}
 
+	@Override
 	public RevCommit next() {
 		final Block b = head;
 		if (b == null)
@@ -122,12 +124,14 @@ public RevCommit next() {
 		return c;
 	}
 
+	@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 +141,7 @@ boolean everbodyHasFlag(final int f) {
 		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 +159,7 @@ void removeFlag(final int f) {
 		}
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder s = new StringBuilder();
 		for (Block q = head; q != null; q = q.next) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
index 319b819..36965f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.revwalk;
 
+import java.util.Locale;
+
 import org.eclipse.jgit.lib.Constants;
 
 /** Case insensitive key for a {@link FooterLine}. */
@@ -68,7 +70,7 @@ public final class FooterKey {
 	 */
 	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. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
index 9abaf8d..f9da5b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
@@ -63,6 +63,7 @@ public LIFORevQueue() {
 		super(s);
 	}
 
+	@Override
 	public void add(final RevCommit c) {
 		Block b = head;
 		if (b == null || !b.canUnpop()) {
@@ -74,6 +75,7 @@ public void add(final RevCommit c) {
 		b.unpop(c);
 	}
 
+	@Override
 	public RevCommit next() {
 		final Block b = head;
 		if (b == null)
@@ -87,11 +89,13 @@ public RevCommit next() {
 		return c;
 	}
 
+	@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 +105,7 @@ boolean everbodyHasFlag(final int f) {
 		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 +115,7 @@ boolean anybodyHasFlag(final int f) {
 		return false;
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder s = new StringBuilder();
 		for (Block q = head; q != null; q = q.next) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
index 3609d46..5d7e72d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -87,7 +87,7 @@ class MergeBaseGenerator extends Generator {
 	private int recarryMask;
 
 	private int mergeBaseAncestor = -1;
-	private LinkedList<RevCommit> ret = new LinkedList<RevCommit>();
+	private LinkedList<RevCommit> ret = new LinkedList<>();
 
 	MergeBaseGenerator(final RevWalk w) {
 		walker = w;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index 9c3af5a..5868998 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -133,7 +133,7 @@ public ObjectWalk(final Repository repo) {
 	public ObjectWalk(ObjectReader or) {
 		super(or);
 		setRetainBody(false);
-		rootObjects = new ArrayList<RevObject>();
+		rootObjects = new ArrayList<>();
 		pendingObjects = new BlockObjQueue();
 		objectFilter = ObjectFilter.ALL;
 		pathBuf = new byte[256];
@@ -243,6 +243,7 @@ else if (o instanceof RevTree)
 			addObject(o);
 	}
 
+	@Override
 	public void sort(RevSort s) {
 		super.sort(s);
 		boundary = hasRevSort(RevSort.BOUNDARY);
@@ -681,7 +682,7 @@ protected void reset(final int retainFlags) {
 		for (RevObject obj : rootObjects)
 			obj.flags &= ~IN_PENDING;
 
-		rootObjects = new ArrayList<RevObject>();
+		rootObjects = new ArrayList<>();
 		pendingObjects = new BlockObjQueue();
 		currVisit = null;
 		freeVisit = null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index 1e91006..c641a13 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -565,7 +565,7 @@ public final List<FooterLine> getFooterLines() {
 			ptr--;
 
 		final int msgB = RawParseUtils.commitMessage(raw, 0);
-		final ArrayList<FooterLine> r = new ArrayList<FooterLine>(4);
+		final ArrayList<FooterLine> r = new ArrayList<>(4);
 		final Charset enc = guessEncoding();
 		for (;;) {
 			ptr = RawParseUtils.prevLF(raw, ptr);
@@ -628,7 +628,7 @@ public final List<String> getFooterLines(final FooterKey keyName) {
 		final List<FooterLine> src = getFooterLines();
 		if (src.isEmpty())
 			return Collections.emptyList();
-		final ArrayList<String> r = new ArrayList<String>(src.size());
+		final ArrayList<String> r = new ArrayList<>(src.size());
 		for (final FooterLine f : src) {
 			if (f.matches(keyName))
 				r.add(f.getValue());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
index 0998a3a..dae52f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
@@ -99,6 +99,7 @@ public RevWalk getRevWalk() {
 		return walker;
 	}
 
+	@Override
 	public String toString() {
 		return name;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java
index ca30fd9..bb699e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java
@@ -61,7 +61,7 @@ public class RevFlagSet extends AbstractSet<RevFlag> {
 
 	/** Create an empty set of flags. */
 	public RevFlagSet() {
-		active = new ArrayList<RevFlag>();
+		active = new ArrayList<>();
 	}
 
 	/**
@@ -72,7 +72,7 @@ public RevFlagSet() {
 	 */
 	public RevFlagSet(final RevFlagSet s) {
 		mask = s.mask;
-		active = new ArrayList<RevFlag>(s.active);
+		active = new ArrayList<>(s.active);
 	}
 
 	/**
@@ -132,14 +132,17 @@ public Iterator<RevFlag> iterator() {
 		return new Iterator<RevFlag>() {
 			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();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
index 5052a4d..9598678 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
@@ -78,6 +78,7 @@ public RevObjectList() {
 		// Initialized above.
 	}
 
+	@Override
 	public void add(final int index, final E element) {
 		if (index != size)
 			throw new UnsupportedOperationException(MessageFormat.format(
@@ -87,6 +88,7 @@ public void add(final int index, final E element) {
 		size++;
 	}
 
+	@Override
 	@SuppressWarnings("unchecked")
 	public E set(int index, E element) {
 		Block s = contents;
@@ -107,6 +109,7 @@ public E set(int index, E element) {
 		return (E) old;
 	}
 
+	@Override
 	@SuppressWarnings("unchecked")
 	public E get(int index) {
 		Block s = contents;
@@ -120,6 +123,7 @@ public E get(int index) {
 		return s != null ? (E) s.contents[index] : null;
 	}
 
+	@Override
 	public int size() {
 		return size;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index a7f7cd4..320f05f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -226,8 +226,8 @@ public RevWalk(ObjectReader or) {
 	private RevWalk(ObjectReader or, boolean closeReader) {
 		reader = or;
 		idBuffer = new MutableObjectId();
-		objects = new ObjectIdOwnerMap<RevObject>();
-		roots = new ArrayList<RevCommit>();
+		objects = new ObjectIdOwnerMap<>();
+		roots = new ArrayList<>();
 		queue = new DateRevQueue();
 		pending = new StartGenerator(this);
 		sorting = EnumSet.of(RevSort.NONE);
@@ -931,8 +931,8 @@ private RevObject parseNew(AnyObjectId id, ObjectLoader ldr)
 	 */
 	public <T extends ObjectId> AsyncRevObjectQueue parseAny(
 			Iterable<T> objectIds, boolean reportMissing) {
-		List<T> need = new ArrayList<T>();
-		List<RevObject> have = new ArrayList<RevObject>();
+		List<T> need = new ArrayList<>();
+		List<RevObject> have = new ArrayList<>();
 		for (T id : objectIds) {
 			RevObject r = objects.get(id);
 			if (r != null && (r.flags & PARSED) != 0)
@@ -944,14 +944,17 @@ public <T extends ObjectId> AsyncRevObjectQueue parseAny(
 		final Iterator<RevObject> 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 +963,7 @@ public void release() {
 
 		final AsyncObjectLoaderQueue<T> lItr = reader.open(need, reportMissing);
 		return new AsyncRevObjectQueue() {
+			@Override
 			public RevObject next() throws MissingObjectException,
 					IncorrectObjectTypeException, IOException {
 				if (objItr.hasNext())
@@ -983,10 +987,12 @@ else if (r instanceof RevCommit) {
 				return r;
 			}
 
+			@Override
 			public boolean cancel(boolean mayInterruptIfRunning) {
 				return lItr.cancel(mayInterruptIfRunning);
 			}
 
+			@Override
 			public void release() {
 				lItr.release();
 			}
@@ -1316,6 +1322,7 @@ public void dispose() {
 	 * @return an iterator over this walker's commits.
 	 * @see RevWalkException
 	 */
+	@Override
 	public Iterator<RevCommit> iterator() {
 		final RevCommit first;
 		try {
@@ -1331,10 +1338,12 @@ public Iterator<RevCommit> iterator() {
 		return new Iterator<RevCommit>() {
 			RevCommit next = first;
 
+			@Override
 			public boolean hasNext() {
 				return next != null;
 			}
 
+			@Override
 			public RevCommit next() {
 				try {
 					final RevCommit r = next;
@@ -1349,6 +1358,7 @@ public RevCommit next() {
 				}
 			}
 
+			@Override
 			public void remove() {
 				throw new UnsupportedOperationException();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
index 11fec31..e751d77 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
@@ -121,7 +121,7 @@ public static List<RevCommit> find(final RevWalk walk,
 		if (end != null)
 			walk.markUninteresting(end);
 
-		List<RevCommit> commits = new ArrayList<RevCommit>();
+		List<RevCommit> commits = new ArrayList<>();
 		for (RevCommit c : walk)
 			commits.add(c);
 		return commits;
@@ -155,7 +155,7 @@ public static List<Ref> findBranchesReachableFrom(RevCommit commit,
 		// Make sure commit is from the same RevWalk
 		commit = revWalk.parseCommit(commit.getId());
 		revWalk.reset();
-		List<Ref> result = new ArrayList<Ref>();
+		List<Ref> result = new ArrayList<>();
 
 		final int SKEW = 24*3600; // one day clock skew
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
index 14a98a1..6b90d29 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
@@ -286,6 +286,7 @@ public abstract boolean include(RevWalk walker, RevCommit cmit)
 	 *
 	 * @return another copy of this filter, suitable for another thread.
 	 */
+	@Override
 	public abstract RevFilter clone();
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index 2d02ad5..b268979 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -208,6 +208,7 @@ public void load() throws IOException, ConfigInvalidException {
 	 * @throws IOException
 	 *             the file could not be written.
 	 */
+	@Override
 	public void save() throws IOException {
 		final byte[] out;
 		final String text = toText();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index d594e97..c64aa2d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -75,6 +75,20 @@ public class PackConfig {
 	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)
@@ -204,6 +218,10 @@ public class PackConfig {
 
 	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;
@@ -281,6 +299,8 @@ public PackConfig(PackConfig cfg) {
 		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;
@@ -364,6 +384,61 @@ public void setReuseObjects(boolean reuseObjects) {
 	}
 
 	/**
+	 * 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
@@ -969,6 +1044,7 @@ public void fromConfig(final Config rc) {
 						getBitmapInactiveBranchAgeInDays()));
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder b = new StringBuilder();
 		b.append("maxDeltaDepth=").append(getMaxDeltaDepth()); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index 5b9e8d9..a10f3d7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -45,6 +45,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.Locale;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheIterator;
@@ -663,7 +664,8 @@ public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
 				ConfigConstants.CONFIG_KEY_IGNORE);
 		if (name == null)
 			return null;
-		return IgnoreSubmoduleMode.valueOf(name.trim().toUpperCase());
+		return IgnoreSubmoduleMode
+				.valueOf(name.trim().toUpperCase(Locale.ROOT));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
index 2d730a1..6f13694 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
@@ -57,12 +57,14 @@
  * @since 2.0
  */
 public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook {
+	@Override
 	public void advertiseRefs(UploadPack uploadPack)
 			throws ServiceMayNotContinueException {
 		uploadPack.setAdvertisedRefs(getAdvertisedRefs(
 				uploadPack.getRepository(), uploadPack.getRevWalk()));
 	}
 
+	@Override
 	public void advertiseRefs(BaseReceivePack receivePack)
 			throws ServiceMayNotContinueException {
 		Map<String, Ref> refs = getAdvertisedRefs(receivePack.getRepository(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
index c3af74a..96d7c24 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
@@ -56,10 +56,12 @@ public interface AdvertiseRefsHook {
 	 * {@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.
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
index 00942ab..22ea5cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
@@ -79,12 +79,14 @@ else if (i == 1)
 			return new AdvertiseRefsHookChain(newHooks, i);
 	}
 
+	@Override
 	public void advertiseRefs(BaseReceivePack rp)
 			throws ServiceMayNotContinueException {
 		for (int i = 0; i < count; i++)
 			hooks[i].advertiseRefs(rp);
 	}
 
+	@Override
 	public void advertiseRefs(UploadPack rp)
 			throws ServiceMayNotContinueException {
 		for (int i = 0; i < count; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index 1aebadd..64cb4dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -120,7 +120,7 @@ public class AmazonS3 {
 	private static final String X_AMZ_META = "x-amz-meta-"; //$NON-NLS-1$
 
 	static {
-		SIGNED_HEADERS = new HashSet<String>();
+		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$
@@ -606,7 +606,7 @@ HttpURLConnection open(final String method, final String bucket,
 
 	void authorize(final HttpURLConnection c) throws IOException {
 		final Map<String, List<String>> reqHdr = c.getRequestProperties();
-		final SortedMap<String, String> sigHdr = new TreeMap<String, String>();
+		final SortedMap<String, String> sigHdr = new TreeMap<>();
 		for (final Map.Entry<String, List<String>> entry : reqHdr.entrySet()) {
 			final String hdr = entry.getKey();
 			if (isSignedHeader(hdr))
@@ -664,7 +664,7 @@ static Properties properties(final File authFile)
 	}
 
 	private final class ListParser extends DefaultHandler {
-		final List<String> entries = new ArrayList<String>();
+		final List<String> entries = new ArrayList<>();
 
 		private final String bucket;
 
@@ -680,7 +680,7 @@ private final class ListParser extends DefaultHandler {
 		}
 
 		void list() throws IOException {
-			final Map<String, String> args = new TreeMap<String, String>();
+			final Map<String, String> args = new TreeMap<>();
 			if (prefix.length() > 0)
 				args.put("prefix", prefix); //$NON-NLS-1$
 			if (!entries.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
index 59ff1bd..69028fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
@@ -71,18 +71,22 @@ public abstract class BaseConnection implements Connection {
 
 	private Writer messageWriter;
 
+	@Override
 	public Map<String, Ref> getRefsMap() {
 		return advertisedRefs;
 	}
 
+	@Override
 	public final Collection<Ref> getRefs() {
 		return advertisedRefs.values();
 	}
 
+	@Override
 	public final Ref getRef(final String name) {
 		return advertisedRefs.get(name);
 	}
 
+	@Override
 	public String getMessages() {
 		return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$
 	}
@@ -94,6 +98,7 @@ public String getMessages() {
 	 *         server does not advertise this version.
 	 * @since 4.0
 	 */
+	@Override
 	public String getPeerUserAgent() {
 		return peerUserAgent;
 	}
@@ -109,6 +114,7 @@ protected void setPeerUserAgent(String agent) {
 		peerUserAgent = agent;
 	}
 
+	@Override
 	public abstract void close();
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
index cc27707..41b8c2d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
@@ -64,12 +64,14 @@
  */
 abstract class BaseFetchConnection extends BaseConnection implements
 		FetchConnection {
+	@Override
 	public final void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have)
 			throws TransportException {
 		fetch(monitor, want, have, null);
 	}
 
+	@Override
 	public final void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have,
 			OutputStream out) throws TransportException {
@@ -81,6 +83,7 @@ public final void fetch(final ProgressMonitor monitor,
 	 * Default implementation of {@link FetchConnection#didFetchIncludeTags()} -
 	 * returning false.
 	 */
+	@Override
 	public boolean didFetchIncludeTags() {
 		return false;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index aa36aeb..c695449 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -117,10 +117,10 @@ abstract class BasePackConnection extends BaseConnection {
 	protected boolean statelessRPC;
 
 	/** Capability tokens advertised by the remote side. */
-	private final Set<String> remoteCapablities = new HashSet<String>();
+	private final Set<String> remoteCapablities = new HashSet<>();
 
 	/** Extra objects the remote has, but which aren't offered as refs. */
-	protected final Set<ObjectId> additionalHaves = new HashSet<ObjectId>();
+	protected final Set<ObjectId> additionalHaves = new HashSet<>();
 
 	BasePackConnection(final PackTransport packTransport) {
 		transport = (Transport) packTransport;
@@ -191,7 +191,7 @@ protected void readAdvertisedRefs() throws TransportException {
 	}
 
 	private void readAdvertisedRefsImpl() throws IOException {
-		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
+		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<>();
 		for (;;) {
 			String line;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index 0dd907f..e8d1881 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -260,7 +260,7 @@ public BasePackFetchConnection(final PackTransport packTransport) {
 
 		if (local != null) {
 			walk = new RevWalk(local);
-			reachableCommits = new RevCommitList<RevCommit>();
+			reachableCommits = new RevCommitList<>();
 			REACHABLE = walk.newFlag("REACHABLE"); //$NON-NLS-1$
 			COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$
 			STATE = walk.newFlag("STATE"); //$NON-NLS-1$
@@ -280,6 +280,7 @@ public BasePackFetchConnection(final PackTransport packTransport) {
 
 	private static class FetchConfig {
 		static final SectionParser<FetchConfig> KEY = new SectionParser<FetchConfig>() {
+			@Override
 			public FetchConfig parse(final Config cfg) {
 				return new FetchConfig(cfg);
 			}
@@ -292,6 +293,7 @@ public FetchConfig parse(final Config cfg) {
 		}
 	}
 
+	@Override
 	public final void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have)
 			throws TransportException {
@@ -301,6 +303,7 @@ public final void fetch(final ProgressMonitor monitor,
 	/**
 	 * @since 3.0
 	 */
+	@Override
 	public final void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have,
 			OutputStream outputStream) throws TransportException {
@@ -308,18 +311,22 @@ public final void fetch(final ProgressMonitor monitor,
 		doFetch(monitor, want, have, outputStream);
 	}
 
+	@Override
 	public boolean didFetchIncludeTags() {
 		return false;
 	}
 
+	@Override
 	public boolean didFetchTestConnectivity() {
 		return false;
 	}
 
+	@Override
 	public void setPackLockMessage(final String message) {
 		lockMessage = message;
 	}
 
+	@Override
 	public Collection<PackLock> getPackLocks() {
 		if (packLock != null)
 			return Collections.singleton(packLock);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 86cc484..679ea0c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -152,6 +152,7 @@ public BasePackPushConnection(final PackTransport packTransport) {
 		pushOptions = transport.getPushOptions();
 	}
 
+	@Override
 	public void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates)
 			throws TransportException {
@@ -161,6 +162,7 @@ public void push(final ProgressMonitor monitor,
 	/**
 	 * @since 3.0
 	 */
+	@Override
 	public void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates, OutputStream outputStream)
 			throws TransportException {
@@ -326,8 +328,8 @@ private String enableCapabilities(final ProgressMonitor monitor,
 
 	private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
 			final ProgressMonitor monitor) throws IOException {
-		Set<ObjectId> remoteObjects = new HashSet<ObjectId>();
-		Set<ObjectId> newObjects = new HashSet<ObjectId>();
+		Set<ObjectId> remoteObjects = new HashSet<>();
+		Set<ObjectId> newObjects = new HashSet<>();
 
 		try (final PackWriter writer = new PackWriter(transport.getPackConfig(),
 				local.newObjectReader())) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index 4d0803a..14b683f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -43,6 +43,7 @@
 
 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;
@@ -76,15 +77,20 @@
 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;
@@ -97,6 +103,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;
@@ -123,7 +130,7 @@ public static class FirstLine {
 		 *            line from the client.
 		 */
 		public FirstLine(String line) {
-			final HashSet<String> caps = new HashSet<String>();
+			final HashSet<String> caps = new HashSet<>();
 			final int nul = line.indexOf('\0');
 			if (nul >= 0) {
 				for (String c : line.substring(nul + 1).split(" ")) //$NON-NLS-1$
@@ -244,6 +251,8 @@ public Set<String> getCapabilities() {
 	String userAgent;
 	private Set<ObjectId> clientShallowCommits;
 	private List<ReceiveCommand> commands;
+	private long maxCommandBytes;
+	private long maxDiscardBytes;
 
 	private StringBuilder advertiseError;
 
@@ -318,16 +327,19 @@ protected BaseReceivePack(final Repository into) {
 		allowNonFastForwards = rc.allowNonFastForwards;
 		allowOfsDelta = rc.allowOfsDelta;
 		allowPushOptions = rc.allowPushOptions;
+		maxCommandBytes = rc.maxCommandBytes;
+		maxDiscardBytes = rc.maxDiscardBytes;
 		advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
 		refFilter = RefFilter.DEFAULT;
-		advertisedHaves = new HashSet<ObjectId>();
-		clientShallowCommits = new HashSet<ObjectId>();
+		advertisedHaves = new HashSet<>();
+		clientShallowCommits = new HashSet<>();
 		signedPushConfig = rc.signedPush;
 	}
 
 	/** Configuration for receive operations. */
 	protected static class ReceiveConfig {
 		static final SectionParser<ReceiveConfig> KEY = new SectionParser<ReceiveConfig>() {
+			@Override
 			public ReceiveConfig parse(final Config cfg) {
 				return new ReceiveConfig(cfg);
 			}
@@ -338,7 +350,8 @@ public ReceiveConfig parse(final Config cfg) {
 		final boolean allowNonFastForwards;
 		final boolean allowOfsDelta;
 		final boolean allowPushOptions;
-
+		final long maxCommandBytes;
+		final long maxDiscardBytes;
 		final SignedPushConfig signedPush;
 
 		ReceiveConfig(final Config config) {
@@ -350,6 +363,12 @@ public ReceiveConfig parse(final Config cfg) {
 					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);
 		}
 	}
@@ -729,6 +748,38 @@ public void setTimeout(final int seconds) {
 	}
 
 	/**
+	 * 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.
+	 * <p>
+	 * 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.
+	 * <p>
+	 * 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.
 	 * <p>
 	 * If an object is larger than the given size the pack-parsing will throw an
@@ -741,7 +792,6 @@ public void setMaxObjectSizeLimit(final long limit) {
 		maxObjectSizeLimit = limit;
 	}
 
-
 	/**
 	 * Set the maximum allowed pack size.
 	 * <p>
@@ -1014,20 +1064,12 @@ protected void init(final InputStream input, final OutputStream output,
 			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<String>();
-		commands = new ArrayList<ReceiveCommand>();
+		enabledCapabilities = new HashSet<>();
+		commands = new ArrayList<>();
 	}
 
 	/** @return advertised refs, or the default if not explicitly advertised. */
@@ -1045,8 +1087,10 @@ protected Map<String, Ref> getAdvertisedOrDefaultRefs() {
 	 */
 	protected void receivePackAndCheckConnectivity() throws IOException {
 		receivePack();
-		if (needCheckConnectivity())
+		if (needCheckConnectivity()) {
+			checkSubmodules();
 			checkConnectivity();
+		}
 		parser = null;
 	}
 
@@ -1134,13 +1178,16 @@ public ReceivedPackStatistics getReceivedPackStatistics() {
 	 * @throws IOException
 	 */
 	protected void recvCommands() throws IOException {
+		PacketLineIn pck = maxCommandBytes > 0
+				? new PacketLineIn(rawIn, maxCommandBytes)
+				: pckIn;
 		PushCertificateParser certParser = getPushCertificateParser();
 		boolean firstPkt = true;
 		try {
 			for (;;) {
 				String line;
 				try {
-					line = pckIn.readString();
+					line = pck.readString();
 				} catch (EOFException eof) {
 					if (commands.isEmpty())
 						return;
@@ -1163,13 +1210,13 @@ protected void recvCommands() throws IOException {
 					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;
 				}
 
@@ -1186,18 +1233,31 @@ protected void recvCommands() throws IOException {
 			}
 			pushCert = certParser.build();
 			if (hasCommands()) {
-				readPostCommands(pckIn);
+				readPostCommands(pck);
 			}
 		} catch (PackProtocolException e) {
-			if (sideBand) {
-				try {
-					pckIn.discardUntilEnd();
-				} catch (IOException e2) {
-					// Ignore read failures attempting to discard.
-				}
-			}
+			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.
+			}
 		}
 	}
 
@@ -1309,7 +1369,7 @@ private void receivePack() throws IOException {
 			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);
@@ -1329,12 +1389,40 @@ private void receivePack() throws IOException {
 			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<ObjectId> baseObjects = null;
 		ObjectIdSubclassMap<ObjectId> providedObjects = null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index 8038fa4..f37ba01 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -92,7 +92,7 @@ class BundleFetchConnection extends BaseFetchConnection {
 
 	InputStream bin;
 
-	final Map<ObjectId, String> prereqs = new HashMap<ObjectId, String>();
+	final Map<ObjectId, String> prereqs = new HashMap<>();
 
 	private String lockMessage;
 
@@ -130,7 +130,7 @@ private int readSignature() throws IOException {
 
 	private void readBundleV2() throws IOException {
 		final byte[] hdrbuf = new byte[1024];
-		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
+		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<>();
 		for (;;) {
 			String line = readLine(hdrbuf);
 			if (line.length() == 0)
@@ -180,6 +180,7 @@ private String readLine(final byte[] hdrbuf) throws IOException {
 		return line.toString();
 	}
 
+	@Override
 	public boolean didFetchTestConnectivity() {
 		return false;
 	}
@@ -207,10 +208,12 @@ protected void doFetch(final ProgressMonitor monitor,
 		}
 	}
 
+	@Override
 	public void setPackLockMessage(final String message) {
 		lockMessage = message;
 	}
 
+	@Override
 	public Collection<PackLock> getPackLocks() {
 		if (packLock != null)
 			return Collections.singleton(packLock);
@@ -225,8 +228,8 @@ private void verifyPrerequisites() throws TransportException {
 			final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$
 			final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$
 
-			final Map<ObjectId, String> missing = new HashMap<ObjectId, String>();
-			final List<RevObject> commits = new ArrayList<RevObject>();
+			final Map<ObjectId, String> missing = new HashMap<>();
+			final List<RevObject> commits = new ArrayList<>();
 			for (final Map.Entry<ObjectId, String> e : prereqs.entrySet()) {
 				ObjectId p = e.getKey();
 				try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index ca624c0..37d70e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -102,9 +102,9 @@ public class BundleWriter {
 	 */
 	public BundleWriter(final Repository repo) {
 		db = repo;
-		include = new TreeMap<String, ObjectId>();
-		assume = new HashSet<RevCommit>();
-		tagTargets = new HashSet<ObjectId>();
+		include = new TreeMap<>();
+		assume = new HashSet<>();
+		tagTargets = new HashSet<>();
 	}
 
 	/**
@@ -202,8 +202,8 @@ public void writeBundle(ProgressMonitor monitor, OutputStream os)
 		try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) {
 			packWriter.setObjectCountCallback(callback);
 
-			final HashSet<ObjectId> inc = new HashSet<ObjectId>();
-			final HashSet<ObjectId> exc = new HashSet<ObjectId>();
+			final HashSet<ObjectId> inc = new HashSet<>();
+			final HashSet<ObjectId> exc = new HashSet<>();
 			inc.addAll(include.values());
 			for (final RevCommit r : assume)
 				exc.add(r.getId());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
index 739ddcc..8cb3d60 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
@@ -68,7 +68,7 @@ public class ChainingCredentialsProvider extends CredentialsProvider {
 	 *            here
 	 */
 	public ChainingCredentialsProvider(CredentialsProvider... providers) {
-		this.credentialProviders = new ArrayList<CredentialsProvider>(
+		this.credentialProviders = new ArrayList<>(
 				Arrays.asList(providers));
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index da288ec..9a272a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -112,6 +112,7 @@ public interface Connection extends AutoCloseable {
 	 * Implementers shouldn't throw checked exceptions. This override narrows
 	 * the signature to prevent them from doing so.
 	 */
+	@Override
 	public void close();
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
index 6757aaf..8358635 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
@@ -85,14 +85,17 @@ private static URIish createURI(Session session) {
 		return uri;
 	}
 
+	@Override
 	public String getPassword() {
 		return password;
 	}
 
+	@Override
 	public String getPassphrase() {
 		return passphrase;
 	}
 
+	@Override
 	public boolean promptPassphrase(String msg) {
 		CredentialItem.StringType v = newPrompt(msg);
 		if (provider.get(uri, v)) {
@@ -104,6 +107,7 @@ public boolean promptPassphrase(String msg) {
 		}
 	}
 
+	@Override
 	public boolean promptPassword(String msg) {
 		CredentialItem.Password p = new CredentialItem.Password(msg);
 		if (provider.get(uri, p)) {
@@ -119,22 +123,25 @@ private CredentialItem.StringType newPrompt(String msg) {
 		return new CredentialItem.StringType(msg, true);
 	}
 
+	@Override
 	public boolean promptYesNo(String msg) {
 		CredentialItem.YesNoType v = new CredentialItem.YesNoType(msg);
 		return provider.get(uri, v) && v.getValue();
 	}
 
+	@Override
 	public void showMessage(String msg) {
 		provider.get(uri, new CredentialItem.InformationalMessage(msg));
 	}
 
+	@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<CredentialItem> items = new ArrayList<CredentialItem>();
+		List<CredentialItem> items = new ArrayList<>();
 		if (instruction != null && instruction.length() > 0)
 			items.add(new CredentialItem.InformationalMessage(instruction));
 		items.addAll(Arrays.asList(v));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index 1beec90..40b2c47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -111,6 +111,7 @@ public Daemon(final InetSocketAddress addr) {
 		repositoryResolver = (RepositoryResolver<DaemonClient>) RepositoryResolver.NONE;
 
 		uploadPackFactory = new UploadPackFactory<DaemonClient>() {
+			@Override
 			public UploadPack create(DaemonClient req, Repository db)
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
@@ -122,6 +123,7 @@ public UploadPack create(DaemonClient req, Repository db)
 		};
 
 		receivePackFactory = new ReceivePackFactory<DaemonClient>() {
+			@Override
 			public ReceivePack create(DaemonClient req, Repository db)
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
@@ -298,6 +300,7 @@ public synchronized void start() throws IOException {
 
 		run = true;
 		acceptThread = new Thread(processors, "Git-Daemon-Accept") { //$NON-NLS-1$
+			@Override
 			public void run() {
 				while (isRunning()) {
 					try {
@@ -344,6 +347,7 @@ void startClient(final Socket s) {
 			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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
index ec6f242..80b2cae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
@@ -65,6 +65,7 @@ public abstract class DaemonService {
 	DaemonService(final String cmdName, final String cfgName) {
 		command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$
 		configKey = new SectionParser<ServiceConfig>() {
+			@Override
 			public ServiceConfig parse(final Config cfg) {
 				return new ServiceConfig(DaemonService.this, cfg, cfgName);
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java
index 7012807..23fd7e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java
@@ -59,6 +59,7 @@
  * connection will immediately fail.
  */
 class DefaultSshSessionFactory extends JschConfigSessionFactory {
+	@Override
 	protected void configure(final OpenSshConfig.Host hc, final Session session) {
 		// No additional configuration required.
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 8cb36c7..280e6d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -88,18 +88,18 @@ class FetchProcess {
 	private final Collection<RefSpec> toFetch;
 
 	/** Set of refs we will actually wind up asking to obtain. */
-	private final HashMap<ObjectId, Ref> askFor = new HashMap<ObjectId, Ref>();
+	private final HashMap<ObjectId, Ref> askFor = new HashMap<>();
 
 	/** Objects we know we have locally. */
-	private final HashSet<ObjectId> have = new HashSet<ObjectId>();
+	private final HashSet<ObjectId> have = new HashSet<>();
 
 	/** Updates to local tracking branches (if any). */
-	private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<TrackingRefUpdate>();
+	private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<>();
 
 	/** Records to be recorded into FETCH_HEAD. */
-	private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<FetchHeadRecord>();
+	private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<>();
 
-	private final ArrayList<PackLock> packLocks = new ArrayList<PackLock>();
+	private final ArrayList<PackLock> packLocks = new ArrayList<>();
 
 	private FetchConnection conn;
 
@@ -137,7 +137,7 @@ private void executeImp(final ProgressMonitor monitor,
 		try {
 			result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
 			result.peerUserAgent = conn.getPeerUserAgent();
-			final Set<Ref> matched = new HashSet<Ref>();
+			final Set<Ref> matched = new HashSet<>();
 			for (final RefSpec spec : toFetch) {
 				if (spec.getSource() == null)
 					throw new TransportException(MessageFormat.format(
@@ -275,11 +275,11 @@ private void reopenConnection() throws NotSupportedException,
 		// We rebuild our askFor list using only the refs that the
 		// new connection has offered to us.
 		//
-		final HashMap<ObjectId, Ref> avail = new HashMap<ObjectId, Ref>();
+		final HashMap<ObjectId, Ref> avail = new HashMap<>();
 		for (final Ref r : conn.getRefs())
 			avail.put(r.getObjectId(), r);
 
-		final Collection<Ref> wants = new ArrayList<Ref>(askFor.values());
+		final Collection<Ref> wants = new ArrayList<>(askFor.values());
 		askFor.clear();
 		for (final Ref want : wants) {
 			final Ref newRef = avail.get(want.getObjectId());
@@ -369,7 +369,7 @@ private void expandSingle(final RefSpec spec, final Set<Ref> matched)
 	}
 
 	private Collection<Ref> expandAutoFollowTags() throws TransportException {
-		final Collection<Ref> additionalTags = new ArrayList<Ref>();
+		final Collection<Ref> additionalTags = new ArrayList<>();
 		final Map<String, Ref> haveRefs = localRefs();
 		for (final Ref r : conn.getRefs()) {
 			if (!isTag(r))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java
index 3d95edd..2667ec3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java
@@ -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<FetchHeadRecord> forMerge;
 
+	private final Map<String, FetchResult> submodules;
+
 	FetchResult() {
-		forMerge = new ArrayList<FetchHeadRecord>();
+		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<String, FetchResult> submoduleResults() {
+		return Collections.unmodifiableMap(submodules);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 319ae1e..d43be89 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -82,6 +82,7 @@ public HMACSHA1NonceGenerator(String seed) throws IllegalStateException {
 		}
 	}
 
+	@Override
 	public synchronized String createNonce(Repository repo, long timestamp)
 			throws IllegalStateException {
 		String path;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index 81e6904..c97daa9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -56,6 +56,7 @@
 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;
@@ -168,7 +169,8 @@ static HttpAuthMethod scanResponse(final HttpConnection conn,
 									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))) {
@@ -346,7 +348,7 @@ void authorize(final String username, final String password) {
 		@SuppressWarnings("boxing")
 		@Override
 		void configureRequest(final HttpConnection conn) throws IOException {
-			final Map<String, String> r = new LinkedHashMap<String, String>();
+			final Map<String, String> r = new LinkedHashMap<>();
 
 			final String realm = params.get("realm"); //$NON-NLS-1$
 			final String nonce = params.get("nonce"); //$NON-NLS-1$
@@ -465,7 +467,7 @@ private static String LHEX(byte[] bin) {
 		}
 
 		private static Map<String, String> parse(String auth) {
-			Map<String, String> p = new HashMap<String, String>();
+			Map<String, String> p = new HashMap<>();
 			int next = 0;
 			while (next < auth.length()) {
 				if (next < auth.length() && auth.charAt(next) == ',') {
@@ -540,7 +542,7 @@ void configureRequest(HttpConnection conn) throws IOException {
 			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);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
index 7fc4048..bea8d60 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
@@ -87,6 +87,7 @@ public InternalFetchConnection(PackTransport transport,
 		}
 
 		worker = new Thread("JGit-Upload-Pack") { //$NON-NLS-1$
+			@Override
 			public void run() {
 				try {
 					final UploadPack rp = uploadPackFactory.create(req, remote);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
index ab76e0f..8a1884e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
@@ -79,6 +79,7 @@ public InternalPushConnection(PackTransport transport,
 		}
 
 		worker = new Thread("JGit-Receive-Pack") { //$NON-NLS-1$
+			@Override
 			public void run() {
 				try {
 					final ReceivePack rp = receivePackFactory.create(req, remote);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index d1cbd8d..ce14183 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -80,7 +80,7 @@
  * to supply appropriate {@link UserInfo} to the session.
  */
 public abstract class JschConfigSessionFactory extends SshSessionFactory {
-	private final Map<String, JSch> byIdentityFile = new HashMap<String, JSch>();
+	private final Map<String, JSch> byIdentityFile = new HashMap<>();
 
 	private JSch defaultJSch;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index fa27bfc..f445bcb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -87,10 +87,12 @@ public JschSession(final Session session, URIish uri) {
 		this.uri = uri;
 	}
 
+	@Override
 	public Process exec(String command, int timeout) throws IOException {
 		return new JschProcess(command, timeout);
 	}
 
+	@Override
 	public void disconnect() {
 		if (sock.isConnected())
 			sock.disconnect();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
index 88b4b07..4d60202 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
@@ -106,7 +106,7 @@ V put(final long key, final V value) {
 
 		if (++size == growAt)
 			grow();
-		insert(new Node<V>(key, value));
+		insert(new Node<>(key, value));
 		return null;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index bacab7e..bab5bf0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -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 @@ boolean complete() {
 
 	private long lastModified;
 
-	private Map<String, NetRCEntry> hosts = new HashMap<String, NetRCEntry>();
+	private Map<String, NetRCEntry> hosts = new HashMap<>();
 
 	private static final TreeMap<String, State> STATE = new TreeMap<String, NetRC.State>() {
 		private static final long serialVersionUID = -4285910831814853334L;
@@ -230,7 +231,7 @@ private void parse() {
 
 				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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
index 38f3a2a..8b7b60d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -175,9 +175,9 @@ private synchronized Map<String, Host> refresh() {
 	}
 
 	private Map<String, Host> parse(final InputStream in) throws IOException {
-		final Map<String, Host> m = new LinkedHashMap<String, Host>();
+		final Map<String, Host> m = new LinkedHashMap<>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(in));
-		final List<Host> current = new ArrayList<Host>(4);
+		final List<Host> current = new ArrayList<>(4);
 		String line;
 
 		while ((line = br.readLine()) != null) {
@@ -311,6 +311,7 @@ private File toFile(final String path) {
 
 	static String userName() {
 		return AccessController.doPrivileged(new PrivilegedAction<String>() {
+			@Override
 			public String run() {
 				return System.getProperty("user.name"); //$NON-NLS-1$
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
index ad51f3e..4231798 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
@@ -64,7 +64,7 @@ public abstract class OperationResult {
 
 	URIish uri;
 
-	final SortedMap<String, TrackingRefUpdate> updates = new TreeMap<String, TrackingRefUpdate>();
+	final SortedMap<String, TrackingRefUpdate> updates = new TreeMap<>();
 
 	StringBuilder messageBuffer;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index 4bbe3f8..c82b389 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -83,6 +83,7 @@
 import org.eclipse.jgit.util.BlockList;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.sha1.SHA1;
 
 /**
  * Parses a pack stream and imports it for an {@link ObjectInserter}.
@@ -116,8 +117,7 @@ public static enum Source {
 
 	private byte[] hdrBuf;
 
-	private final MessageDigest objectDigest;
-
+	private final SHA1 objectHasher = SHA1.newInstance();
 	private final MutableObjectId tempObjectId;
 
 	private InputStream in;
@@ -206,7 +206,6 @@ protected PackParser(final ObjectDatabase odb, final InputStream src) {
 		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;
@@ -275,7 +274,7 @@ protected void setCheckObjectCollisions(boolean check) {
 	 */
 	public void setNeedNewObjectIds(boolean b) {
 		if (b)
-			newObjectIds = new ObjectIdSubclassMap<ObjectId>();
+			newObjectIds = new ObjectIdSubclassMap<>();
 		else
 			newObjectIds = null;
 	}
@@ -333,14 +332,14 @@ public void setExpectDataAfterPackFooter(boolean e) {
 	public ObjectIdSubclassMap<ObjectId> getNewObjectIds() {
 		if (newObjectIds != null)
 			return newObjectIds;
-		return new ObjectIdSubclassMap<ObjectId>();
+		return new ObjectIdSubclassMap<>();
 	}
 
 	/** @return set of objects the incoming pack assumed for delta purposes */
 	public ObjectIdSubclassMap<ObjectId> getBaseObjectIds() {
 		if (baseObjectIds != null)
 			return baseObjectIds;
-		return new ObjectIdSubclassMap<ObjectId>();
+		return new ObjectIdSubclassMap<>();
 	}
 
 	/**
@@ -527,9 +526,9 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
 			readPackHeader();
 
 			entries = new PackedObjectInfo[(int) objectCount];
-			baseById = new ObjectIdOwnerMap<DeltaChain>();
-			baseByPos = new LongMap<UnresolvedDelta>();
-			deferredCheckBlobs = new BlockList<PackedObjectInfo>();
+			baseById = new ObjectIdOwnerMap<>();
+			baseByPos = new LongMap<>();
+			deferredCheckBlobs = new BlockList<>();
 
 			receiving.beginTask(JGitText.get().receivingObjects,
 					(int) objectCount);
@@ -667,12 +666,13 @@ private void resolveDeltas(DeltaVisit visit, final int type,
 						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);
 
@@ -826,9 +826,9 @@ private void resolveDeltasWithExternalBases(final ProgressMonitor progress)
 		growEntries(baseById.size());
 
 		if (needBaseObjectIds)
-			baseObjectIds = new ObjectIdSubclassMap<ObjectId>();
+			baseObjectIds = new ObjectIdSubclassMap<>();
 
-		final List<DeltaChain> missing = new ArrayList<DeltaChain>(64);
+		final List<DeltaChain> missing = new ArrayList<>(64);
 		for (final DeltaChain baseId : baseById) {
 			if (baseId.head == null)
 				continue;
@@ -1024,6 +1024,7 @@ private void indexOneObject() throws IOException {
 
 	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));
@@ -1043,7 +1044,7 @@ private void whole(final long pos, final int type, final long sz)
 				cnt += r;
 			}
 			inf.close();
-			tempObjectId.fromRaw(objectDigest.digest(), 0);
+			objectDigest.digest(tempObjectId);
 			checkContentLater = isCheckObjectCollisions()
 					&& readCurs.has(tempObjectId);
 			data = null;
@@ -1051,7 +1052,7 @@ private void whole(final long pos, final int type, final long sz)
 		} else {
 			data = inflateAndReturn(Source.INPUT, sz);
 			objectDigest.update(data);
-			tempObjectId.fromRaw(objectDigest.digest(), 0);
+			objectDigest.digest(tempObjectId);
 			verifySafeObject(tempObjectId, type, data);
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index e142bab..e709256 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -87,19 +87,32 @@ static enum AckNackResult {
 		ACK_READY;
 	}
 
+	private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
 	private final InputStream in;
-
-	private final byte[] lineBuffer;
+	private long limit;
 
 	/**
 	 * Create a new packet line reader.
 	 *
-	 * @param i
+	 * @param in
 	 *            the input stream to consume.
 	 */
-	public PacketLineIn(final InputStream i) {
-		in = i;
-		lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
+	public PacketLineIn(InputStream in) {
+		this(in, 0);
+	}
+
+	/**
+	 * Create a new packet line reader.
+	 *
+	 * @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(InputStream in, long limit) {
+		this.in = in;
+		this.limit = limit;
 	}
 
 	AckNackResult readACK(final MutableObjectId returnedId) throws IOException {
@@ -210,15 +223,48 @@ void discardUntilEnd() throws IOException {
 
 	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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
index 1e66275..8e39501 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
@@ -62,6 +62,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<ReceiveCommand> commands) {
 			// Do nothing.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
index da86525..3bdcd68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
@@ -77,6 +77,7 @@ else if (i == 1)
 			return new PostReceiveHookChain(newHooks, i);
 	}
 
+	@Override
 	public void onPostReceive(ReceivePack rp,
 			Collection<ReceiveCommand> commands) {
 		for (int i = 0; i < count; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
index 53eeab1..5b37bcd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
@@ -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.
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
index 4e2eaea..26323ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
@@ -78,6 +78,7 @@ else if (i == 1)
 			return new PostUploadHookChain(newHooks, i);
 	}
 
+	@Override
 	public void onPostUpload(PackStatistics stats) {
 		for (int i = 0; i < count; i++)
 			hooks[i].onPostUpload(stats);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
index 9a743a5..27b2df0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
@@ -78,6 +78,7 @@
 public interface PreReceiveHook {
 	/** A simple no-op hook. */
 	public static final PreReceiveHook NULL = new PreReceiveHook() {
+		@Override
 		public void onPreReceive(final ReceivePack rp,
 				final Collection<ReceiveCommand> commands) {
 			// Do nothing.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
index bd4441f..7b1c8fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
@@ -76,6 +76,7 @@ else if (i == 1)
 			return new PreReceiveHookChain(newHooks, i);
 	}
 
+	@Override
 	public void onPreReceive(ReceivePack rp,
 			Collection<ReceiveCommand> commands) {
 		for (int i = 0; i < count; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
index 0360f12..7d9638c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
@@ -58,12 +58,14 @@
 public interface PreUploadHook {
 	/** A simple no-op hook. */
 	public static final PreUploadHook NULL = new PreUploadHook() {
+		@Override
 		public void onBeginNegotiateRound(UploadPack up,
 				Collection<? extends ObjectId> wants, int cntOffered)
 				throws ServiceMayNotContinueException {
 			// Do nothing.
 		}
 
+		@Override
 		public void onEndNegotiateRound(UploadPack up,
 				Collection<? extends ObjectId> wants, int cntCommon,
 				int cntNotFound, boolean ready)
@@ -71,6 +73,7 @@ public void onEndNegotiateRound(UploadPack up,
 			// Do nothing.
 		}
 
+		@Override
 		public void onSendPack(UploadPack up,
 				Collection<? extends ObjectId> wants,
 				Collection<? extends ObjectId> haves)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
index 7f515e0..c9f88dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
@@ -79,6 +79,7 @@ else if (i == 1)
 			return new PreUploadHookChain(newHooks, i);
 	}
 
+	@Override
 	public void onBeginNegotiateRound(UploadPack up,
 			Collection<? extends ObjectId> wants, int cntOffered)
 			throws ServiceMayNotContinueException {
@@ -86,6 +87,7 @@ public void onBeginNegotiateRound(UploadPack up,
 			hooks[i].onBeginNegotiateRound(up, wants, cntOffered);
 	}
 
+	@Override
 	public void onEndNegotiateRound(UploadPack up,
 			Collection<? extends ObjectId> wants, int cntCommon,
 			int cntNotFound, boolean ready)
@@ -94,6 +96,7 @@ public void onEndNegotiateRound(UploadPack up,
 			hooks[i].onEndNegotiateRound(up, wants, cntCommon, cntNotFound, ready);
 	}
 
+	@Override
 	public void onSendPack(UploadPack up,
 			Collection<? extends ObjectId> wants,
 			Collection<? extends ObjectId> haves)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index d436e08..706e727 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -140,6 +140,7 @@ public PushCertificateStore(Repository db) {
 	 * 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();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 5590c2d..3201732 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -124,7 +124,7 @@ class PushProcess {
 			throws TransportException {
 		this.walker = new RevWalk(transport.local);
 		this.transport = transport;
-		this.toPush = new HashMap<String, RemoteRefUpdate>();
+		this.toPush = new HashMap<>();
 		this.out = out;
 		this.pushOptions = transport.getPushOptions();
 		for (final RemoteRefUpdate rru : toPush) {
@@ -190,7 +190,7 @@ else if (!preprocessed.isEmpty())
 	private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
 			throws TransportException {
 		boolean atomic = transport.isPushAtomic();
-		final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>();
+		final Map<String, RemoteRefUpdate> result = new HashMap<>();
 		for (final RemoteRefUpdate rru : toPush.values()) {
 			final Ref advertisedRef = connection.getRef(rru.getRemoteName());
 			ObjectId advertisedOld = null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 393e25a..169df3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -273,6 +273,7 @@ private void service() throws IOException {
 			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$
 						}
@@ -285,6 +286,7 @@ void sendString(final String s) throws IOException {
 					}
 				}
 				sendStatusReport(true, unpackError, new Reporter() {
+					@Override
 					void sendString(final String s) throws IOException {
 						pckOut.writeString(s + "\n"); //$NON-NLS-1$
 					}
@@ -292,6 +294,7 @@ void sendString(final String s) throws IOException {
 				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$
 					}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 0cd720c..745e813 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -163,9 +163,9 @@ protected void end() throws IOException {
 
 	private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH];
 
-	final Set<String> capablities = new LinkedHashSet<String>();
+	final Set<String> capablities = new LinkedHashSet<>();
 
-	private final Set<ObjectId> sent = new HashSet<ObjectId>();
+	private final Set<ObjectId> sent = new HashSet<>();
 
 	private Repository repository;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
index e46195f..d4f85f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
@@ -59,6 +59,7 @@
 public interface RefFilter {
 	/** The default filter, allows all refs to be shown. */
 	public static final RefFilter DEFAULT = new RefFilter() {
+		@Override
 		public Map<String, Ref> filter (final Map<String, Ref> refs) {
 			return refs;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java
new file mode 100644
index 0000000..734f523
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+	/**
+	 *
+	 * @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;
+	}
+
+	@Override
+	public String toString() {
+		final StringBuilder r = new StringBuilder();
+		r.append(getRef());
+		r.append(':');
+		r.append(getExpected());
+		return r.toString();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index 1440b83..64f6c3f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -556,6 +556,7 @@ private static boolean isValid(final String s) {
 		return true;
 	}
 
+	@Override
 	public int hashCode() {
 		int hc = 0;
 		if (getSource() != null)
@@ -565,6 +566,7 @@ public int hashCode() {
 		return hc;
 	}
 
+	@Override
 	public boolean equals(final Object obj) {
 		if (!(obj instanceof RefSpec))
 			return false;
@@ -588,6 +590,7 @@ private static boolean eq(final String a, final String b) {
 		return a.equals(b);
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder r = new StringBuilder();
 		if (isForceUpdate())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
index ba0931b..d91684e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
@@ -114,11 +114,11 @@ public class RemoteConfig implements Serializable {
 	 */
 	public static List<RemoteConfig> getAllRemoteConfigs(final Config rc)
 			throws URISyntaxException {
-		final List<String> names = new ArrayList<String>(rc
+		final List<String> names = new ArrayList<>(rc
 				.getSubsections(SECTION));
 		Collections.sort(names);
 
-		final List<RemoteConfig> result = new ArrayList<RemoteConfig>(names
+		final List<RemoteConfig> result = new ArrayList<>(names
 				.size());
 		for (final String name : names)
 			result.add(new RemoteConfig(rc, name));
@@ -169,24 +169,24 @@ public RemoteConfig(final Config rc, final String remoteName)
 
 		vlst = rc.getStringList(SECTION, name, KEY_URL);
 		Map<String, String> insteadOf = getReplacements(rc, KEY_INSTEADOF);
-		uris = new ArrayList<URIish>(vlst.length);
+		uris = new ArrayList<>(vlst.length);
 		for (final String s : vlst)
 			uris.add(new URIish(replaceUri(s, insteadOf)));
 
 		Map<String, String> pushInsteadOf = getReplacements(rc,
 				KEY_PUSHINSTEADOF);
 		vlst = rc.getStringList(SECTION, name, KEY_PUSHURL);
-		pushURIs = new ArrayList<URIish>(vlst.length);
+		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<RefSpec>(vlst.length);
+		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<RefSpec>(vlst.length);
+		push = new ArrayList<>(vlst.length);
 		for (final String s : vlst)
 			push.add(new RefSpec(s));
 
@@ -213,7 +213,7 @@ public RemoteConfig(final Config rc, final String remoteName)
 	 *            the configuration file to store ourselves into.
 	 */
 	public void update(final Config rc) {
-		final List<String> vlst = new ArrayList<String>();
+		final List<String> vlst = new ArrayList<>();
 
 		vlst.clear();
 		for (final URIish u : getURIs())
@@ -272,7 +272,7 @@ private void unset(final Config rc, final String key) {
 
 	private Map<String, String> getReplacements(final Config config,
 			final String keyName) {
-		final Map<String, String> replacements = new HashMap<String, String>();
+		final Map<String, String> replacements = new HashMap<>();
 		for (String url : config.getSubsections(KEY_URL))
 			for (String insteadOf : config.getStringList(KEY_URL, url, keyName))
 				replacements.put(insteadOf, url);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
index 81c5da3..87c9a500 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
@@ -45,8 +45,6 @@
 
 import java.io.IOException;
 
-import javax.servlet.http.HttpServletResponse;
-
 import org.eclipse.jgit.internal.JGitText;
 
 /**
@@ -55,6 +53,7 @@
  * @since 2.0
  */
 public class ServiceMayNotContinueException extends IOException {
+	private static final int FORBIDDEN = 403;
 	private static final long serialVersionUID = 1L;
 
 	private final int statusCode;
@@ -63,7 +62,7 @@ public class ServiceMayNotContinueException extends IOException {
 	/** Initialize with no message. */
 	public ServiceMayNotContinueException() {
 		// Do not set a message.
-		statusCode = HttpServletResponse.SC_FORBIDDEN;
+		statusCode = FORBIDDEN;
 	}
 
 	/**
@@ -73,7 +72,7 @@ public ServiceMayNotContinueException() {
 	 */
 	public ServiceMayNotContinueException(String msg) {
 		super(msg);
-		statusCode = HttpServletResponse.SC_FORBIDDEN;
+		statusCode = FORBIDDEN;
 	}
 
 	/**
@@ -99,7 +98,7 @@ public ServiceMayNotContinueException(String msg, int statusCode) {
 	 */
 	public ServiceMayNotContinueException(String msg, Throwable cause) {
 		super(msg, cause);
-		statusCode = HttpServletResponse.SC_FORBIDDEN;
+		statusCode = FORBIDDEN;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
index 942e7d7..83b4aca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
@@ -55,6 +55,7 @@ public class SignedPushConfig {
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final SectionParser<SignedPushConfig> KEY =
 			new SectionParser<SignedPushConfig>() {
+		@Override
 		public SignedPushConfig parse(Config cfg) {
 			return new SignedPushConfig(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
index 5fd2f84..8a28e3a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
@@ -103,7 +103,7 @@ public TestProtocol(UploadPackFactory<C> uploadPackFactory,
 			ReceivePackFactory<C> receivePackFactory) {
 		this.uploadPackFactory = uploadPackFactory;
 		this.receivePackFactory = receivePackFactory;
-		this.handles = new HashMap<URIish, Handle>();
+		this.handles = new HashMap<>();
 	}
 
 	@Override
@@ -174,7 +174,7 @@ private class TransportInternal extends Transport implements PackTransport {
 		public FetchConnection openFetch() throws NotSupportedException,
 				TransportException {
 			handle.remote.incrementOpen();
-			return new InternalFetchConnection<C>(
+			return new InternalFetchConnection<>(
 					this, uploadPackFactory, handle.req, handle.remote);
 		}
 
@@ -182,7 +182,7 @@ public FetchConnection openFetch() throws NotSupportedException,
 		public PushConnection openPush() throws NotSupportedException,
 				TransportException {
 			handle.remote.incrementOpen();
-			return new InternalPushConnection<C>(
+			return new InternalPushConnection<>(
 					this, receivePackFactory, handle.req, handle.remote);
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index 72c9c8b..2198b50 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -70,6 +70,7 @@ public class TransferConfig {
 
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {
+		@Override
 		public TransferConfig parse(final Config cfg) {
 			return new TransferConfig(cfg);
 		}
@@ -207,8 +208,9 @@ public RefFilter getRefFilter() {
 			return RefFilter.DEFAULT;
 
 		return new RefFilter() {
+			@Override
 			public Map<String, Ref> filter(Map<String, Ref> refs) {
-				Map<String, Ref> result = new HashMap<String, Ref>();
+				Map<String, Ref> result = new HashMap<>();
 				for (Map.Entry<String, Ref> e : refs.entrySet()) {
 					boolean add = true;
 					for (String hide : hideRefs) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index df86069..649e840 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -80,6 +80,7 @@
 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;
@@ -108,7 +109,7 @@ public enum Operation {
 	}
 
 	private static final List<WeakReference<TransportProtocol>> protocols =
-		new CopyOnWriteArrayList<WeakReference<TransportProtocol>>();
+		new CopyOnWriteArrayList<>();
 
 	static {
 		// Registration goes backwards in order of priority.
@@ -225,7 +226,7 @@ private static void load(ClassLoader ldr, String cn) {
 	 *            the protocol definition. Must not be null.
 	 */
 	public static void register(TransportProtocol proto) {
-		protocols.add(0, new WeakReference<TransportProtocol>(proto));
+		protocols.add(0, new WeakReference<>(proto));
 	}
 
 	/**
@@ -255,7 +256,7 @@ public static void unregister(TransportProtocol proto) {
 	 */
 	public static List<TransportProtocol> getTransportProtocols() {
 		int cnt = protocols.size();
-		List<TransportProtocol> res = new ArrayList<TransportProtocol>(cnt);
+		List<TransportProtocol> res = new ArrayList<>(cnt);
 		for (WeakReference<TransportProtocol> ref : protocols) {
 			TransportProtocol proto = ref.get();
 			if (proto != null)
@@ -379,7 +380,7 @@ public static List<Transport> openAll(final Repository local,
 			TransportException {
 		final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
 		if (doesNotExist(cfg)) {
-			final ArrayList<Transport> transports = new ArrayList<Transport>(1);
+			final ArrayList<Transport> transports = new ArrayList<>(1);
 			transports.add(open(local, new URIish(remote), null));
 			return transports;
 		}
@@ -489,7 +490,7 @@ public static List<Transport> openAll(final Repository local,
 			final RemoteConfig cfg, final Operation op)
 			throws NotSupportedException, TransportException {
 		final List<URIish> uris = getURIs(cfg, op);
-		final List<Transport> transports = new ArrayList<Transport>(uris.size());
+		final List<Transport> transports = new ArrayList<>(uris.size());
 		for (final URIish uri : uris) {
 			final Transport tn = open(local, uri, cfg.getName());
 			tn.applyConfig(cfg);
@@ -603,14 +604,16 @@ public static Transport open(URIish uri) throws NotSupportedException, Transport
 	 * 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.
+	 * 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.
@@ -618,13 +621,15 @@ public static Transport open(URIish uri) throws NotSupportedException, Transport
 	 * @throws IOException
 	 *             when problem occurred during conversion or specification set
 	 *             up: most probably, missing objects or refs.
+	 * @since 4.7
 	 */
 	public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
 			final Repository db, final Collection<RefSpec> specs,
+			final Map<String, RefLeaseSpec> leases,
 			Collection<RefSpec> fetchSpecs) throws IOException {
 		if (fetchSpecs == null)
 			fetchSpecs = Collections.emptyList();
-		final List<RemoteRefUpdate> result = new LinkedList<RemoteRefUpdate>();
+		final List<RemoteRefUpdate> result = new LinkedList<>();
 		final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
 
 		for (final RefSpec spec : procRefs) {
@@ -652,18 +657,48 @@ public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
 
 			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 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.
+	 *
+	 * @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 RemoteRefUpdate}.
+	 * @throws IOException
+	 *             when problem occurred during conversion or specification set
+	 *             up: most probably, missing objects or refs.
+	 */
+	public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
+			final Repository db, final Collection<RefSpec> specs,
+			Collection<RefSpec> fetchSpecs) throws IOException {
+		return findRemoteRefUpdatesFor(db, specs, Collections.emptyMap(),
+					       fetchSpecs);
+	}
+
 	private static Collection<RefSpec> expandPushWildcardsFor(
 			final Repository db, final Collection<RefSpec> specs)
 			throws IOException {
 		final Map<String, Ref> localRefs = db.getRefDatabase().getRefs(ALL);
-		final Collection<RefSpec> procRefs = new HashSet<RefSpec>();
+		final Collection<RefSpec> procRefs = new HashSet<>();
 
 		for (final RefSpec spec : specs) {
 			if (spec.isWildcard()) {
@@ -1182,7 +1217,7 @@ public FetchResult fetch(final ProgressMonitor monitor,
 			// the local tracking branches without incurring additional
 			// object transfer overheads.
 			//
-			final Collection<RefSpec> tmp = new ArrayList<RefSpec>(toFetch);
+			final Collection<RefSpec> tmp = new ArrayList<>(toFetch);
 			for (final RefSpec requested : toFetch) {
 				final String reqSrc = requested.getSource();
 				for (final RefSpec configured : fetch) {
@@ -1341,7 +1376,36 @@ public PushResult push(final ProgressMonitor monitor,
 	 */
 	public Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
 			final Collection<RefSpec> specs) throws IOException {
-		return findRemoteRefUpdatesFor(local, specs, fetch);
+		return findRemoteRefUpdatesFor(local, specs, Collections.emptyMap(),
+					       fetch);
+	}
+
+	/**
+	 * 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
+	 * set according to leases. Tracking branch is configured if RefSpec destination
+	 * matches source of any fetch ref spec for this transport remote
+	 * configuration.
+	 * <p>
+	 * 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 RemoteRefUpdate}.
+	 * @throws IOException
+	 *             when problem occurred during conversion or specification set
+	 *             up: most probably, missing objects or refs.
+	 * @since 4.7
+	 */
+	public Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
+			final Collection<RefSpec> specs,
+			final Map<String, RefLeaseSpec> leases) throws IOException {
+		return findRemoteRefUpdatesFor(local, specs, leases,
+					       fetch);
 	}
 
 	/**
@@ -1383,5 +1447,6 @@ public abstract PushConnection openPush() throws NotSupportedException,
 	 * Implementers shouldn't throw checked exceptions. This override narrows
 	 * the signature to prevent them from doing so.
 	 */
+	@Override
 	public abstract void close();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 23c506b..6cd119b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -101,23 +101,28 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
 	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<String> getSchemes() {
 			return Collections.singleton(S3_SCHEME);
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
 					URIishField.HOST, URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> 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);
@@ -265,10 +270,10 @@ WalkRemoteObjectDatabase openAlternate(final String location)
 
 		@Override
 		Collection<String> getPackNames() throws IOException {
-			final HashSet<String> have = new HashSet<String>();
+			final HashSet<String> have = new HashSet<>();
 			have.addAll(s3.list(bucket, resolveKey("pack"))); //$NON-NLS-1$
 
-			final Collection<String> packs = new ArrayList<String>();
+			final Collection<String> packs = new ArrayList<>();
 			for (final String n : have) {
 				if (!n.startsWith("pack-") || !n.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
 					continue;
@@ -307,7 +312,7 @@ void writeFile(final String path, final byte[] data) throws IOException {
 		}
 
 		Map<String, Ref> readAdvertisedRefs() throws TransportException {
-			final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+			final TreeMap<String, Ref> avail = new TreeMap<>();
 			readPackedRefs(avail);
 			readLooseRefs(avail);
 			readRef(avail, Constants.HEAD);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
index 9b08341..f2ddc0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
@@ -66,7 +66,7 @@ class TransportBundleFile extends Transport implements TransportBundle {
 		private final String[] schemeNames = { "bundle", "file" }; //$NON-NLS-1$ //$NON-NLS-2$
 
 		private final Set<String> schemeSet = Collections
-				.unmodifiableSet(new LinkedHashSet<String>(Arrays
+				.unmodifiableSet(new LinkedHashSet<>(Arrays
 						.asList(schemeNames)));
 
 		@Override
@@ -74,6 +74,7 @@ public String getName() {
 			return JGitText.get().transportProtoBundleFile;
 		}
 
+		@Override
 		public Set<String> getSchemes() {
 			return schemeSet;
 		}
@@ -106,6 +107,7 @@ public Transport open(URIish uri, Repository local, String remoteName)
 			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$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
index c6e4c50..7bf5b94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
@@ -75,27 +75,33 @@ class TransportGitAnon extends TcpTransport implements PackTransport {
 	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<String> getSchemes() {
 			return Collections.singleton("git"); //$NON-NLS-1$
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
 					URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> 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);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index da98e8c..9a40f47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -56,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;
@@ -86,27 +87,32 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
 		private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 
 		private final Set<String> schemeSet = Collections
-				.unmodifiableSet(new LinkedHashSet<String>(Arrays
+				.unmodifiableSet(new LinkedHashSet<>(Arrays
 						.asList(schemeNames)));
 
+		@Override
 		public String getName() {
 			return JGitText.get().transportProtoSSH;
 		}
 
+		@Override
 		public Set<String> getSchemes() {
 			return schemeSet;
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
 					URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> getOptionalFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
 					URIishField.PASS, URIishField.PORT));
 		}
 
+		@Override
 		public int getDefaultPort() {
 			return 22;
 		}
@@ -123,6 +129,7 @@ public boolean canHandle(URIish uri, Repository local, String remoteName) {
 			return super.canHandle(uri, local, remoteName);
 		}
 
+		@Override
 		public Transport open(URIish uri, Repository local, String remoteName)
 				throws NotSupportedException {
 			return new TransportGitSsh(local, uri);
@@ -214,14 +221,16 @@ private static boolean useExtSession() {
 	}
 
 	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<String> args = new ArrayList<String>();
+			List<String> 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$
@@ -248,6 +257,7 @@ public Process exec(String command, int timeout)
 			}
 		}
 
+		@Override
 		public void disconnect() {
 			// Nothing to do
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 96a6fe1..26a254d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -52,6 +52,7 @@
 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;
@@ -153,64 +154,77 @@ public enum AcceptEncoding {
 		private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$
 
 		private final Set<String> schemeSet = Collections
-				.unmodifiableSet(new LinkedHashSet<String>(Arrays
+				.unmodifiableSet(new LinkedHashSet<>(Arrays
 						.asList(schemeNames)));
 
+		@Override
 		public String getName() {
 			return JGitText.get().transportProtoHTTP;
 		}
 
+		@Override
 		public Set<String> getSchemes() {
 			return schemeSet;
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
 					URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> 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<String> getSchemes() {
 			return Collections.singleton("ftp"); //$NON-NLS-1$
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
 					URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> 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);
@@ -218,6 +232,7 @@ public Transport open(URIish uri, Repository local, String remoteName)
 	};
 
 	private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
+		@Override
 		public HttpConfig parse(final Config cfg) {
 			return new HttpConfig(cfg);
 		}
@@ -536,7 +551,7 @@ private HttpConnection connect(final String service)
 			} catch (IOException e) {
 				if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
 					if (ignoreTypes == null) {
-						ignoreTypes = new HashSet<Type>();
+						ignoreTypes = new HashSet<>();
 					}
 
 					ignoreTypes.add(authMethod.getType());
@@ -711,7 +726,7 @@ BufferedReader openReader(String path) throws IOException {
 
 		@Override
 		Collection<String> getPackNames() throws IOException {
-			final Collection<String> packs = new ArrayList<String>();
+			final Collection<String> packs = new ArrayList<>();
 			try {
 				final BufferedReader br = openReader(INFO_PACKS);
 				try {
@@ -764,7 +779,7 @@ FileStream open(String path, AcceptEncoding acceptEncoding)
 
 		Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
 				throws IOException, PackProtocolException {
-			final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+			final TreeMap<String, Ref> avail = new TreeMap<>();
 			for (;;) {
 				String line = br.readLine();
 				if (line == null)
@@ -862,6 +877,7 @@ class SmartHttpPushConnection extends BasePackPushConnection {
 			readAdvertisedRefs();
 		}
 
+		@Override
 		protected void doPush(final ProgressMonitor monitor,
 				final Map<String, RemoteRefUpdate> refUpdates,
 				OutputStream outputStream) throws TransportException {
@@ -898,9 +914,13 @@ abstract class Service {
 		}
 
 		void openStream() throws IOException {
+			openStream(null);
+		}
+
+		void openStream(final String redirectUrl) throws IOException {
 			conn = httpOpen(
 					METHOD_POST,
-					new URL(baseUrl, serviceName),
+					redirectUrl == null ? new URL(baseUrl, serviceName) : new URL(redirectUrl),
 					AcceptEncoding.GZIP);
 			conn.setInstanceFollowRedirects(false);
 			conn.setDoOutput(true);
@@ -909,6 +929,10 @@ void openStream() throws IOException {
 		}
 
 		void sendRequest() throws IOException {
+			sendRequest(null);
+		}
+
+		void sendRequest(final String redirectUrl) throws IOException {
 			// Try to compress the content, but only if that is smaller.
 			TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer);
 			try {
@@ -923,7 +947,7 @@ void sendRequest() throws IOException {
 				buf = out;
 			}
 
-			openStream();
+			openStream(redirectUrl);
 			if (buf != out)
 				conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
 			conn.setFixedLengthStreamingMode((int) buf.length());
@@ -933,6 +957,12 @@ void sendRequest() throws IOException {
 			} finally {
 				httpOut.close();
 			}
+
+			final int status = HttpSupport.response(conn);
+			if (status == HttpConnection.HTTP_MOVED_PERM) {
+				String locationHeader = HttpSupport.responseHeader(conn, HDR_LOCATION);
+				sendRequest(locationHeader);
+			}
 		}
 
 		void openResponse() throws IOException {
@@ -960,16 +990,19 @@ InputStream getInputStream() {
 		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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
index 1528c71..f483ec7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -100,6 +100,7 @@ public String getName() {
 			return JGitText.get().transportProtoLocal;
 		}
 
+		@Override
 		public Set<String> getSchemes() {
 			return Collections.singleton("file"); //$NON-NLS-1$
 		}
@@ -132,6 +133,7 @@ public Transport open(URIish uri, Repository local, String remoteName)
 			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$
@@ -189,7 +191,7 @@ public UploadPack create(Void req, Repository db) {
 				return createUploadPack(db);
 			}
 		};
-		return new InternalFetchConnection<Void>(this, upf, null, openRepo());
+		return new InternalFetchConnection<>(this, upf, null, openRepo());
 	}
 
 	@Override
@@ -205,7 +207,7 @@ public ReceivePack create(Void req, Repository db) {
 				return createReceivePack(db);
 			}
 		};
-		return new InternalPushConnection<Void>(this, rpf, null, openRepo());
+		return new InternalPushConnection<>(this, rpf, null, openRepo());
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index fa073ae..99e4809 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -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;
@@ -99,28 +101,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<String> getSchemes() {
 			return Collections.singleton("sftp"); //$NON-NLS-1$
 		}
 
+		@Override
 		public Set<URIishField> getRequiredFields() {
 			return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
 					URIishField.PATH));
 		}
 
+		@Override
 		public Set<URIishField> 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);
@@ -225,15 +233,15 @@ WalkRemoteObjectDatabase openAlternate(final String location)
 
 		@Override
 		Collection<String> getPackNames() throws IOException {
-			final List<String> packs = new ArrayList<String>();
+			final List<String> packs = new ArrayList<>();
 			try {
 				@SuppressWarnings("unchecked")
 				final Collection<ChannelSftp.LsEntry> list = ftp.ls("pack"); //$NON-NLS-1$
 				final HashMap<String, ChannelSftp.LsEntry> files;
 				final HashMap<String, Integer> mtimes;
 
-				files = new HashMap<String, ChannelSftp.LsEntry>();
-				mtimes = new HashMap<String, Integer>();
+				files = new HashMap<>();
+				mtimes = new HashMap<>();
 
 				for (final ChannelSftp.LsEntry ent : list)
 					files.put(ent.getFilename(), ent);
@@ -251,6 +259,7 @@ Collection<String> getPackNames() throws IOException {
 				}
 
 				Collections.sort(packs, new Comparator<String>() {
+					@Override
 					public int compare(final String o1, final String o2) {
 						return mtimes.get(o2).intValue()
 								- mtimes.get(o1).intValue();
@@ -334,7 +343,7 @@ OutputStream writeFile(final String path,
 
 		@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 +390,7 @@ private void mkdir_p(String path) throws IOException {
 		}
 
 		Map<String, Ref> readAdvertisedRefs() throws TransportException {
-			final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+			final TreeMap<String, Ref> 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$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 3c5c8da..ffd4d41 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -560,6 +560,7 @@ public URIish setPort(final int n) {
 		return r;
 	}
 
+	@Override
 	public int hashCode() {
 		int hc = 0;
 		if (getScheme() != null)
@@ -577,6 +578,7 @@ public int hashCode() {
 		return hc;
 	}
 
+	@Override
 	public boolean equals(final Object obj) {
 		if (!(obj instanceof URIish))
 			return false;
@@ -615,6 +617,7 @@ public String toPrivateString() {
 		return format(true, false);
 	}
 
+	@Override
 	public String toString() {
 		return format(false, false);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index b1cc5de..7de5a9f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -176,7 +176,7 @@ public static class FirstLine {
 		 */
 		public FirstLine(String line) {
 			if (line.length() > 45) {
-				final HashSet<String> opts = new HashSet<String>();
+				final HashSet<String> opts = new HashSet<>();
 				String opt = line.substring(45);
 				if (opt.startsWith(" ")) //$NON-NLS-1$
 					opt = opt.substring(1);
@@ -262,19 +262,19 @@ public Set<String> getOptions() {
 	String userAgent;
 
 	/** Raw ObjectIds the client has asked for, before validating them. */
-	private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
+	private final Set<ObjectId> wantIds = new HashSet<>();
 
 	/** Objects the client wants to obtain. */
-	private final Set<RevObject> wantAll = new HashSet<RevObject>();
+	private final Set<RevObject> wantAll = new HashSet<>();
 
 	/** Objects on both sides, these don't have to be sent. */
-	private final Set<RevObject> commonBase = new HashSet<RevObject>();
+	private final Set<RevObject> commonBase = new HashSet<>();
 
 	/** Shallow commits the client already has. */
-	private final Set<ObjectId> clientShallowCommits = new HashSet<ObjectId>();
+	private final Set<ObjectId> clientShallowCommits = new HashSet<>();
 
 	/** Shallow commits on the client which are now becoming unshallow */
-	private final List<ObjectId> unshallowCommits = new ArrayList<ObjectId>();
+	private final List<ObjectId> unshallowCommits = new ArrayList<>();
 
 	/** Desired depth from the client on a shallow request. */
 	private int depth;
@@ -781,7 +781,7 @@ else if (requestValidator instanceof AnyRequestValidator)
 	}
 
 	private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
-		Set<ObjectId> ids = new HashSet<ObjectId>(refs.size());
+		Set<ObjectId> ids = new HashSet<>(refs.size());
 		for (Ref ref : refs) {
 			ObjectId id = ref.getObjectId();
 			if (id != null) {
@@ -1014,7 +1014,7 @@ private boolean negotiate() throws IOException {
 		okToGiveUp = Boolean.FALSE;
 
 		ObjectId last = ObjectId.zeroId();
-		List<ObjectId> peerHas = new ArrayList<ObjectId>(64);
+		List<ObjectId> peerHas = new ArrayList<>(64);
 		for (;;) {
 			String line;
 			try {
@@ -1168,7 +1168,7 @@ private void parseWants() throws IOException {
 		for (ObjectId obj : wantIds) {
 			if (!advertised.contains(obj)) {
 				if (notAdvertisedWants == null)
-					notAdvertisedWants = new ArrayList<ObjectId>();
+					notAdvertisedWants = new ArrayList<>();
 				notAdvertisedWants.add(obj);
 			}
 		}
@@ -1211,6 +1211,7 @@ private void want(RevObject obj) {
 	 */
 	public static final class AdvertisedRequestValidator
 			implements RequestValidator {
+		@Override
 		public void checkWants(UploadPack up, List<ObjectId> wants)
 				throws PackProtocolException, IOException {
 			if (!up.isBiDirectionalPipe())
@@ -1227,6 +1228,7 @@ else if (!wants.isEmpty())
 	 */
 	public static final class ReachableCommitRequestValidator
 			implements RequestValidator {
+		@Override
 		public void checkWants(UploadPack up, List<ObjectId> wants)
 				throws PackProtocolException, IOException {
 			checkNotAdvertisedWants(up.getRevWalk(), wants,
@@ -1240,6 +1242,7 @@ public void checkWants(UploadPack up, List<ObjectId> wants)
 	 * @since 3.1
 	 */
 	public static final class TipRequestValidator implements RequestValidator {
+		@Override
 		public void checkWants(UploadPack up, List<ObjectId> wants)
 				throws PackProtocolException, IOException {
 			if (!up.isBiDirectionalPipe())
@@ -1262,6 +1265,7 @@ else if (!wants.isEmpty()) {
 	 */
 	public static final class ReachableCommitTipRequestValidator
 			implements RequestValidator {
+		@Override
 		public void checkWants(UploadPack up, List<ObjectId> wants)
 				throws PackProtocolException, IOException {
 			checkNotAdvertisedWants(up.getRevWalk(), wants,
@@ -1275,6 +1279,7 @@ public void checkWants(UploadPack up, List<ObjectId> wants)
 	 * @since 3.1
 	 */
 	public static final class AnyRequestValidator implements RequestValidator {
+		@Override
 		public void checkWants(UploadPack up, List<ObjectId> wants)
 				throws PackProtocolException, IOException {
 			// All requests are valid.
@@ -1479,7 +1484,7 @@ private void sendPack(final boolean sideband) throws IOException {
 			pw.setReuseValidatingObjects(false);
 
 			if (commonBase.isEmpty() && refs != null) {
-				Set<ObjectId> tagTargets = new HashSet<ObjectId>();
+				Set<ObjectId> tagTargets = new HashSet<>();
 				for (Ref ref : refs.values()) {
 					if (ref.getPeeledObjectId() != null)
 						tagTargets.add(ref.getPeeledObjectId());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java
index 0588634..afc9965 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java
@@ -59,6 +59,7 @@
 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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java
index 4ea0319..9a8c12c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java
@@ -84,6 +84,7 @@ else if (i == 1)
 	/**
 	 * @since 3.0
 	 */
+	@Override
 	public void onPackStatistics(PackWriter.Statistics stats) {
 		for (int i = 0; i < count; i++)
 			loggers[i].onPackStatistics(stats);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
index 4c3fdd8..333e09d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
@@ -52,6 +52,7 @@
 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;
@@ -192,7 +193,7 @@ static class JetS3tV2 extends WalkEncryption {
 
 			// Standard names are not case-sensitive.
 			// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
-			String cryptoName = cryptoAlg.toUpperCase();
+			String cryptoName = cryptoAlg.toUpperCase(Locale.ROOT);
 
 			if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$
 				throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE);
@@ -373,7 +374,7 @@ static abstract class SymmetricEncryption extends WalkEncryption
 
 			SecretKey keyBase = factory.generateSecret(keySpec);
 
-			String name = cipherAlgo.toUpperCase();
+			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()) {
@@ -506,7 +507,7 @@ static Properties wrap(String algo, String pass) {
 		JGitV1(String algo, String pass)
 				throws GeneralSecurityException {
 			super(wrap(algo, pass));
-			String name = cipherAlgo.toUpperCase();
+			String name = cipherAlgo.toUpperCase(Locale.ROOT);
 			Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name);
 			if (!matcherPBE.matches())
 				throw new GeneralSecurityException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index 13d4a24..3d60aed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -197,20 +197,20 @@ class WalkFetchConnection extends BaseFetchConnection {
 		inserter = local.newObjectInserter();
 		reader = inserter.newReader();
 
-		remotes = new ArrayList<WalkRemoteObjectDatabase>();
+		remotes = new ArrayList<>();
 		remotes.add(w);
 
-		unfetchedPacks = new LinkedList<RemotePack>();
-		packsConsidered = new HashSet<String>();
+		unfetchedPacks = new LinkedList<>();
+		packsConsidered = new HashSet<>();
 
-		noPacksYet = new LinkedList<WalkRemoteObjectDatabase>();
+		noPacksYet = new LinkedList<>();
 		noPacksYet.add(w);
 
-		noAlternatesYet = new LinkedList<WalkRemoteObjectDatabase>();
+		noAlternatesYet = new LinkedList<>();
 		noAlternatesYet.add(w);
 
-		fetchErrors = new HashMap<ObjectId, List<Throwable>>();
-		packLocks = new ArrayList<PackLock>(4);
+		fetchErrors = new HashMap<>();
+		packLocks = new ArrayList<>(4);
 
 		revWalk = new RevWalk(reader);
 		revWalk.setRetainBody(false);
@@ -220,9 +220,10 @@ class WalkFetchConnection extends BaseFetchConnection {
 		LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$
 
 		localCommitQueue = new DateRevQueue();
-		workQueue = new LinkedList<ObjectId>();
+		workQueue = new LinkedList<>();
 	}
 
+	@Override
 	public boolean didFetchTestConnectivity() {
 		return true;
 	}
@@ -248,10 +249,12 @@ protected void doFetch(final ProgressMonitor monitor,
 		}
 	}
 
+	@Override
 	public Collection<PackLock> getPackLocks() {
 		return packLocks;
 	}
 
+	@Override
 	public void setPackLockMessage(final String message) {
 		lockMessage = message;
 	}
@@ -270,7 +273,7 @@ public void close() {
 
 	private void queueWants(final Collection<Ref> want)
 			throws TransportException {
-		final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
+		final HashSet<ObjectId> inWorkQueue = new HashSet<>();
 		for (final Ref r : want) {
 			final ObjectId id = r.getObjectId();
 			if (id == null) {
@@ -594,7 +597,7 @@ private boolean downloadPackedObject(final ProgressMonitor monitor,
 
 	private Iterator<ObjectId> swapFetchQueue() {
 		final Iterator<ObjectId> r = workQueue.iterator();
-		workQueue = new LinkedList<ObjectId>();
+		workQueue = new LinkedList<>();
 		return r;
 	}
 
@@ -791,7 +794,7 @@ private void recordError(final AnyObjectId id, final Throwable what) {
 		final ObjectId objId = id.copy();
 		List<Throwable> errors = fetchErrors.get(objId);
 		if (errors == null) {
-			errors = new ArrayList<Throwable>(2);
+			errors = new ArrayList<>(2);
 			fetchErrors.put(objId, errors);
 		}
 		errors.add(what);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index 7b449c7..5c4e14c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -134,25 +134,27 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
 		dest = w;
 	}
 
+	@Override
 	public void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates)
 			throws TransportException {
 		push(monitor, refUpdates, null);
 	}
 
+	@Override
 	public void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates, OutputStream out)
 			throws TransportException {
 		markStartedOperation();
 		packNames = null;
-		newRefs = new TreeMap<String, Ref>(getRefsMap());
-		packedRefUpdates = new ArrayList<RemoteRefUpdate>(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<RemoteRefUpdate> updates = new ArrayList<RemoteRefUpdate>();
+		final List<RemoteRefUpdate> updates = new ArrayList<>();
 		for (final RemoteRefUpdate u : refUpdates.values()) {
 			final String n = u.getRemoteName();
 			if (!n.startsWith("refs/") || !Repository.isValidRefName(n)) { //$NON-NLS-1$
@@ -223,8 +225,8 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 		try (final PackWriter writer = new PackWriter(transport.getPackConfig(),
 				local.newObjectReader())) {
 
-			final Set<ObjectId> need = new HashSet<ObjectId>();
-			final Set<ObjectId> have = new HashSet<ObjectId>();
+			final Set<ObjectId> need = new HashSet<>();
+			final Set<ObjectId> have = new HashSet<>();
 			for (final RemoteRefUpdate r : updates)
 				need.add(r.getNewObjectId());
 			for (final Ref r : getRefs()) {
@@ -241,7 +243,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 			if (writer.getObjectCount() == 0)
 				return;
 
-			packNames = new LinkedHashMap<String, String>();
+			packNames = new LinkedHashMap<>();
 			for (final String n : dest.getPackNames())
 				packNames.put(n, n);
 
@@ -277,7 +279,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 			// way clients are likely to consult the newest pack first,
 			// and discover the most recent objects there.
 			//
-			final ArrayList<String> infoPacks = new ArrayList<String>();
+			final ArrayList<String> infoPacks = new ArrayList<>();
 			infoPacks.add(packName);
 			infoPacks.addAll(packNames.keySet());
 			dest.writeInfoPacks(infoPacks);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
index 24f30ed..17f8c3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -396,7 +396,7 @@ Collection<WalkRemoteObjectDatabase> readAlternates(final String listPath)
 			throws IOException {
 		final BufferedReader br = openReader(listPath);
 		try {
-			final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<WalkRemoteObjectDatabase>();
+			final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<>();
 			for (;;) {
 				String line = br.readLine();
 				if (line == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index 09613fd..58081c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -73,6 +73,12 @@ public interface HttpConnection {
 	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_NOT_FOUND
 	 */
 	public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
index ed37fea..534e3d7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
@@ -94,95 +94,118 @@ protected JDKHttpConnection(URL url, Proxy proxy)
 				.openConnection(proxy);
 	}
 
+	@Override
 	public int getResponseCode() throws IOException {
 		return wrappedUrlConnection.getResponseCode();
 	}
 
+	@Override
 	public URL getURL() {
 		return wrappedUrlConnection.getURL();
 	}
 
+	@Override
 	public String getResponseMessage() throws IOException {
 		return wrappedUrlConnection.getResponseMessage();
 	}
 
+	@Override
 	public Map<String, List<String>> getHeaderFields() {
 		return wrappedUrlConnection.getHeaderFields();
 	}
 
+	@Override
 	public void setRequestProperty(String key, String value) {
 		wrappedUrlConnection.setRequestProperty(key, value);
 	}
 
+	@Override
 	public void setRequestMethod(String method) throws ProtocolException {
 		wrappedUrlConnection.setRequestMethod(method);
 	}
 
+	@Override
 	public void setUseCaches(boolean usecaches) {
 		wrappedUrlConnection.setUseCaches(usecaches);
 	}
 
+	@Override
 	public void setConnectTimeout(int timeout) {
 		wrappedUrlConnection.setConnectTimeout(timeout);
 	}
 
+	@Override
 	public void setReadTimeout(int timeout) {
 		wrappedUrlConnection.setReadTimeout(timeout);
 	}
 
+	@Override
 	public String getContentType() {
 		return wrappedUrlConnection.getContentType();
 	}
 
+	@Override
 	public InputStream getInputStream() throws IOException {
 		return wrappedUrlConnection.getInputStream();
 	}
 
+	@Override
 	public String getHeaderField(String name) {
 		return wrappedUrlConnection.getHeaderField(name);
 	}
 
+	@Override
 	public int getContentLength() {
 		return wrappedUrlConnection.getContentLength();
 	}
 
+	@Override
 	public void setInstanceFollowRedirects(boolean followRedirects) {
 		wrappedUrlConnection.setInstanceFollowRedirects(followRedirects);
 	}
 
+	@Override
 	public void setDoOutput(boolean dooutput) {
 		wrappedUrlConnection.setDoOutput(dooutput);
 	}
 
+	@Override
 	public void setFixedLengthStreamingMode(int contentLength) {
 		wrappedUrlConnection.setFixedLengthStreamingMode(contentLength);
 	}
 
+	@Override
 	public OutputStream getOutputStream() throws IOException {
 		return wrappedUrlConnection.getOutputStream();
 	}
 
+	@Override
 	public void setChunkedStreamingMode(int chunklen) {
 		wrappedUrlConnection.setChunkedStreamingMode(chunklen);
 	}
 
+	@Override
 	public String getRequestMethod() {
 		return wrappedUrlConnection.getRequestMethod();
 	}
 
+	@Override
 	public boolean usingProxy() {
 		return wrappedUrlConnection.usingProxy();
 	}
 
+	@Override
 	public void connect() throws IOException {
 		wrappedUrlConnection.connect();
 	}
 
+	@Override
 	public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
 		((HttpsURLConnection) wrappedUrlConnection)
 				.setHostnameVerifier(hostnameverifier);
 	}
 
+	@Override
 	public void configure(KeyManager[] km, TrustManager[] tm,
 			SecureRandom random) throws NoSuchAlgorithmException,
 			KeyManagementException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java
index d1c875d..b9f009f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java
@@ -52,10 +52,12 @@
  * @since 3.3
  */
 public class JDKHttpConnectionFactory implements HttpConnectionFactory {
+	@Override
 	public HttpConnection create(URL url) throws IOException {
 		return new JDKHttpConnection(url);
 	}
 
+	@Override
 	public HttpConnection create(URL url, Proxy proxy)
 			throws IOException {
 		return new JDKHttpConnection(url, proxy);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 6964e7f..7654d46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -72,8 +72,8 @@ public class FileResolver<C> implements RepositoryResolver<C> {
 
 	/** Initialize an empty file based resolver. */
 	public FileResolver() {
-		exports = new ConcurrentHashMap<String, Repository>();
-		exportBase = new CopyOnWriteArrayList<File>();
+		exports = new ConcurrentHashMap<>();
+		exportBase = new CopyOnWriteArrayList<>();
 	}
 
 	/**
@@ -91,6 +91,7 @@ public FileResolver(final File basePath, final boolean exportAll) {
 		setExportAll(exportAll);
 	}
 
+	@Override
 	public Repository open(final C req, final String name)
 			throws RepositoryNotFoundException, ServiceNotEnabledException {
 		if (isUnreasonableName(name))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
index 4cf49f5..73c2ed8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
@@ -55,6 +55,7 @@
 public interface ReceivePackFactory<C> {
 	/** A factory disabling the ReceivePack service for all repositories */
 	public static final ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
+		@Override
 		public ReceivePack create(Object req, Repository db)
 				throws ServiceNotEnabledException {
 			throw new ServiceNotEnabledException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
index c7f0d32..80211e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
@@ -56,6 +56,7 @@
 public interface RepositoryResolver<C> {
 	/** Resolver configured to open nothing. */
 	public static final RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
+		@Override
 		public Repository open(Object req, String name)
 				throws RepositoryNotFoundException {
 			throw new RepositoryNotFoundException(name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
index f0d2ba8..d7ed0f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
@@ -55,6 +55,7 @@
 public interface UploadPackFactory<C> {
 	/** A factory disabling the UploadPack service for all repositories. */
 	public static final UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
+		@Override
 		public UploadPack create(Object req, Repository db)
 				throws ServiceNotEnabledException {
 			throw new ServiceNotEnabledException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index c038f07..2d6abd1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -249,6 +249,7 @@ public final CanonicalTreeParser createSubtreeIterator0(
 		return p;
 	}
 
+	@Override
 	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
 		return createSubtreeIterator(reader, new MutableObjectId());
@@ -280,6 +281,7 @@ public boolean first() {
 		return currPtr == 0;
 	}
 
+	@Override
 	public boolean eof() {
 		return currPtr == raw.length;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index b9293eb..c0b29ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -353,6 +353,7 @@ void skipEntriesEqual() throws CorruptObjectException {
 			dfConflict = null;
 	}
 
+	@Override
 	void stopWalk() throws IOException {
 		if (!needsStopWalk()) {
 			return;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 5dfebe9..c54e148 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -128,7 +128,7 @@ public static enum OperationType {
 	 * The filter command as defined in gitattributes. The keys are
 	 * filterName+"."+filterCommandType. E.g. "lfs.clean"
 	 */
-	private Map<String, String> filterCommandsByNameDotType = new HashMap<String, String>();
+	private Map<String, String> filterCommandsByNameDotType = new HashMap<>();
 
 	/**
 	 * @param operationType
@@ -559,6 +559,7 @@ public AttributesNodeProvider getAttributesNodeProvider() {
 	 * @return a {@link Set} of {@link Attribute}s that match the current entry.
 	 * @since 4.2
 	 */
+	@Override
 	public Attributes getAttributes() {
 		if (attrs != null)
 			return attrs;
@@ -825,7 +826,7 @@ public boolean next() throws MissingObjectException,
 				}
 
 				currentHead = t;
-				if (!filter.include(this)) {
+				if (filter.matchFilter(this) == 1) {
 					skipEntriesEqual();
 					continue;
 				}
@@ -1061,6 +1062,60 @@ public int getPathLength() {
 	/**
 	 * Test if the supplied path matches the current entry's path.
 	 * <p>
+	 * 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 <code>startsWith</code> or some other type of string
+	 * match function.
+	 * <p>
+	 * 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 <code>buf</code> 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.
+	 * <p>
 	 * 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
 	 * method then to use {@link #getPathString()} to first create a String
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 52477cb..b1b146c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -56,7 +56,6 @@
 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;
@@ -99,6 +98,7 @@
 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}.
@@ -364,7 +364,7 @@ public boolean isWorkTree() {
 			if (is == null)
 				return zeroid;
 			try {
-				state.initializeDigestAndReadBuffer();
+				state.initializeReadBuffer();
 
 				final long len = e.getLength();
 				InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
@@ -715,6 +715,7 @@ public AttributesNode getEntryAttributesNode() throws IOException {
 	}
 
 	private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
+		@Override
 		public int compare(Entry a, Entry b) {
 			return Paths.compare(
 					a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
@@ -1098,10 +1099,9 @@ private static long computeLength(InputStream in) throws IOException {
 	}
 
 	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) ' ');
 
@@ -1154,6 +1154,7 @@ void encodeName(final CharsetEncoder enc) {
 				b.get(encodedName = new byte[encodedNameLen]);
 		}
 
+		@Override
 		public String toString() {
 			return getMode().toString() + " " + getName(); //$NON-NLS-1$
 		}
@@ -1328,9 +1329,6 @@ private static final class IteratorState {
 		/** File name character encoder. */
 		final CharsetEncoder nameEncoder;
 
-		/** Digest computer for {@link #contentId} computations. */
-		MessageDigest contentDigest;
-
 		/** Buffer used to perform {@link #contentId} computations. */
 		byte[] contentReadBuffer;
 
@@ -1345,9 +1343,8 @@ private static final class IteratorState {
 			this.nameEncoder = Constants.CHARSET.newEncoder();
 		}
 
-		void initializeDigestAndReadBuffer() {
-			if (contentDigest == null) {
-				contentDigest = Constants.newMessageDigest();
+		void initializeReadBuffer() {
+			if (contentReadBuffer == null) {
 				contentReadBuffer = new byte[BUFFER_SIZE];
 			}
 		}
@@ -1366,7 +1363,7 @@ public String getCleanFilterCommand() throws IOException {
 				cmd = state.walk
 						.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
 			}
-			cleanFilterCommandHolder = new Holder<String>(cmd);
+			cleanFilterCommandHolder = new Holder<>(cmd);
 		}
 		return cleanFilterCommandHolder.get();
 	}
@@ -1413,7 +1410,7 @@ private EolStreamType getEolStreamType(OperationType opType)
 					break;
 				}
 			}
-			eolStreamTypeHolder = new Holder<EolStreamType>(type);
+			eolStreamTypeHolder = new Holder<>(type);
 		}
 		return eolStreamTypeHolder.get();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
index 112ce8f..7d2b33f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
@@ -56,6 +56,7 @@
 public class WorkingTreeOptions {
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<WorkingTreeOptions> KEY = new SectionParser<WorkingTreeOptions>() {
+		@Override
 		public WorkingTreeOptions parse(final Config cfg) {
 			return new WorkingTreeOptions(cfg);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
index d5e7464..9658166 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
@@ -128,7 +128,25 @@ private static class Binary extends AndTreeFilter {
 		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 +177,24 @@ private static class List extends AndTreeFilter {
 		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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
index 42725bc..b821a16 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
@@ -86,11 +86,11 @@ public class IndexDiffFilter extends TreeFilter {
 
 	private final boolean honorIgnores;
 
-	private final Set<String> ignoredPaths = new HashSet<String>();
+	private final Set<String> ignoredPaths = new HashSet<>();
 
-	private final LinkedList<String> untrackedParentFolders = new LinkedList<String>();
+	private final LinkedList<String> untrackedParentFolders = new LinkedList<>();
 
-	private final LinkedList<String> untrackedFolders = new LinkedList<String>();
+	private final LinkedList<String> untrackedFolders = new LinkedList<>();
 
 	/**
 	 * Creates a new instance of this filter. Do not use an instance of this
@@ -292,7 +292,7 @@ public Set<String> getIgnoredPaths() {
 	 *         empty list will be returned.
 	 */
 	public List<String> getUntrackedFolders() {
-		LinkedList<String> ret = new LinkedList<String>(untrackedFolders);
+		LinkedList<String> ret = new LinkedList<>(untrackedFolders);
 		if (!untrackedParentFolders.isEmpty()) {
 			String toBeAdded = untrackedParentFolders.getLast();
 			while (!ret.isEmpty() && ret.getLast().startsWith(toBeAdded))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
index 8ec04bb..80c0b87 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
@@ -78,7 +78,23 @@ public TreeFilter negate() {
 	public boolean include(final TreeWalk walker)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		return !a.include(walker);
+		return matchFilter(walker) == 0;
+	}
+
+	@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;
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
index 270633c..2c1a9d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
@@ -126,7 +126,25 @@ private static class Binary extends OrTreeFilter {
 		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 +175,24 @@ private static class List extends OrTreeFilter {
 		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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
index d85ea8c..445ba15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
@@ -97,7 +97,12 @@ public String getPath() {
 
 	@Override
 	public boolean include(final TreeWalk walker) {
-		return walker.isPathPrefix(pathRaw, pathRaw.length) == 0;
+		return matchFilter(walker) <= 0;
+	}
+
+	@Override
+	public int matchFilter(final TreeWalk walker) {
+		return walker.isPathMatch(pathRaw, pathRaw.length);
 	}
 
 	@Override
@@ -113,6 +118,7 @@ public PathFilter clone() {
 		return this;
 	}
 
+	@Override
 	@SuppressWarnings("nls")
 	public String toString() {
 		return "PATH(\"" + pathStr + "\")";
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
index 7601956..174a4f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
@@ -173,6 +173,7 @@ public TreeFilter clone() {
 			return this;
 		}
 
+		@Override
 		public String toString() {
 			return "FAST_" + path.toString(); //$NON-NLS-1$
 		}
@@ -267,6 +268,7 @@ public TreeFilter clone() {
 			return this;
 		}
 
+		@Override
 		public String toString() {
 			final StringBuilder r = new StringBuilder();
 			r.append("FAST("); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
index 7d99e58..2c2fb47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
@@ -199,6 +199,34 @@ public abstract boolean include(TreeWalk walker)
 			IOException;
 
 	/**
+	 * Determine if the current entry is a parent, a match, or no match.
+	 * <p>
+	 * 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 MissingObjectException
+	 *             as thrown by {@link #include(TreeWalk)}
+	 * @throws IncorrectObjectTypeException
+	 *             as thrown by {@link #include(TreeWalk)}
+	 * @throws 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?
 	 * <p>
 	 * If this tree filter is matching on full entry path names and its pattern
@@ -220,6 +248,7 @@ public abstract boolean include(TreeWalk walker)
 	 *
 	 * @return another copy of this filter, suitable for another thread.
 	 */
+	@Override
 	public abstract TreeFilter clone();
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
index ed5838a..c05570b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
@@ -7,6 +7,7 @@
 package org.eclipse.jgit.util;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.text.MessageFormat;
 import java.util.Arrays;
 
@@ -184,11 +185,7 @@ public static String encodeBytes(byte[] source, int off, int len) {
 			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);
 	}
 
 	/**
@@ -304,12 +301,7 @@ else if (source[srcOffset + 3] == EQUALS_SIGN) {
 	 * @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(StandardCharsets.UTF_8);
 		return decode(bytes, 0, bytes.length);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
index 9d0ad73..c86c588 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
@@ -309,10 +309,12 @@ private class MyIterator implements Iterator<T> {
 
 		private T[] block = directory[0];
 
+		@Override
 		public boolean hasNext() {
 			return index < size;
 		}
 
+		@Override
 		public T next() {
 			if (size <= index)
 				throw new NoSuchElementException();
@@ -329,6 +331,7 @@ public T next() {
 			return res;
 		}
 
+		@Override
 		public void remove() {
 			if (index == 0)
 				throw new IllegalStateException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
index 6828185..8677c69 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
@@ -51,7 +51,7 @@
 
 /** Abstract authenticator which remembers prior authentications. */
 public abstract class CachedAuthenticator extends Authenticator {
-	private static final Collection<CachedAuthentication> cached = new CopyOnWriteArrayList<CachedAuthentication>();
+	private static final Collection<CachedAuthentication> cached = new CopyOnWriteArrayList<>();
 
 	/**
 	 * Add a cached authentication for future use.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 4a2a2d6..183bf46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -45,6 +45,7 @@
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -52,6 +53,8 @@
 import java.io.OutputStream;
 import java.io.PrintStream;
 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;
@@ -60,7 +63,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.Callable;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -392,7 +395,7 @@ public File resolve(final File dir, final String name) {
 	public File userHome() {
 		Holder<File> p = userHome;
 		if (p == null) {
-			p = new Holder<File>(userHomeImpl());
+			p = new Holder<>(userHomeImpl());
 			userHome = p;
 		}
 		return p.value;
@@ -407,7 +410,7 @@ public File userHome() {
 	 * @return {@code this}.
 	 */
 	public FS setUserHome(File path) {
-		userHome = new Holder<File>(path);
+		userHome = new Holder<>(path);
 		return this;
 	}
 
@@ -439,6 +442,7 @@ public BasicFileAttributes fileAttributes(File file) throws IOException {
 	protected File userHomeImpl() {
 		final String home = AccessController
 				.doPrivileged(new PrivilegedAction<String>() {
+					@Override
 					public String run() {
 						return System.getProperty("user.home"); //$NON-NLS-1$
 					}
@@ -679,7 +683,7 @@ protected File discoverGitSystemConfig() {
 	 */
 	public File getGitSystemConfig() {
 		if (gitSystemConfig == null) {
-			gitSystemConfig = new Holder<File>(discoverGitSystemConfig());
+			gitSystemConfig = new Holder<>(discoverGitSystemConfig());
 		}
 		return gitSystemConfig.value;
 	}
@@ -693,7 +697,7 @@ public File getGitSystemConfig() {
 	 * @since 4.0
 	 */
 	public FS setGitSystemConfig(File configFile) {
-		gitSystemConfig = new Holder<File>(configFile);
+		gitSystemConfig = new Holder<>(configFile);
 		return this;
 	}
 
@@ -814,14 +818,84 @@ public void createSymLink(File path, String target) throws IOException {
 	 *            the file to be created
 	 * @return <code>true</code> if the file was created, <code>false</code> if
 	 *         the file already existed
-	 * @throws IOException
+	 * @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<Path> link;
+
+		LockToken(boolean isCreated, Optional<Path> 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$
+							: "<null>]"); //$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 <code>false</code>
+	 *
+	 * @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 FileUtils#relativize(String, String)}.
 	 *
 	 * @param base
@@ -1056,16 +1130,13 @@ public int runProcess(ProcessBuilder processBuilder,
 		IOException ioException = null;
 		try {
 			process = processBuilder.start();
-			final Callable<Void> errorGobbler = new StreamGobbler(
-					process.getErrorStream(), errRedirect);
-			final Callable<Void> outputGobbler = new StreamGobbler(
-					process.getInputStream(), outRedirect);
-			executor.submit(errorGobbler);
-			executor.submit(outputGobbler);
+			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)
-						.call();
+				new StreamGobbler(inRedirect, outputStream).copy();
 			}
 			try {
 				outputStream.close();
@@ -1381,7 +1452,7 @@ public String normalize(String name) {
 	 * streams.
 	 * </p>
 	 */
-	private static class StreamGobbler implements Callable<Void> {
+	private static class StreamGobbler implements Runnable {
 		private InputStream in;
 
 		private OutputStream out;
@@ -1391,7 +1462,16 @@ public StreamGobbler(InputStream stream, OutputStream output) {
 			this.out = output;
 		}
 
-		public Void call() throws IOException {
+		@Override
+		public void run() {
+			try {
+				copy();
+			} catch (IOException e) {
+				// Do nothing on read failure; leave streams open.
+			}
+		}
+
+		void copy() throws IOException {
 			boolean writeFailure = false;
 			byte buffer[] = new byte[4096];
 			int readBytes;
@@ -1408,7 +1488,6 @@ public Void call() throws IOException {
 					}
 				}
 			}
-			return null;
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index f63c437..607e078 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -52,14 +52,19 @@
 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;
@@ -261,7 +266,7 @@ private static void apply(Set<PosixFilePermission> set,
 
 	@Override
 	public ProcessBuilder runInShell(String cmd, String[] args) {
-		List<String> argv = new ArrayList<String>(4 + args.length);
+		List<String> argv = new ArrayList<>(4 + args.length);
 		argv.add("sh"); //$NON-NLS-1$
 		argv.add("-c"); //$NON-NLS-1$
 		argv.add(cmd + " \"$@\""); //$NON-NLS-1$
@@ -346,6 +351,7 @@ public boolean supportsAtomicCreateNewFile() {
 		return supportsAtomicCreateNewFile.booleanValue();
 	}
 
+	@Override
 	@SuppressWarnings("boxing")
 	/**
 	 * An implementation of the File#createNewFile() semantics which works also
@@ -359,9 +365,12 @@ public boolean supportsAtomicCreateNewFile() {
 	 * 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
+	 * @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;
@@ -370,22 +379,94 @@ public boolean createNewFile(File lock) throws IOException {
 			return true;
 		}
 		Path lockPath = lock.toPath();
-		Path link = Files.createLink(Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
-				lockPath);
+		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) {
+			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 {
-			Files.delete(link);
+			if (link != null) {
+				Files.delete(link);
+			}
 		}
 	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * 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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index 0e9172e..0602921 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -83,18 +83,22 @@ protected FS_Win32(FS src) {
 		super(src);
 	}
 
+	@Override
 	public FS newInstance() {
 		return new FS_Win32(this);
 	}
 
+	@Override
 	public boolean supportsExecute() {
 		return false;
 	}
 
+	@Override
 	public boolean canExecute(final File f) {
 		return false;
 	}
 
+	@Override
 	public boolean setExecute(final File f, final boolean canExec) {
 		return false;
 	}
@@ -158,7 +162,7 @@ protected File userHomeImpl() {
 
 	@Override
 	public ProcessBuilder runInShell(String cmd, String[] args) {
-		List<String> argv = new ArrayList<String>(3 + args.length);
+		List<String> argv = new ArrayList<>(3 + args.length);
 		argv.add("cmd.exe"); //$NON-NLS-1$
 		argv.add("/c"); //$NON-NLS-1$
 		argv.add(cmd);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index f8ea5d0..545cc01 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -77,6 +77,7 @@ public class FS_Win32_Cygwin extends FS_Win32 {
 	public static boolean isCygwin() {
 		final String path = AccessController
 				.doPrivileged(new PrivilegedAction<String>() {
+					@Override
 					public String run() {
 						return System.getProperty("java.library.path"); //$NON-NLS-1$
 					}
@@ -106,10 +107,12 @@ protected FS_Win32_Cygwin(FS src) {
 		super(src);
 	}
 
+	@Override
 	public FS newInstance() {
 		return new FS_Win32_Cygwin(this);
 	}
 
+	@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$
@@ -133,6 +136,7 @@ public File resolve(final File dir, final String pn) {
 	protected File userHomeImpl() {
 		final String home = AccessController
 				.doPrivileged(new PrivilegedAction<String>() {
+					@Override
 					public String run() {
 						return System.getenv("HOME"); //$NON-NLS-1$
 					}
@@ -144,7 +148,7 @@ public String run() {
 
 	@Override
 	public ProcessBuilder runInShell(String cmd, String[] args) {
-		List<String> argv = new ArrayList<String>(4 + args.length);
+		List<String> 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$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 88b1f9e..31cfe42 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -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;
@@ -150,8 +151,8 @@ public static void delete(final File f, int options) throws IOException {
 		if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
 			final File[] items = f.listFiles();
 			if (items != null) {
-				List<File> files = new ArrayList<File>();
-				List<File> dirs = new ArrayList<File>();
+				List<File> files = new ArrayList<>();
+				List<File> dirs = new ArrayList<>();
 				for (File c : items)
 					if (c.isFile())
 						files.add(c);
@@ -542,7 +543,28 @@ else if (!ignoreCase
 	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
+	 * @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;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
index 7bc3c88..da78008 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
@@ -75,8 +75,9 @@ public class GitDateParser {
 	private static ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>> formatCache =
 			new ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>>() {
 
+		@Override
 		protected Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> initialValue() {
-			return new HashMap<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>();
+			return new HashMap<>();
 		}
 	};
 
@@ -90,7 +91,7 @@ private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f,
 		Map<ParseableSimpleDateFormat, SimpleDateFormat> map = cache
 				.get(locale);
 		if (map == null) {
-			map = new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>();
+			map = new HashMap<>();
 			cache.put(locale, map);
 			return getNewSimpleDateFormat(f, locale, map);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 251381a..13e61a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -141,6 +141,12 @@ public class HttpSupport {
 	/** 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$
 
@@ -235,6 +241,23 @@ public static int response(final java.net.HttpURLConnection c)
 	}
 
 	/**
+	 * 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 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
@@ -280,15 +303,18 @@ public static void disableSslVerify(HttpConnection conn)
 	}
 
 	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
@@ -296,6 +322,7 @@ public void checkServerTrusted(X509Certificate[] certs,
 	}
 
 	private static class DummyHostnameVerifier implements HostnameVerifier {
+		@Override
 		public boolean verify(String hostname, SSLSession session) {
 			// always accept
 			return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
index 0d283fd..6cff76c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
@@ -344,7 +344,7 @@ public static void skipFully(final InputStream fd, long toSkip)
 	 * @since 2.0
 	 */
 	public static List<String> readLines(final String s) {
-		List<String> l = new ArrayList<String>();
+		List<String> l = new ArrayList<>();
 		StringBuilder sb = new StringBuilder();
 		for (int i = 0; i < s.length(); i++) {
 			char c = s.charAt(i);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
index f7688e3..658dd06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
@@ -138,6 +138,7 @@ private void grow() {
 		entries = n;
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder r = new StringBuilder();
 		r.append('[');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
index dc4004f..e3639f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
@@ -159,6 +159,7 @@ private void grow() {
 		entries = n;
 	}
 
+	@Override
 	public String toString() {
 		final StringBuilder r = new StringBuilder();
 		r.append('[');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
index 4eeecdb..e85bd65 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
@@ -74,14 +74,17 @@ public RawCharSequence(final byte[] buf, final int start, final int end) {
 		endPtr = end;
 	}
 
+	@Override
 	public char charAt(final int index) {
 		return (char) (buffer[startPtr + index] & 0xff);
 	}
 
+	@Override
 	public int length() {
 		return endPtr - startPtr;
 	}
 
+	@Override
 	public CharSequence subSequence(final int start, final int end) {
 		return new RawCharSequence(buffer, startPtr + start, startPtr + end);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
index 0853e95..1597817 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -68,7 +68,7 @@
  *            the type of reference being stored in the collection.
  */
 public class RefList<T extends Ref> implements Iterable<Ref> {
-	private static final RefList<Ref> EMPTY = new RefList<Ref>(new Ref[0], 0);
+	private static final RefList<Ref> EMPTY = new RefList<>(new Ref[0], 0);
 
 	/**
 	 * @return an empty unmodifiable reference list.
@@ -100,20 +100,24 @@ protected RefList(RefList<T> src) {
 		this.cnt = src.cnt;
 	}
 
+	@Override
 	public Iterator<Ref> iterator() {
 		return new Iterator<Ref>() {
 			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();
 			}
@@ -209,7 +213,7 @@ public final T get(int idx) {
 	 * @return a new builder with the first {@code n} elements already added.
 	 */
 	public final Builder<T> copy(int n) {
-		Builder<T> r = new Builder<T>(Math.max(16, n));
+		Builder<T> r = new Builder<>(Math.max(16, n));
 		r.addAll(list, 0, n);
 		return r;
 	}
@@ -230,7 +234,7 @@ public final RefList<T> set(int idx, T ref) {
 		Ref[] newList = new Ref[cnt];
 		System.arraycopy(list, 0, newList, 0, cnt);
 		newList[idx] = ref;
-		return new RefList<T>(newList, cnt);
+		return new RefList<>(newList, cnt);
 	}
 
 	/**
@@ -257,7 +261,7 @@ public final RefList<T> add(int idx, T ref) {
 		newList[idx] = ref;
 		if (idx < cnt)
 			System.arraycopy(list, idx, newList, idx + 1, cnt - idx);
-		return new RefList<T>(newList, cnt + 1);
+		return new RefList<>(newList, cnt + 1);
 	}
 
 	/**
@@ -278,7 +282,7 @@ public final RefList<T> remove(int idx) {
 			System.arraycopy(list, 0, newList, 0, idx);
 		if (idx + 1 < cnt)
 			System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1));
-		return new RefList<T>(newList, cnt - 1);
+		return new RefList<>(newList, cnt - 1);
 	}
 
 	/**
@@ -427,7 +431,7 @@ public void sort() {
 
 		/** @return an unmodifiable list using this collection's backing array. */
 		public RefList<T> toRefList() {
-			return new RefList<T>(list, size);
+			return new RefList<>(list, size);
 		}
 
 		@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
index c72727b..510e818 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -304,12 +304,14 @@ private class SetIterator implements Iterator<Entry<String, Ref>> {
 			}
 		}
 
+		@Override
 		public boolean hasNext() {
 			if (next == null)
 				next = peek();
 			return next != null;
 		}
 
+		@Override
 		public Entry<String, Ref> next() {
 			if (hasNext()) {
 				Entry<String, Ref> r = next;
@@ -367,6 +369,7 @@ private Ent toEntry(Ref p) {
 			return null;
 		}
 
+		@Override
 		public void remove() {
 			throw new UnsupportedOperationException();
 		}
@@ -379,14 +382,17 @@ private class Ent implements Entry<String, Ref> {
 			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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index b36fc23..2cd8035 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -87,22 +87,27 @@ public abstract class SystemReader {
 	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;
@@ -112,11 +117,13 @@ public boolean isOutdated() {
 			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 {
@@ -331,6 +338,7 @@ public boolean isMacOS() {
 
 	private String getOsName() {
 		return AccessController.doPrivileged(new PrivilegedAction<String>() {
+			@Override
 			public String run() {
 				return getProperty("os.name"); //$NON-NLS-1$
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
index 57bcfbd..e3f1916 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -325,7 +325,7 @@ public void reset() {
 		if (blocks != null)
 			blocks.clear();
 		else
-			blocks = new ArrayList<Block>(initialBlocks);
+			blocks = new ArrayList<>(initialBlocks);
 		blocks.add(new Block(Math.min(inCoreLimit, Block.SZ)));
 	}
 
@@ -363,6 +363,7 @@ private void switchToOverflow() throws IOException {
 		overflow.write(last.buffer, 0, last.count);
 	}
 
+	@Override
 	public void close() throws IOException {
 		if (overflow != null) {
 			try {
@@ -441,11 +442,13 @@ public LocalFile(final File directory, final int inCoreLimit) {
 			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();
@@ -453,6 +456,7 @@ public long length() {
 			return onDiskFile.length();
 		}
 
+		@Override
 		public byte[] toByteArray() throws IOException {
 			if (onDiskFile == null) {
 				return super.toByteArray();
@@ -471,6 +475,7 @@ public long length() {
 			return out;
 		}
 
+		@Override
 		public void writeTo(final OutputStream os, ProgressMonitor pm)
 				throws IOException {
 			if (onDiskFile == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
index 1024380..0c5edb0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
@@ -176,6 +176,7 @@ static final class AlarmState implements Runnable {
 			callingThread = Thread.currentThread();
 		}
 
+		@Override
 		public synchronized void run() {
 			while (!terminated && callingThread.isAlive()) {
 				try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
index 6adadbb..a7a0347 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
@@ -115,6 +115,7 @@ public int read(byte[] b, int off, int len) throws IOException {
 		return n;
 	}
 
+	@Override
 	public void close() throws IOException {
 		byte[] b = skipBuffer();
 		for (;;) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
index c34c1fb..5eca0d9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
@@ -69,6 +69,7 @@ public class ThrowingPrintWriter extends Writer {
 	public ThrowingPrintWriter(Writer out) {
 		this.out = out;
 		LF = AccessController.doPrivileged(new PrivilegedAction<String>() {
+			@Override
 			public String run() {
 				return SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
index 0319afd..1c46310 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
@@ -67,7 +67,7 @@ public int read() throws IOException {
 		}
 	};
 
-	private final LinkedList<InputStream> streams = new LinkedList<InputStream>();
+	private final LinkedList<InputStream> streams = new LinkedList<>();
 
 	/** Create an empty InputStream that is currently at EOF state. */
 	public UnionInputStream() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java
new file mode 100644
index 0000000..5449711
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java
@@ -0,0 +1,601 @@
+/*
+ * 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.
+ *
+ * <p>
+ * See <a href="https://tools.ietf.org/html/rfc3174">RFC 3174</a>.
+ * <p>
+ * 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 <a href="https://shattered.it/">SHAttered</a>. See
+ * <a href="https://github.com/cr-marcstevens/sha1collisiondetection">
+ * sha1collisiondetection</a> for more information.
+ * <p>
+ * When detectCollision is true (default), this implementation throws
+ * {@link 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;
+	}
+
+	/** @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.
+	 * <p>
+	 * Default is {@code true}.
+	 * <p>
+	 * May also be set by system property:
+	 * {@code -Dorg.eclipse.jgit.util.sha1.detectCollision=true}.
+	 *
+	 * @param detect
+	 * @return {@code this}
+	 */
+	public SHA1 setDetectCollision(boolean detect) {
+		detectCollision = detect;
+		return this;
+	}
+
+	/**
+	 * Update the digest computation by adding a byte.
+	 *
+	 * @param b
+	 */
+	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.
+	 * <p>
+	 * Once {@code digest()} is called, this instance should be discarded.
+	 *
+	 * @return the bytes for the resulting hash.
+	 * @throws 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.
+	 * <p>
+	 * Once {@code digest()} is called, this instance should be discarded.
+	 *
+	 * @return the ObjectId for the resulting hash.
+	 * @throws 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.
+	 * <p>
+	 * Once {@code digest()} is called, this instance should be discarded.
+	 *
+	 * @param id
+	 *            destination to copy the digest to.
+	 * @throws 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.
+	 *
+	 * <p>
+	 * 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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java
new file mode 100644
index 0000000..be126a5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java
@@ -0,0 +1,70 @@
+/*
+ * 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 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 --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java
new file mode 100644
index 0000000..cebdbee
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java
@@ -0,0 +1,1040 @@
+/*
+* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+* 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 --git a/pom.xml b/pom.xml
index 588f364..c0b9c5e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,7 +51,7 @@
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>org.eclipse.jgit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>4.6.2-SNAPSHOT</version>
+  <version>4.7.8-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -191,8 +191,8 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>4.2.0.201601211800-r</jgit-last-release-version>
-    <jsch-version>0.1.53</jsch-version>
+    <jgit-last-release-version>4.6.0.201612231935-r</jgit-last-release-version>
+    <jsch-version>0.1.54</jsch-version>
     <javaewah-version>1.1.6</javaewah-version>
     <junit-version>4.12</junit-version>
     <test-fork-count>1C</test-fork-count>
@@ -259,7 +259,7 @@
 
         <plugin>
           <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.6.0</version>
+          <version>3.6.1</version>
           <configuration>
             <encoding>UTF-8</encoding>
             <source>1.8</source>
@@ -309,7 +309,7 @@
             <dependency>
               <groupId>com.google.errorprone</groupId>
               <artifactId>error_prone_core</artifactId>
-              <version>2.0.15</version>
+              <version>2.0.19</version>
             </dependency>
           </dependencies>
         </plugin>
@@ -322,7 +322,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>2.4.3</version>
+          <version>3.0.0</version>
         </plugin>
 
         <plugin>
@@ -362,7 +362,7 @@
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>build-helper-maven-plugin</artifactId>
-          <version>1.12</version>
+          <version>3.0.0</version>
         </plugin>
 
         <plugin>
@@ -420,7 +420,7 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.7.8</version>
+          <version>0.7.9</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
diff --git a/tools/BUILD b/tools/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/BUILD
diff --git a/tools/bazlets.bzl b/tools/bazlets.bzl
new file mode 100644
index 0000000..f97b72c
--- /dev/null
+++ b/tools/bazlets.bzl
@@ -0,0 +1,16 @@
+NAME = "com_googlesource_gerrit_bazlets"
+
+def load_bazlets(
+        commit,
+        local_path = None):
+    if not local_path:
+        native.git_repository(
+            name = NAME,
+            remote = "https://gerrit.googlesource.com/bazlets",
+            commit = commit,
+        )
+    else:
+        native.local_repository(
+            name = NAME,
+            path = local_path,
+        )