Line data Source code
1 : // Copyright (C) 2016 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.server.update; 16 : 17 : import static com.google.common.base.Preconditions.checkArgument; 18 : import static java.util.Objects.requireNonNull; 19 : 20 : import com.google.gerrit.server.git.RefCache; 21 : import com.google.gerrit.server.git.RepoRefCache; 22 : import java.io.IOException; 23 : import java.util.Collections; 24 : import java.util.LinkedHashMap; 25 : import java.util.Map; 26 : import java.util.Optional; 27 : import org.eclipse.jgit.lib.BatchRefUpdate; 28 : import org.eclipse.jgit.lib.ObjectId; 29 : import org.eclipse.jgit.lib.Repository; 30 : import org.eclipse.jgit.transport.ReceiveCommand; 31 : 32 : /** 33 : * Collection of {@link ReceiveCommand}s that supports multiple updates per ref. 34 : * 35 : * <p>The underlying behavior of {@link BatchRefUpdate} is undefined (an implementations vary) when 36 : * more than one command per ref is added. This class works around that limitation by allowing 37 : * multiple updates per ref, as long as the previous new SHA-1 matches the next old SHA-1. 38 : */ 39 : public class ChainedReceiveCommands implements RefCache { 40 110 : private final Map<String, ReceiveCommand> commands = new LinkedHashMap<>(); 41 : private final RepoRefCache refCache; 42 : private final boolean closeRefCache; 43 : 44 : public ChainedReceiveCommands(Repository repo) { 45 110 : this(new RepoRefCache(repo), true); 46 110 : } 47 : 48 : public ChainedReceiveCommands(RepoRefCache refCache) { 49 0 : this(refCache, false); 50 0 : } 51 : 52 110 : private ChainedReceiveCommands(RepoRefCache refCache, boolean closeRefCache) { 53 110 : this.refCache = requireNonNull(refCache); 54 110 : this.closeRefCache = closeRefCache; 55 110 : } 56 : 57 : public RepoRefCache getRepoRefCache() { 58 59 : return refCache; 59 : } 60 : 61 : public boolean isEmpty() { 62 110 : return commands.isEmpty(); 63 : } 64 : 65 : /** 66 : * Add a command. 67 : * 68 : * @param cmd command to add. If a command has been previously added for the same ref, the new 69 : * SHA-1 of the most recent previous command must match the old SHA-1 of this command. 70 : */ 71 : public void add(ReceiveCommand cmd) { 72 109 : checkArgument(!cmd.getOldId().equals(cmd.getNewId()), "ref update is a no-op: %s", cmd); 73 109 : ReceiveCommand old = commands.get(cmd.getRefName()); 74 109 : if (old == null) { 75 109 : commands.put(cmd.getRefName(), cmd); 76 109 : return; 77 : } 78 9 : checkArgument( 79 9 : old.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED, 80 : "cannot chain ref update %s after update %s with result %s", 81 : cmd, 82 : old, 83 9 : old.getResult()); 84 9 : checkArgument( 85 9 : cmd.getOldId().equals(old.getNewId()), 86 : "cannot chain ref update %s after update %s with different new ID", 87 : cmd, 88 : old); 89 9 : commands.put( 90 9 : cmd.getRefName(), new ReceiveCommand(old.getOldId(), cmd.getNewId(), cmd.getRefName())); 91 9 : } 92 : 93 : /** 94 : * Get the latest value of a ref according to this sequence of commands. 95 : * 96 : * <p>After the value for a ref is read from the repo once, it is cached as in {@link 97 : * RepoRefCache}. 98 : * 99 : * @see RefCache#get(String) 100 : */ 101 : @Override 102 : public Optional<ObjectId> get(String refName) throws IOException { 103 103 : ReceiveCommand cmd = commands.get(refName); 104 103 : if (cmd != null) { 105 103 : return !cmd.getNewId().equals(ObjectId.zeroId()) 106 103 : ? Optional.of(cmd.getNewId()) 107 12 : : Optional.empty(); 108 : } 109 103 : return refCache.get(refName); 110 : } 111 : 112 : /** 113 : * Add commands from this instance to a native JGit batch update. 114 : * 115 : * <p>Exactly one command per ref will be added to the update. The old SHA-1 will be the old SHA-1 116 : * of the first command added to this instance for that ref; the new SHA-1 will be the new SHA-1 117 : * of the last command. 118 : * 119 : * @param bru batch update 120 : */ 121 : public void addTo(BatchRefUpdate bru) { 122 109 : for (ReceiveCommand cmd : commands.values()) { 123 109 : bru.addCommand(cmd); 124 109 : } 125 109 : } 126 : 127 : /** Returns an unmodifiable view of commands. */ 128 : public Map<String, ReceiveCommand> getCommands() { 129 110 : return Collections.unmodifiableMap(commands); 130 : } 131 : 132 : @Override 133 : public void close() { 134 0 : if (closeRefCache) { 135 0 : refCache.close(); 136 : } 137 0 : } 138 : }