summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/update/RefUpdateUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/update/RefUpdateUtil.java')
-rw-r--r--java/com/google/gerrit/server/update/RefUpdateUtil.java152
1 files changed, 152 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/update/RefUpdateUtil.java b/java/com/google/gerrit/server/update/RefUpdateUtil.java
new file mode 100644
index 0000000000..3e336776bf
--- /dev/null
+++ b/java/com/google/gerrit/server/update/RefUpdateUtil.java
@@ -0,0 +1,152 @@
+// Copyright (C) 2017 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.update;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.git.LockFailureException;
+import java.io.IOException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Static utilities for working with JGit's ref update APIs. */
+public class RefUpdateUtil {
+ /**
+ * Execute a batch ref update, throwing a checked exception if not all updates succeeded.
+ *
+ * <p>Creates a new {@link RevWalk} used only for this operation.
+ *
+ * @param bru batch update; should already have been executed.
+ * @param repo repository that created {@code bru}.
+ * @throws LockFailureException if the transaction was aborted due to lock failure; see {@link
+ * #checkResults(BatchRefUpdate)} for details.
+ * @throws IOException if any result was not {@code OK}.
+ */
+ public static void executeChecked(BatchRefUpdate bru, Repository repo) throws IOException {
+ try (RevWalk rw = new RevWalk(repo)) {
+ executeChecked(bru, rw);
+ }
+ }
+
+ /**
+ * Execute a batch ref update, throwing a checked exception if not all updates succeeded.
+ *
+ * @param bru batch update; should already have been executed.
+ * @param rw walk for executing the update.
+ * @throws LockFailureException if the transaction was aborted due to lock failure; see {@link
+ * #checkResults(BatchRefUpdate)} for details.
+ * @throws IOException if any result was not {@code OK}.
+ */
+ public static void executeChecked(BatchRefUpdate bru, RevWalk rw) throws IOException {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ checkResults(bru);
+ }
+
+ /**
+ * Check results of all commands in the update batch, reducing to a single exception if there was
+ * a failure.
+ *
+ * <p>Throws {@link LockFailureException} if at least one command failed with {@code
+ * LOCK_FAILURE}, and the entire transaction was aborted, i.e. any non-{@code LOCK_FAILURE}
+ * results, if there were any, failed with "transaction aborted".
+ *
+ * <p>In particular, if the underlying ref database does not {@link
+ * org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions() perform atomic transactions},
+ * then a combination of {@code LOCK_FAILURE} on one ref and {@code OK} or another result on other
+ * refs will <em>not</em> throw {@code LockFailureException}.
+ *
+ * @param bru batch update; should already have been executed.
+ * @throws LockFailureException if the transaction was aborted due to lock failure.
+ * @throws IOException if any result was not {@code OK}.
+ */
+ @VisibleForTesting
+ static void checkResults(BatchRefUpdate bru) throws IOException {
+ if (bru.getCommands().isEmpty()) {
+ return;
+ }
+
+ int lockFailure = 0;
+ int aborted = 0;
+ int failure = 0;
+
+ for (ReceiveCommand cmd : bru.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ failure++;
+ }
+ if (cmd.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
+ lockFailure++;
+ } else if (cmd.getResult() == ReceiveCommand.Result.REJECTED_OTHER_REASON
+ && JGitText.get().transactionAborted.equals(cmd.getMessage())) {
+ aborted++;
+ }
+ }
+
+ if (lockFailure + aborted == bru.getCommands().size()) {
+ throw new LockFailureException("Update aborted with one or more lock failures: " + bru, bru);
+ } else if (failure > 0) {
+ throw new IOException("Update failed: " + bru);
+ }
+ }
+
+ /**
+ * Delete a single ref, throwing a checked exception on failure.
+ *
+ * <p>Does not require that the ref have any particular old value. Succeeds as a no-op if the ref
+ * did not exist.
+ *
+ * @param repo repository.
+ * @param refName ref name to delete.
+ * @throws LockFailureException if a low-level lock failure (e.g. compare-and-swap failure)
+ * occurs.
+ * @throws IOException if an error occurred.
+ */
+ public static void deleteChecked(Repository repo, String refName) throws IOException {
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setForceUpdate(true);
+ switch (ru.delete()) {
+ case FORCED:
+ // Ref was deleted.
+ return;
+
+ case NEW:
+ // Ref didn't exist (yes, really).
+ return;
+
+ case LOCK_FAILURE:
+ throw new LockFailureException("Failed to delete " + refName + ": " + ru.getResult(), ru);
+
+ // Not really failures, but should not be the result of a deletion, so the best option is to
+ // throw.
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case RENAMED:
+ case NOT_ATTEMPTED:
+
+ case IO_FAILURE:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new IOException("Failed to delete " + refName + ": " + ru.getResult());
+ }
+ }
+
+ private RefUpdateUtil() {}
+}