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.submit; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : import static com.google.gerrit.server.project.ProjectCache.noSuchProject; 19 : import static java.util.Objects.requireNonNull; 20 : 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.common.collect.Maps; 23 : import com.google.gerrit.entities.BranchNameKey; 24 : import com.google.gerrit.entities.Project; 25 : import com.google.gerrit.entities.RefNames; 26 : import com.google.gerrit.exceptions.StorageException; 27 : import com.google.gerrit.server.IdentifiedUser; 28 : import com.google.gerrit.server.change.NotifyResolver; 29 : import com.google.gerrit.server.git.CodeReviewCommit; 30 : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; 31 : import com.google.gerrit.server.git.GitRepositoryManager; 32 : import com.google.gerrit.server.git.MergeTip; 33 : import com.google.gerrit.server.git.validators.OnSubmitValidators; 34 : import com.google.gerrit.server.project.NoSuchProjectException; 35 : import com.google.gerrit.server.project.ProjectCache; 36 : import com.google.gerrit.server.project.ProjectState; 37 : import com.google.gerrit.server.update.BatchUpdate; 38 : import com.google.inject.Inject; 39 : import java.io.IOException; 40 : import java.time.Instant; 41 : import java.util.ArrayList; 42 : import java.util.Collection; 43 : import java.util.HashMap; 44 : import java.util.List; 45 : import java.util.Map; 46 : import java.util.Objects; 47 : import org.eclipse.jgit.errors.RepositoryNotFoundException; 48 : import org.eclipse.jgit.lib.ObjectInserter; 49 : import org.eclipse.jgit.lib.ObjectReader; 50 : import org.eclipse.jgit.lib.Ref; 51 : import org.eclipse.jgit.lib.Repository; 52 : import org.eclipse.jgit.revwalk.RevFlag; 53 : import org.eclipse.jgit.revwalk.RevSort; 54 : 55 : /** 56 : * This is a helper class for MergeOp and not intended for general use. 57 : * 58 : * <p>Some database backends require to open a repository just once within a transaction of a 59 : * submission, this caches open repositories to satisfy that requirement. 60 : */ 61 : public class MergeOpRepoManager implements AutoCloseable { 62 : public class OpenRepo { 63 : final Repository repo; 64 : final CodeReviewRevWalk rw; 65 : final RevFlag canMergeFlag; 66 : final ObjectInserter ins; 67 : 68 : final ProjectState project; 69 : BatchUpdate update; 70 : 71 : private final ObjectReader reader; 72 : private final Map<BranchNameKey, OpenBranch> branches; 73 : 74 54 : private OpenRepo(Repository repo, ProjectState project) { 75 54 : this.repo = repo; 76 54 : this.project = project; 77 54 : ins = repo.newObjectInserter(); 78 54 : reader = ins.newReader(); 79 54 : rw = CodeReviewCommit.newRevWalk(reader); 80 54 : rw.sort(RevSort.TOPO); 81 54 : rw.sort(RevSort.COMMIT_TIME_DESC, true); 82 54 : rw.setRetainBody(false); 83 54 : canMergeFlag = rw.newFlag("CAN_MERGE"); 84 54 : rw.retainOnReset(canMergeFlag); 85 : 86 54 : branches = Maps.newHashMapWithExpectedSize(1); 87 54 : } 88 : 89 : OpenBranch getBranch(BranchNameKey branch) throws IntegrationConflictException { 90 53 : OpenBranch ob = branches.get(branch); 91 53 : if (ob == null) { 92 53 : ob = new OpenBranch(this, branch); 93 53 : branches.put(branch, ob); 94 : } 95 53 : return ob; 96 : } 97 : 98 : public Repository getRepo() { 99 53 : return repo; 100 : } 101 : 102 : Project.NameKey getProjectName() { 103 53 : return project.getNameKey(); 104 : } 105 : 106 : public CodeReviewRevWalk getCodeReviewRevWalk() { 107 3 : return rw; 108 : } 109 : 110 : public BatchUpdate getUpdate() { 111 53 : checkState(caller != null, "call setContext before getUpdate"); 112 53 : if (update == null) { 113 53 : update = 114 : batchUpdateFactory 115 53 : .create(getProjectName(), caller, ts) 116 53 : .setRepository(repo, rw, ins) 117 53 : .setNotify(notify) 118 53 : .setOnSubmitValidators(onSubmitValidatorsFactory.create()); 119 : } 120 53 : return update; 121 : } 122 : 123 : // We want to reuse the open repo BUT not the BatchUpdate (because they are already executed) 124 : public void resetExecutedUpdates() { 125 53 : if (update != null && update.isExecuted()) { 126 53 : update.close(); 127 53 : update = null; 128 : } 129 53 : } 130 : 131 : private void close() { 132 53 : if (update != null) { 133 11 : update.close(); 134 : } 135 53 : rw.close(); 136 53 : reader.close(); 137 53 : ins.close(); 138 53 : repo.close(); 139 53 : } 140 : } 141 : 142 : public static class OpenBranch { 143 : final CodeReviewCommit oldTip; 144 : MergeTip mergeTip; 145 : 146 53 : OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationConflictException { 147 : try { 148 53 : Ref ref = or.getRepo().exactRef(name.branch()); 149 53 : if (ref != null) { 150 53 : oldTip = or.rw.parseCommit(ref.getObjectId()); 151 16 : } else if (Objects.equals(or.repo.getFullBranch(), name.branch()) 152 3 : || Objects.equals(RefNames.REFS_CONFIG, name.branch())) { 153 16 : oldTip = null; 154 : } else { 155 0 : throw new IntegrationConflictException( 156 : "The destination branch " + name + " does not exist anymore."); 157 : } 158 0 : } catch (IOException e) { 159 0 : throw new StorageException("Cannot open branch " + name, e); 160 53 : } 161 53 : } 162 : } 163 : 164 : private final Map<Project.NameKey, OpenRepo> openRepos; 165 : private final BatchUpdate.Factory batchUpdateFactory; 166 : private final OnSubmitValidators.Factory onSubmitValidatorsFactory; 167 : private final GitRepositoryManager repoManager; 168 : private final ProjectCache projectCache; 169 : 170 : private Instant ts; 171 : private IdentifiedUser caller; 172 : private NotifyResolver.Result notify; 173 : 174 : @Inject 175 : MergeOpRepoManager( 176 : GitRepositoryManager repoManager, 177 : ProjectCache projectCache, 178 : BatchUpdate.Factory batchUpdateFactory, 179 70 : OnSubmitValidators.Factory onSubmitValidatorsFactory) { 180 70 : this.repoManager = repoManager; 181 70 : this.projectCache = projectCache; 182 70 : this.batchUpdateFactory = batchUpdateFactory; 183 70 : this.onSubmitValidatorsFactory = onSubmitValidatorsFactory; 184 : 185 70 : openRepos = new HashMap<>(); 186 70 : } 187 : 188 : public void setContext(Instant ts, IdentifiedUser caller, NotifyResolver.Result notify) { 189 69 : this.ts = requireNonNull(ts); 190 69 : this.caller = requireNonNull(caller); 191 69 : this.notify = requireNonNull(notify); 192 69 : } 193 : 194 : public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException { 195 54 : if (openRepos.containsKey(project)) { 196 54 : return openRepos.get(project); 197 : } 198 : 199 54 : ProjectState projectState = projectCache.get(project).orElseThrow(noSuchProject(project)); 200 : try { 201 54 : OpenRepo or = new OpenRepo(repoManager.openRepository(project), projectState); 202 54 : openRepos.put(project, or); 203 54 : return or; 204 0 : } catch (RepositoryNotFoundException e) { 205 0 : throw new NoSuchProjectException(project, e); 206 : } 207 : } 208 : 209 : public List<BatchUpdate> batchUpdates(Collection<Project.NameKey> projects, String refLogMessage) 210 : throws NoSuchProjectException, IOException { 211 68 : requireNonNull(refLogMessage, "refLogMessage"); 212 68 : List<BatchUpdate> updates = new ArrayList<>(projects.size()); 213 68 : for (Project.NameKey project : projects) { 214 53 : updates.add(getRepo(project).getUpdate().setNotify(notify).setRefLogMessage(refLogMessage)); 215 53 : } 216 68 : return updates; 217 : } 218 : 219 : public void resetUpdates(ImmutableSet<Project.NameKey> projects) 220 : throws NoSuchProjectException, IOException { 221 53 : for (Project.NameKey project : projects) { 222 53 : getRepo(project).resetExecutedUpdates(); 223 53 : } 224 53 : } 225 : 226 : @Override 227 : public void close() { 228 69 : for (OpenRepo repo : openRepos.values()) { 229 53 : repo.close(); 230 53 : } 231 69 : openRepos.clear(); 232 69 : } 233 : }