Line data Source code
1 : // Copyright (C) 2017 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.git; 16 : 17 : import com.google.common.annotations.VisibleForTesting; 18 : import java.io.IOException; 19 : import org.eclipse.jgit.internal.JGitText; 20 : import org.eclipse.jgit.lib.BatchRefUpdate; 21 : import org.eclipse.jgit.lib.NullProgressMonitor; 22 : import org.eclipse.jgit.lib.RefUpdate; 23 : import org.eclipse.jgit.lib.Repository; 24 : import org.eclipse.jgit.revwalk.RevWalk; 25 : import org.eclipse.jgit.transport.ReceiveCommand; 26 : 27 : /** Static utilities for working with JGit's ref update APIs. */ 28 : public class RefUpdateUtil { 29 : /** 30 : * Execute a batch ref update, throwing a checked exception if not all updates succeeded. 31 : * 32 : * <p>Creates a new {@link RevWalk} used only for this operation. 33 : * 34 : * @param bru batch update; should already have been executed. 35 : * @param repo repository that created {@code bru}. 36 : * @throws LockFailureException if the transaction was aborted due to lock failure; see {@link 37 : * #checkResults(BatchRefUpdate)} for details. 38 : * @throws IOException if any result was not {@code OK}. 39 : */ 40 : public static void executeChecked(BatchRefUpdate bru, Repository repo) throws IOException { 41 152 : try (RevWalk rw = new RevWalk(repo)) { 42 152 : executeChecked(bru, rw); 43 : } 44 152 : } 45 : 46 : /** 47 : * Execute a batch ref update, throwing a checked exception if not all updates succeeded. 48 : * 49 : * @param bru batch update; should already have been executed. 50 : * @param rw walk for executing the update. 51 : * @throws LockFailureException if the transaction was aborted due to lock failure; see {@link 52 : * #checkResults(BatchRefUpdate)} for details. 53 : * @throws IOException if any result was not {@code OK}. 54 : */ 55 : public static void executeChecked(BatchRefUpdate bru, RevWalk rw) throws IOException { 56 152 : bru.execute(rw, NullProgressMonitor.INSTANCE); 57 152 : checkResults(bru); 58 152 : } 59 : 60 : /** 61 : * Check results of all commands in the update batch, reducing to a single exception if there was 62 : * a failure. 63 : * 64 : * <p>Throws {@link LockFailureException} if at least one command failed with {@code 65 : * LOCK_FAILURE}, and the entire transaction was aborted, i.e. any non-{@code LOCK_FAILURE} 66 : * results, if there were any, failed with "transaction aborted". 67 : * 68 : * <p>In particular, if the underlying ref database does not {@link 69 : * org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions() perform atomic transactions}, 70 : * then a combination of {@code LOCK_FAILURE} on one ref and {@code OK} or another result on other 71 : * refs will <em>not</em> throw {@code LockFailureException}. 72 : * 73 : * @param bru batch update; should already have been executed. 74 : * @throws LockFailureException if the transaction was aborted due to lock failure. 75 : * @throws IOException if any result was not {@code OK}. 76 : */ 77 : @VisibleForTesting 78 : static void checkResults(BatchRefUpdate bru) throws IOException { 79 153 : if (bru.getCommands().isEmpty()) { 80 29 : return; 81 : } 82 : 83 153 : int lockFailure = 0; 84 153 : int aborted = 0; 85 153 : int failure = 0; 86 : 87 153 : for (ReceiveCommand cmd : bru.getCommands()) { 88 153 : if (cmd.getResult() != ReceiveCommand.Result.OK) { 89 12 : failure++; 90 : } 91 153 : if (cmd.getResult() == ReceiveCommand.Result.LOCK_FAILURE) { 92 12 : lockFailure++; 93 153 : } else if (cmd.getResult() == ReceiveCommand.Result.REJECTED_OTHER_REASON 94 12 : && JGitText.get().transactionAborted.equals(cmd.getMessage())) { 95 12 : aborted++; 96 : } 97 153 : } 98 : 99 153 : if (lockFailure + aborted == bru.getCommands().size()) { 100 12 : throw new LockFailureException("Update aborted with one or more lock failures: " + bru, bru); 101 153 : } else if (failure > 0) { 102 1 : throw new GitUpdateFailureException("Update failed: " + bru, bru); 103 : } 104 153 : } 105 : 106 : /** 107 : * Check results of a single ref update, throwing an exception if there was a failure. 108 : * 109 : * @param ru ref update; must already have been executed. 110 : * @throws IllegalArgumentException if the result was {@code NOT_ATTEMPTED}. 111 : * @throws LockFailureException if the result was {@code LOCK_FAILURE}. 112 : * @throws IOException if the result failed for another reason. 113 : */ 114 : public static void checkResult(RefUpdate ru) throws IOException { 115 151 : RefUpdate.Result result = ru.getResult(); 116 151 : switch (result) { 117 : case NOT_ATTEMPTED: 118 0 : throw new IllegalArgumentException("Not attempted: " + ru.getName()); 119 : case NEW: 120 : case FORCED: 121 : case NO_CHANGE: 122 : case FAST_FORWARD: 123 : case RENAMED: 124 151 : return; 125 : case LOCK_FAILURE: 126 1 : throw new LockFailureException("Failed to update " + ru.getName() + ": " + result, ru); 127 : default: 128 : case IO_FAILURE: 129 : case REJECTED: 130 : case REJECTED_CURRENT_BRANCH: 131 : case REJECTED_MISSING_OBJECT: 132 : case REJECTED_OTHER_REASON: 133 0 : throw new GitUpdateFailureException( 134 0 : "Failed to update " + ru.getName() + ": " + ru.getResult(), ru); 135 : } 136 : } 137 : 138 : /** 139 : * Delete a single ref, throwing a checked exception on failure. 140 : * 141 : * <p>Does not require that the ref have any particular old value. Succeeds as a no-op if the ref 142 : * did not exist. 143 : * 144 : * @param repo repository. 145 : * @param refName ref name to delete. 146 : * @throws LockFailureException if a low-level lock failure (e.g. compare-and-swap failure) 147 : * occurs. 148 : * @throws IOException if an error occurred. 149 : */ 150 : public static void deleteChecked(Repository repo, String refName) throws IOException { 151 1 : RefUpdate ru = repo.updateRef(refName); 152 1 : ru.setForceUpdate(true); 153 1 : ru.setCheckConflicting(false); 154 1 : switch (ru.delete()) { 155 : case FORCED: 156 : // Ref was deleted. 157 1 : return; 158 : 159 : case NEW: 160 : // Ref didn't exist (yes, really). 161 1 : return; 162 : 163 : case LOCK_FAILURE: 164 0 : throw new LockFailureException("Failed to delete " + refName + ": " + ru.getResult(), ru); 165 : 166 : // Not really failures, but should not be the result of a deletion, so the best option is to 167 : // throw. 168 : case NO_CHANGE: 169 : case FAST_FORWARD: 170 : case RENAMED: 171 : case NOT_ATTEMPTED: 172 : 173 : case IO_FAILURE: 174 : case REJECTED: 175 : case REJECTED_CURRENT_BRANCH: 176 : case REJECTED_MISSING_OBJECT: 177 : case REJECTED_OTHER_REASON: 178 : default: 179 0 : throw new GitUpdateFailureException( 180 0 : "Failed to delete " + refName + ": " + ru.getResult(), ru); 181 : } 182 : } 183 : 184 : private RefUpdateUtil() {} 185 : }