Merge "Update replication plugin" into stable-2.10
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 4ad7d21..3368db9 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -706,14 +706,24 @@
 
 ==== [[cache_options]]Cache Options
 
-[[cache.diff_intraline.maxIdleWorkers]]cache.diff_intraline.maxIdleWorkers::
+[[cache.diff.timeout]]cache.diff.timeout::
 +
-Number of idle worker threads to maintain for the intraline difference
-computations.  There is no upper bound on how many concurrent requests
-can occur at once, if additional threads are started to handle a peak
-load, only this many will remain idle afterwards.
+Maximum number of milliseconds to wait for diff data before giving up and
+falling back on a simpler diff algorithm that will not be able to break down
+modified regions into smaller ones. This is a work around for an infinite loop
+bug in the default difference algorithm implementation.
 +
-Default is 1.5x number of available CPUs.
+Values should use common unit suffixes to express their setting:
++
+* ms, milliseconds
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+
++
+If a unit suffix is not specified, `milliseconds` is assumed.
++
+Default is 5 seconds.
 
 [[cache.diff_intraline.timeout]]cache.diff_intraline.timeout::
 +
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
index 8375e31..9be2630 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
@@ -23,29 +23,20 @@
 public interface OAuthServiceProvider {
 
   /**
-   * Retrieve the request token.
-   *
-   * @return request token
-   */
-  OAuthToken getRequestToken();
-
-  /**
    * Returns the URL where you should redirect your users to authenticate
    * your application.
    *
-   * @param requestToken the request token you need to authorize
-   * @return the URL where you should redirect your users
+   * @return the OAuth service URL to redirect your users for authentication
    */
-  String getAuthorizationUrl(OAuthToken requestToken);
+  String getAuthorizationUrl();
 
   /**
    * Retrieve the access token
    *
-   * @param requestToken request token (obtained previously)
    * @param verifier verifier code
    * @return access token
    */
-  OAuthToken getAccessToken(OAuthToken requestToken, OAuthVerifier verifier);
+  OAuthToken getAccessToken(OAuthVerifier verifier);
 
   /**
    * After establishing of secure communication channel, this method supossed to
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java
index 23a7bec..388ce36 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java
@@ -20,15 +20,18 @@
   private final String userName;
   private final String emailAddress;
   private final String displayName;
+  private final String claimedIdentity;
 
   public OAuthUserInfo(String externalId,
       String userName,
       String emailAddress,
-      String displayName) {
+      String displayName,
+      String claimedIdentity) {
     this.externalId = externalId;
     this.userName = userName;
     this.emailAddress = emailAddress;
     this.displayName = displayName;
+    this.claimedIdentity = claimedIdentity;
   }
 
   public String getExternalId() {
@@ -46,4 +49,8 @@
   public String getDisplayName() {
     return displayName;
   }
+
+  public String getClaimedIdentity() {
+    return claimedIdentity;
+  }
 }
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
index d3dc963..41c83eb 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
@@ -79,7 +79,9 @@
     ChangeBatchIndexer.Result result = batchIndexer.indexAll(
         index, projectCache.all(), -1, -1, null, null);
     if (!result.success()) {
-      log.error("Online reindex of schema version {} failed", version(index));
+      log.error("Online reindex of schema version {} failed. Successfully"
+          + " indexed {} changes, failed to index {} changes",
+          version(index), result.doneCount(), result.failedCount());
       return;
     }
 
diff --git a/gerrit-oauth/BUCK b/gerrit-oauth/BUCK
index 4641e81..fa5a8e2 100644
--- a/gerrit-oauth/BUCK
+++ b/gerrit-oauth/BUCK
@@ -11,9 +11,11 @@
     '//gerrit-common:annotations',
     '//gerrit-extension-api:api',
     '//gerrit-httpd:httpd',
+    '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//lib:gson',
     '//lib:guava',
+    '//lib:gwtorm',
     '//lib/commons:codec',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index d625e02..8ffbbe6 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -23,9 +23,11 @@
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AuthResult;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.servlet.SessionScoped;
 
@@ -87,8 +89,7 @@
       }
 
       log.debug("Login-Retrieve-User " + this);
-      token = oauth.getAccessToken(null,
-          new OAuthVerifier(request.getParameter("code")));
+      token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
 
       user = oauth.getUserInfo(token);
 
@@ -103,7 +104,7 @@
     } else {
       log.debug("Login-PHASE1 " + this);
       redirectUrl = request.getRequestURI();
-      response.sendRedirect(oauth.getAuthorizationUrl(null) +
+      response.sendRedirect(oauth.getAuthorizationUrl() +
           "&state=" + state);
       return false;
     }
@@ -113,11 +114,48 @@
       throws IOException {
     com.google.gerrit.server.account.AuthRequest areq =
         new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
-    areq.setUserName(user.getUserName());
-    areq.setEmailAddress(user.getEmailAddress());
-    areq.setDisplayName(user.getDisplayName());
     AuthResult arsp;
     try {
+      String claimedIdentifier = user.getClaimedIdentity();
+      Account.Id actualId = accountManager.lookup(user.getExternalId());
+      if (!Strings.isNullOrEmpty(claimedIdentifier)) {
+        Account.Id claimedId = accountManager.lookup(claimedIdentifier);
+        if (claimedId != null && actualId != null) {
+          if (claimedId.equals(actualId)) {
+            // Both link to the same account, that's what we expected.
+            log.debug("OAuth2: claimed identity equals current id");
+          } else {
+            // This is (for now) a fatal error. There are two records
+            // for what might be the same user.
+            //
+            log.error("OAuth accounts disagree over user identity:\n"
+                + "  Claimed ID: " + claimedId + " is " + claimedIdentifier
+                + "\n" + "  Delgate ID: " + actualId + " is "
+                + user.getExternalId());
+            rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+          }
+        } else if (claimedId != null && actualId == null) {
+          // Claimed account already exists: link to it.
+          //
+          log.info("OAuth2: linking claimed identity to {}",
+              claimedId.toString());
+          try {
+            accountManager.link(claimedId, areq);
+          } catch (OrmException e) {
+            log.error("Cannot link: " +  user.getExternalId()
+                + " to user identity:\n"
+                + "  Claimed ID: " + claimedId + " is " + claimedIdentifier);
+            rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+          }
+        }
+      } else {
+        log.debug("OAuth2: claimed identity is empty");
+      }
+      areq.setUserName(user.getUserName());
+      areq.setEmailAddress(user.getEmailAddress());
+      areq.setDisplayName(user.getDisplayName());
       arsp = accountManager.authenticate(areq);
     } catch (AccountException e) {
       log.error("Unable to authenticate user \"" + user + "\"", e);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 1681bc2..44b58c3 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -157,6 +157,7 @@
     final AuthRequest aReq;
     try {
       aReq = manager.authenticate(state.discovered, state.retTo.toString());
+      log.debug("OpenID: openid-realm={}", state.contextUrl);
       aReq.setRealm(state.contextUrl);
 
       if (requestRegistration(aReq)) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index e28b5ba..1c667e3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -63,7 +63,7 @@
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
-import com.google.gerrit.server.patch.IntraLineWorkerPool;
+import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginRestApiModule;
 import com.google.gerrit.server.schema.DataSourceProvider;
@@ -320,7 +320,7 @@
     modules.add(new ChangeHookRunner.Module());
     modules.add(new ReceiveCommitsExecutorModule());
     modules.add(new MergeabilityChecksExecutorModule());
-    modules.add(new IntraLineWorkerPool.Module());
+    modules.add(new DiffExecutorModule());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new InternalAccountDirectory.Module());
     modules.add(new DefaultCacheFactory.Module());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index c0cf8f9..161efc1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -70,6 +70,7 @@
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.CommentLinkInfo;
@@ -191,6 +192,7 @@
 
   private Injector createSysInjector() {
     List<Module> modules = Lists.newArrayList();
+    modules.add(new DiffExecutorModule());
     modules.add(PatchListCacheImpl.module());
     AbstractModule changeIndexModule;
     switch (IndexModule.getIndexType(dbInjector)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 4b8f0b4..938d940 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -40,8 +40,7 @@
 
   @Override
   public boolean allowsEdit(final Account.FieldName field) {
-    if (authConfig.getAuthType() == AuthType.HTTP
-        || authConfig.getAuthType() == AuthType.OAUTH) {
+    if (authConfig.getAuthType() == AuthType.HTTP) {
       switch (field) {
         case USER_NAME:
           return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index b35d7e4..709a8ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -52,6 +52,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.MergeStrategy;
 import org.eclipse.jgit.merge.Merger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.revwalk.FooterLine;
@@ -89,6 +90,12 @@
     return cfg.getBoolean("core", null, "useRecursiveMerge", true);
   }
 
+  public static ThreeWayMergeStrategy getMergeStrategy(Config cfg) {
+    return useRecursiveMerge(cfg)
+        ? MergeStrategy.RECURSIVE
+        : MergeStrategy.RESOLVE;
+  }
+
   public static interface Factory {
     MergeUtil create(ProjectState project);
     MergeUtil create(ProjectState project, boolean useContentMerge);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
index 5ee240b..be87ea0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -32,7 +32,9 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.MergeabilityChecker;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.MultiProgressMonitor;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
 import com.google.gerrit.server.patch.PatchListLoader;
@@ -43,6 +45,7 @@
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
@@ -50,6 +53,7 @@
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -112,6 +116,7 @@
   private final ListeningExecutorService executor;
   private final ChangeIndexer.Factory indexerFactory;
   private final MergeabilityChecker mergeabilityChecker;
+  private final ThreeWayMergeStrategy mergeStrategy;
 
   @Inject
   ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
@@ -119,6 +124,7 @@
       GitRepositoryManager repoManager,
       @IndexExecutor ListeningExecutorService executor,
       ChangeIndexer.Factory indexerFactory,
+      @GerritServerConfig Config config,
       @Nullable MergeabilityChecker mergeabilityChecker) {
     this.schemaFactory = schemaFactory;
     this.changeDataFactory = changeDataFactory;
@@ -126,6 +132,7 @@
     this.executor = executor;
     this.indexerFactory = indexerFactory;
     this.mergeabilityChecker = mergeabilityChecker;
+    this.mergeStrategy = MergeUtil.getMergeStrategy(config);
   }
 
   public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
@@ -150,9 +157,7 @@
     final AtomicBoolean ok = new AtomicBoolean(true);
 
     for (final Project.NameKey project : projects) {
-      if (!updateMergeable(project)) {
-        ok.set(false);
-      }
+      updateMergeable(project);
       final ListenableFuture<?> future = executor.submit(reindexProject(
           indexerFactory.create(index), project, doneTask, failedTask,
           verboseWriter));
@@ -212,7 +217,7 @@
     if (mergeabilityChecker != null) {
       try {
         mergeabilityChecker.newCheck().addProject(project).run();
-      } catch (IOException e) {
+      } catch (Exception e) {
         log.error("Error in mergeability checker", e);
         return false;
       }
@@ -239,8 +244,13 @@
               byId.put(r.getObjectId(), changeDataFactory.create(db, c));
             }
           }
-          new ProjectIndexer(indexer, byId, repo, done, failed, verboseWriter)
-              .call();
+          new ProjectIndexer(indexer,
+              mergeStrategy,
+              byId,
+              repo,
+              done,
+              failed,
+              verboseWriter).call();
         } catch (RepositoryNotFoundException rnfe) {
           log.error(rnfe.getMessage());
         } finally {
@@ -261,6 +271,7 @@
 
   public static class ProjectIndexer implements Callable<Void> {
     private final ChangeIndexer indexer;
+    private final ThreeWayMergeStrategy mergeStrategy;
     private final Multimap<ObjectId, ChangeData> byId;
     private final ProgressMonitor done;
     private final ProgressMonitor failed;
@@ -268,16 +279,15 @@
     private final Repository repo;
     private RevWalk walk;
 
-    public ProjectIndexer(ChangeIndexer indexer,
-        Multimap<ObjectId, ChangeData> changesByCommitId, Repository repo) {
-      this(indexer, changesByCommitId, repo,
-          NullProgressMonitor.INSTANCE, NullProgressMonitor.INSTANCE, null);
-    }
-
-    ProjectIndexer(ChangeIndexer indexer,
-        Multimap<ObjectId, ChangeData> changesByCommitId, Repository repo,
-        ProgressMonitor done, ProgressMonitor failed, PrintWriter verboseWriter) {
+    private ProjectIndexer(ChangeIndexer indexer,
+        ThreeWayMergeStrategy mergeStrategy,
+        Multimap<ObjectId, ChangeData> changesByCommitId,
+        Repository repo,
+        ProgressMonitor done,
+        ProgressMonitor failed,
+        PrintWriter verboseWriter) {
       this.indexer = indexer;
+      this.mergeStrategy = mergeStrategy;
       this.byId = changesByCommitId;
       this.repo = repo;
       this.done = done;
@@ -376,7 +386,7 @@
           walk.parseBody(a);
           return walk.parseTree(a.getTree());
         case 2:
-          return PatchListLoader.automerge(repo, walk, b);
+          return PatchListLoader.automerge(repo, walk, b, mergeStrategy);
         default:
           return null;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutor.java
new file mode 100644
index 0000000..564ca58
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutor.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.patch;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Marker on {@link ExecutorService} used by
+ * {@link IntraLineLoader} and {@link PatchListLoader}.
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface DiffExecutor {
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutorModule.java
new file mode 100644
index 0000000..9eaea3a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/DiffExecutorModule.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.patch;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** Module providing the {@link DiffExecutor}. */
+public class DiffExecutorModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+  }
+
+  @Provides
+  @Singleton
+  @DiffExecutor
+  public ExecutorService createDiffExecutor() {
+    return Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+        .setNameFormat("Diff-%d").setDaemon(true).build());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
index 4961bef..009b492 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -15,6 +15,7 @@
 
 package com.google.gerrit.server.patch;
 
+import com.google.common.base.Throwables;
 import com.google.common.cache.CacheLoader;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -29,7 +30,12 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.regex.Pattern;
 
 class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
@@ -41,12 +47,13 @@
   private static final Pattern CONTROL_BLOCK_START_RE = Pattern
       .compile("[{:][ \\t]*$");
 
-  private final IntraLineWorkerPool workerPool;
+  private final ExecutorService diffExecutor;
   private final long timeoutMillis;
 
   @Inject
-  IntraLineLoader(IntraLineWorkerPool pool, @GerritServerConfig Config cfg) {
-    workerPool = pool;
+  IntraLineLoader(@DiffExecutor ExecutorService diffExecutor,
+      @GerritServerConfig Config cfg) {
+    this.diffExecutor = diffExecutor;
     timeoutMillis =
         ConfigUtil.getTimeUnit(cfg, "cache", PatchListCacheImpl.INTRA_NAME,
             "timeout", TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
@@ -54,27 +61,30 @@
   }
 
   @Override
-  public IntraLineDiff load(IntraLineDiffKey key) throws Exception {
-    IntraLineWorkerPool.Worker w = workerPool.acquire();
-    IntraLineWorkerPool.Worker.Result r = w.computeWithTimeout(key, timeoutMillis);
-
-    if (r == IntraLineWorkerPool.Worker.Result.TIMEOUT) {
-      // Don't keep this thread. We have to murder it unsafely, which
-      // means its unable to be reused in the future. Return back a
-      // null result, indicating the cache cannot load this key.
-      //
+  public IntraLineDiff load(final IntraLineDiffKey key) throws Exception {
+    Future<IntraLineDiff> result = diffExecutor.submit(new Callable<IntraLineDiff>() {
+      @Override
+      public IntraLineDiff call() throws Exception {
+        return IntraLineLoader.compute(key);
+      }
+    });
+    try {
+      return result.get(timeoutMillis, TimeUnit.MILLISECONDS);
+    } catch (InterruptedException | TimeoutException e) {
+      log.warn(timeoutMillis + " ms timeout reached for IntraLineDiff"
+          + " in project " + key.getProject().get()
+          + " on commit " + key.getCommit().name()
+          + " for path " + key.getPath()
+          + " comparing " + key.getBlobA().name()
+          + ".." + key.getBlobB().name());
+      result.cancel(true);
       return new IntraLineDiff(IntraLineDiff.Status.TIMEOUT);
-    }
-    workerPool.release(w);
-
-    if (r.error != null) {
+    } catch (ExecutionException e) {
       // If there was an error computing the result, carry it
       // up to the caller so the cache knows this key is invalid.
-      //
-      throw r.error;
+      Throwables.propagateIfInstanceOf(e.getCause(), Exception.class);
+      throw new Exception(e.getMessage(), e.getCause());
     }
-
-    return r.diff;
   }
 
   static IntraLineDiff compute(IntraLineDiffKey key) throws Exception {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWorkerPool.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWorkerPool.java
deleted file mode 100644
index 49ae950..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWorkerPool.java
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package com.google.gerrit.server.patch;
-
-import static com.google.gerrit.server.patch.IntraLineLoader.log;
-
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.Config;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@Singleton
-public class IntraLineWorkerPool {
-  public static class Module extends AbstractModule {
-    @Override
-    protected void configure() {
-      bind(IntraLineWorkerPool.class);
-    }
-  }
-
-  private final BlockingQueue<Worker> workerPool;
-
-  @Inject
-  public IntraLineWorkerPool(@GerritServerConfig Config cfg) {
-    int workers = cfg.getInt(
-        "cache", PatchListCacheImpl.INTRA_NAME, "maxIdleWorkers",
-        Runtime.getRuntime().availableProcessors() * 3 / 2);
-    workerPool = new ArrayBlockingQueue<>(workers, true /* fair */);
-  }
-
-  Worker acquire() {
-    Worker w = workerPool.poll();
-    if (w == null) {
-      // If no worker is immediately available, start a new one.
-      // Maximum parallelism is controlled by the web server.
-      w = new Worker();
-      w.start();
-    }
-    return w;
-  }
-
-  void release(Worker w) {
-    if (!workerPool.offer(w)) {
-      // If the idle worker pool is full, terminate the worker.
-      w.shutdownGracefully();
-    }
-  }
-
-  static class Worker extends Thread {
-    private static final AtomicInteger count = new AtomicInteger(1);
-
-    private final ArrayBlockingQueue<Input> input;
-    private final ArrayBlockingQueue<Result> result;
-
-    Worker() {
-      input = new ArrayBlockingQueue<>(1);
-      result = new ArrayBlockingQueue<>(1);
-
-      setName("IntraLineDiff-" + count.getAndIncrement());
-      setDaemon(true);
-    }
-
-    Result computeWithTimeout(IntraLineDiffKey key, long timeoutMillis)
-        throws Exception {
-      if (!input.offer(new Input(key))) {
-        log.error("Cannot enqueue task to thread " + getName());
-        return Result.TIMEOUT;
-      }
-
-      Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);
-      if (r != null) {
-        return r;
-      } else {
-        log.warn(timeoutMillis + " ms timeout reached for IntraLineDiff"
-            + " in project " + key.getProject().get()
-            + " on commit " + key.getCommit().name()
-            + " for path " + key.getPath()
-            + " comparing " + key.getBlobA().name()
-            + ".." + key.getBlobB().name()
-            + ".  Killing " + getName());
-        forcefullyKillThreadInAnUglyWay();
-        return Result.TIMEOUT;
-      }
-    }
-
-    @SuppressWarnings("deprecation")
-    private void forcefullyKillThreadInAnUglyWay() {
-      try {
-        stop();
-      } catch (Throwable error) {
-        // Ignore any reason the thread won't stop.
-        log.error("Cannot stop runaway thread " + getName(), error);
-      }
-    }
-
-    private void shutdownGracefully() {
-      if (!input.offer(Input.END_THREAD)) {
-        log.error("Cannot gracefully stop thread " + getName());
-      }
-    }
-
-    @Override
-    public void run() {
-      try {
-        for (;;) {
-          Input in;
-          try {
-            in = input.take();
-          } catch (InterruptedException e) {
-            log.error("Unexpected interrupt on " + getName());
-            continue;
-          }
-
-          if (in == Input.END_THREAD) {
-            return;
-          }
-
-          Result r;
-          try {
-            r = new Result(IntraLineLoader.compute(in.key));
-          } catch (Exception error) {
-            r = new Result(error);
-          }
-
-          if (!result.offer(r)) {
-            log.error("Cannot return result from " + getName());
-          }
-        }
-      } catch (ThreadDeath iHaveBeenShot) {
-        // Handle thread death by gracefully returning to the caller,
-        // allowing the thread to be destroyed.
-      }
-    }
-
-    private static class Input {
-      static final Input END_THREAD = new Input(null);
-
-      final IntraLineDiffKey key;
-
-      Input(IntraLineDiffKey key) {
-        this.key = key;
-      }
-    }
-
-    static class Result {
-      static final Result TIMEOUT = new Result((IntraLineDiff) null);
-
-      final IntraLineDiff diff;
-      final Exception error;
-
-      Result(IntraLineDiff diff) {
-        this.diff = diff;
-        this.error = null;
-      }
-
-      Result(Exception error) {
-        this.diff = null;
-        this.error = error;
-      }
-    }
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 7b7c731..6c769f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -35,7 +35,7 @@
 /** Provides a cached list of {@link PatchListEntry}. */
 @Singleton
 public class PatchListCacheImpl implements PatchListCache {
-  private static final String FILE_NAME = "diff";
+  static final String FILE_NAME = "diff";
   static final String INTRA_NAME = "diff_intraline";
 
   public static Module module() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 4eede00..d9e7969 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -16,12 +16,16 @@
 package com.google.gerrit.server.patch;
 
 import com.google.common.base.Function;
+import com.google.common.base.Throwables;
 import com.google.common.cache.CacheLoader;
 import com.google.common.collect.FluentIterable;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.diff.DiffEntry;
@@ -35,6 +39,7 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -45,8 +50,8 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.MergeFormatter;
 import org.eclipse.jgit.merge.MergeResult;
-import org.eclipse.jgit.merge.MergeStrategy;
 import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.patch.FileHeader;
 import org.eclipse.jgit.patch.FileHeader.PatchType;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -66,17 +71,36 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
   static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
 
   private final GitRepositoryManager repoManager;
   private final PatchListCache patchListCache;
+  private final ThreeWayMergeStrategy mergeStrategy;
+  private final ExecutorService diffExecutor;
+  private final long timeoutMillis;
+
 
   @Inject
-  PatchListLoader(GitRepositoryManager mgr, PatchListCache plc) {
+  PatchListLoader(GitRepositoryManager mgr,
+      PatchListCache plc,
+      @GerritServerConfig Config cfg,
+      @DiffExecutor ExecutorService de) {
     repoManager = mgr;
     patchListCache = plc;
+    mergeStrategy = MergeUtil.getMergeStrategy(cfg);
+    diffExecutor = de;
+    timeoutMillis =
+        ConfigUtil.getTimeUnit(cfg, "cache", PatchListCacheImpl.FILE_NAME,
+            "timeout", TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
+            TimeUnit.MILLISECONDS);
   }
 
   @Override
@@ -158,7 +182,7 @@
         DiffEntry diffEntry = diffEntries.get(i);
         if (paths == null || paths.contains(diffEntry.getNewPath())
             || paths.contains(diffEntry.getOldPath())) {
-          FileHeader fh = df.toFileHeader(diffEntry);
+          FileHeader fh = toFileHeader(key, df, diffEntry);
           entries.add(newEntry(aTree, fh));
         }
       }
@@ -169,6 +193,44 @@
     }
   }
 
+  private FileHeader toFileHeader(PatchListKey key,
+      final DiffFormatter diffFormatter, final DiffEntry diffEntry)
+      throws IOException {
+
+    Future<FileHeader> result = diffExecutor.submit(new Callable<FileHeader>() {
+      @Override
+      public FileHeader call() throws IOException {
+        return diffFormatter.toFileHeader(diffEntry);
+      }
+    });
+
+    try {
+      return result.get(timeoutMillis, TimeUnit.MILLISECONDS);
+    } catch (InterruptedException | TimeoutException e) {
+      log.warn(timeoutMillis + " ms timeout reached for Diff loader"
+                      + " in project " + key.projectKey.get()
+                      + " on commit " + key.getNewId().name()
+                      + " on path " + diffEntry.getNewPath()
+                      + " comparing " + diffEntry.getOldId().name()
+                      + ".." + diffEntry.getNewId().name());
+      result.cancel(true);
+      return toFileHeaderWithoutMyersDiff(diffFormatter, diffEntry);
+    } catch (ExecutionException e) {
+      // If there was an error computing the result, carry it
+      // up to the caller so the cache knows this key is invalid.
+      Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
+      throw new IOException(e.getMessage(), e.getCause());
+    }
+  }
+
+  private FileHeader toFileHeaderWithoutMyersDiff(DiffFormatter diffFormatter,
+      DiffEntry diffEntry) throws IOException {
+    HistogramDiff histogramDiff = new HistogramDiff();
+    histogramDiff.setFallbackAlgorithm(null);
+    diffFormatter.setDiffAlgorithm(histogramDiff);
+    return diffFormatter.toFileHeader(diffEntry);
+  }
+
   private PatchListEntry newCommitMessage(final RawTextComparator cmp,
       final Repository db, final ObjectReader reader,
       final RevCommit aCommit, final RevCommit bCommit) throws IOException {
@@ -224,7 +286,7 @@
     }
   }
 
-  private static RevObject aFor(final PatchListKey key,
+  private RevObject aFor(final PatchListKey key,
       final Repository repo, final RevWalk rw, final RevCommit b)
       throws IOException {
     if (key.getOldId() != null) {
@@ -240,20 +302,20 @@
         return r;
       }
       case 2:
-        return automerge(repo, rw, b);
+        return automerge(repo, rw, b, mergeStrategy);
       default:
         // TODO(sop) handle an octopus merge.
         return null;
     }
   }
 
-  public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b)
-      throws IOException {
-    return automerge(repo, rw, b, true);
+  public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
+      ThreeWayMergeStrategy mergeStrategy) throws IOException {
+    return automerge(repo, rw, b, mergeStrategy, true);
   }
 
   public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
-      boolean save) throws IOException {
+      ThreeWayMergeStrategy mergeStrategy, boolean save) throws IOException {
     String hash = b.name();
     String refName = RefNames.REFS_CACHE_AUTOMERGE
         + hash.substring(0, 2)
@@ -264,8 +326,7 @@
       return rw.parseTree(ref.getObjectId());
     }
 
-    ObjectId treeId;
-    ResolveMerger m = (ResolveMerger) MergeStrategy.RESOLVE.newMerger(repo, true);
+    ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(repo, true);
     final ObjectInserter ins = repo.newObjectInserter();
     try {
       DirCache dc = DirCache.newInCore();
@@ -297,6 +358,7 @@
         return null;
       }
 
+      ObjectId treeId;
       if (couldMerge) {
         treeId = m.getResultTreeId();
 
@@ -381,17 +443,18 @@
         treeId = dc.writeTree(ins);
       }
       ins.flush();
+
+      if (save) {
+        RefUpdate update = repo.updateRef(refName);
+        update.setNewObjectId(treeId);
+        update.disableRefLog();
+        update.forceUpdate();
+      }
+
+      return rw.lookupTree(treeId);
     } finally {
       ins.release();
     }
-
-    if (save) {
-      RefUpdate update = repo.updateRef(refName);
-      update.setNewObjectId(treeId);
-      update.disableRefLog();
-      update.forceUpdate();
-    }
-    return rw.parseTree(treeId);
   }
 
   private static ObjectId emptyTree(final Repository repo) throws IOException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 6b2f7c7..d96e861 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -18,6 +18,7 @@
 import static com.google.inject.Scopes.SINGLETON;
 
 import com.google.common.net.InetAddresses;
+import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.DisabledChangeHooks;
 import com.google.gerrit.reviewdb.client.AuthType;
@@ -49,6 +50,7 @@
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.patch.DiffExecutor;
 import com.google.gerrit.server.schema.Current;
 import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.SchemaCreator;
@@ -75,6 +77,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.concurrent.ExecutorService;
 
 public class InMemoryModule extends FactoryModule {
   public static Config newDefaultConfig() {
@@ -157,6 +160,18 @@
         return CanonicalWebUrlProvider.class;
       }
     });
+    //Replacement of DiffExecutorModule to not use thread pool in the tests
+    install(new AbstractModule() {
+      @Override
+      protected void configure() {
+      }
+      @Provides
+      @Singleton
+      @DiffExecutor
+      public ExecutorService createDiffExecutor() {
+        return MoreExecutors.sameThreadExecutor();
+      }
+    });
     install(new DefaultCacheFactory.Module());
     install(new SmtpEmailSender.Module());
     install(new SignedTokenEmailTokenVerifier.Module());
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index e9bd296..f59401c 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -46,7 +46,7 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
-import com.google.gerrit.server.patch.IntraLineWorkerPool;
+import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginRestApiModule;
 import com.google.gerrit.server.schema.DataSourceModule;
@@ -280,7 +280,7 @@
     modules.add(new ChangeHookRunner.Module());
     modules.add(new ReceiveCommitsExecutorModule());
     modules.add(new MergeabilityChecksExecutorModule());
-    modules.add(new IntraLineWorkerPool.Module());
+    modules.add(new DiffExecutorModule());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new InternalAccountDirectory.Module());
     modules.add(new DefaultCacheFactory.Module());
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index 2d0af1b..a658fc3 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -1,13 +1,15 @@
 include_defs('//lib/maven.defs')
 
-VERS = '3.6.2.201501210735-r'
+REPO = GERRIT  # Leave here even if set to MAVEN_CENTRAL.
+VERS = '3.7.0.201502260915-r.58-g65c379e'
 
 maven_jar(
   name = 'jgit',
   id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
-  bin_sha1 = '47d59dffb5f02470ccfb6c1a5a31b6040a1636e5',
-  src_sha1 = '52e133360f2c38046de262c827dfec8ec5b7c885',
+  bin_sha1 = '8fc9620ec499169facad3355f7417eb6a8aff511',
+  src_sha1 = '40bd9ae8af8e0b03eb4e43f44f5feda8b7325221',
   license = 'jgit',
+  repository = REPO,
   unsign = True,
   deps = [':ewah'],
   exclude = [
@@ -20,8 +22,9 @@
 maven_jar(
   name = 'jgit-servlet',
   id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
-  sha1 = 'f7788bbd0c0414e856c84ddf353e6f4c62fe1d28',
+  sha1 = 'cecc2b9c0b94455348c3a0c63eb83f72cc595757',
   license = 'jgit',
+  repository = REPO,
   deps = [':jgit'],
   unsign = True,
   exclude = [
@@ -33,8 +36,9 @@
 maven_jar(
   name = 'jgit-archive',
   id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
-  sha1 = 'ca9da919275dad5ac2844529f4cdccdd919bab5f',
+  sha1 = '7ccc7c78bf47566045ea7a3c08508ba18e4684ca',
   license = 'jgit',
+  repository = REPO,
   deps = [':jgit',
     '//lib/commons:compress',
     '//lib:tukaani-xz',
@@ -49,8 +53,9 @@
 maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
-  sha1 = 'b7418e19efbeb9490b359c8a921cf32bfc57ebbd',
+  sha1 = '87d64d722447dc3971ace30d2a72593c72a4d05f',
   license = 'DO_NOT_DISTRIBUTE',
+  repository = REPO,
   unsign = True,
   deps = [':jgit'],
 )
diff --git a/lib/maven.defs b/lib/maven.defs
index 5f4006f..23708ca 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -40,7 +40,8 @@
     sha1 = '', bin_sha1 = '', src_sha1 = '',
     repository = MAVEN_CENTRAL,
     attach_source = True,
-    visibility = ['PUBLIC']):
+    visibility = ['PUBLIC'],
+    local_license = False):
   from os import path
 
   parts = id.split(':')
@@ -89,7 +90,10 @@
     cmd = ' '.join(cmd),
     out = binjar,
   )
-  license = ['//lib:LICENSE-' + license]
+  license = ':LICENSE-' + license
+  if not local_license:
+    license = '//lib' + license
+  license = [license]
 
   if src_sha1 or attach_source:
     cmd = ['$(exe //tools:download_file)', '-o', '$OUT', '-u', srcurl]