CreateTag: Convert ConcurrentRefUpdateException into LockFailureException

Creating a tag may fail with a ConcurrentRefUpdateException that
represents a LOCK_FAILURE. Convert this exception into a
LockFailureException so that our auto-retrying logic for LOCK_FAILURES
can recognize the LOCK_FAILURE and retry the operation, instead of
failing immediately.

Example stacktrace:

Cannot create tag "refs/tags/foo-bar" [CONTEXT project="my-project" request="REST /projects/*/tags/*" ]

org.eclipse.jgit.api.errors.ConcurrentRefUpdateException: Could not lock HEAD. RefUpdate return code was: LOCK_FAILURE
	at org.eclipse.jgit.api.TagCommand.updateTagRef(TagCommand.java:178)
	at org.eclipse.jgit.api.TagCommand.call(TagCommand.java:129)
	at com.google.gerrit.server.restapi.project.CreateTag.apply(CreateTag.java:144)
	at com.google.gerrit.server.restapi.project.CreateTag.apply(CreateTag.java:59)
	at com.google.gerrit.httpd.restapi.RestApiServlet.lambda$invokeRestCollectionCreateViewWithRetry$7(RestApiServlet.java:897)
     ...
Caused by: org.eclipse.jgit.api.errors.ConcurrentRefUpdateException: Could not lock HEAD. RefUpdate return code was: LOCK_FAILURE
	at org.eclipse.jgit.api.TagCommand.updateTagRef(TagCommand.java:178)
	at org.eclipse.jgit.api.TagCommand.call(TagCommand.java:129)
	at com.google.gerrit.server.restapi.project.CreateTag.apply(CreateTag.java:144)
	... 227 more

Release-Notes: skip
Change-Id: I2ff60b01ab0e2305fdf8739cd884038091f2b888
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/git/GitUpdateFailureException.java b/java/com/google/gerrit/git/GitUpdateFailureException.java
index 7fcb828..339339c 100644
--- a/java/com/google/gerrit/git/GitUpdateFailureException.java
+++ b/java/com/google/gerrit/git/GitUpdateFailureException.java
@@ -46,6 +46,11 @@
             .collect(toImmutableList());
   }
 
+  protected GitUpdateFailureException(String message, Throwable cause) {
+    super(message, cause);
+    this.failures = ImmutableList.of();
+  }
+
   /** Returns the names of the refs for which the update failed. */
   public ImmutableList<String> getFailedRefs() {
     return failures.stream().map(GitUpdateFailure::ref).collect(toImmutableList());
diff --git a/java/com/google/gerrit/git/LockFailureException.java b/java/com/google/gerrit/git/LockFailureException.java
index 371488d..2908db2 100644
--- a/java/com/google/gerrit/git/LockFailureException.java
+++ b/java/com/google/gerrit/git/LockFailureException.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.git;
 
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.RefUpdate;
 
@@ -21,6 +22,9 @@
 public class LockFailureException extends GitUpdateFailureException {
   private static final long serialVersionUID = 1L;
 
+  private static final String REF_UPDATE_RETURN_CODE_WAS_LOCK_FAILURE =
+      "RefUpdate return code was: LOCK_FAILURE";
+
   public LockFailureException(String message, RefUpdate refUpdate) {
     super(message, refUpdate);
   }
@@ -28,4 +32,15 @@
   public LockFailureException(String message, BatchRefUpdate batchRefUpdate) {
     super(message, batchRefUpdate);
   }
+
+  protected LockFailureException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public static void throwIfLockFailure(ConcurrentRefUpdateException e)
+      throws LockFailureException {
+    if (e.getMessage().contains(REF_UPDATE_RETURN_CODE_WAS_LOCK_FAILURE)) {
+      throw new LockFailureException(e.getMessage(), e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index 34c3ff7..b12ceef 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
+import com.google.gerrit.git.LockFailureException;
 import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -48,6 +49,7 @@
 import java.time.ZoneId;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.TagCommand;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -141,18 +143,23 @@
                         .newCommitterIdent(TimeUtil.now(), ZoneId.systemDefault()));
           }
 
-          Ref result = tag.call();
-          tagCache.updateFastForward(
-              resource.getNameKey(), ref, ObjectId.zeroId(), result.getObjectId());
-          referenceUpdated.fire(
-              resource.getNameKey(),
-              ref,
-              ObjectId.zeroId(),
-              result.getObjectId(),
-              resource.getUser().asIdentifiedUser().state());
-          try (RevWalk w = new RevWalk(repo)) {
-            return Response.created(
-                ListTags.createTagInfo(perm, result, w, resource.getProjectState(), links));
+          try {
+            Ref result = tag.call();
+            tagCache.updateFastForward(
+                resource.getNameKey(), ref, ObjectId.zeroId(), result.getObjectId());
+            referenceUpdated.fire(
+                resource.getNameKey(),
+                ref,
+                ObjectId.zeroId(),
+                result.getObjectId(),
+                resource.getUser().asIdentifiedUser().state());
+            try (RevWalk w = new RevWalk(repo)) {
+              return Response.created(
+                  ListTags.createTagInfo(perm, result, w, resource.getProjectState(), links));
+            }
+          } catch (ConcurrentRefUpdateException e) {
+            LockFailureException.throwIfLockFailure(e);
+            throw e;
           }
         }
       } catch (GitAPIException e) {