diff options
Diffstat (limited to 'java/com/google/gerrit/server/submit/MergeOpRepoManager.java')
-rw-r--r-- | java/com/google/gerrit/server/submit/MergeOpRepoManager.java | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java new file mode 100644 index 0000000000..3f07ed7500 --- /dev/null +++ b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java @@ -0,0 +1,218 @@ +// Copyright (C) 2016 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.submit; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.Maps; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.git.CodeReviewCommit; +import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.git.MergeTip; +import com.google.gerrit.server.git.validators.OnSubmitValidators; +import com.google.gerrit.server.project.NoSuchProjectException; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.gerrit.server.update.BatchUpdate; +import com.google.inject.Inject; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevFlag; +import org.eclipse.jgit.revwalk.RevSort; + +/** + * This is a helper class for MergeOp and not intended for general use. + * + * <p>Some database backends require to open a repository just once within a transaction of a + * submission, this caches open repositories to satisfy that requirement. + */ +public class MergeOpRepoManager implements AutoCloseable { + public class OpenRepo { + final Repository repo; + final CodeReviewRevWalk rw; + final RevFlag canMergeFlag; + final ObjectInserter ins; + + final ProjectState project; + BatchUpdate update; + + private final ObjectReader reader; + private final Map<Branch.NameKey, OpenBranch> branches; + + private OpenRepo(Repository repo, ProjectState project) { + this.repo = repo; + this.project = project; + ins = repo.newObjectInserter(); + reader = ins.newReader(); + rw = CodeReviewCommit.newRevWalk(reader); + rw.sort(RevSort.TOPO); + rw.sort(RevSort.COMMIT_TIME_DESC, true); + rw.setRetainBody(false); + canMergeFlag = rw.newFlag("CAN_MERGE"); + rw.retainOnReset(canMergeFlag); + + branches = Maps.newHashMapWithExpectedSize(1); + } + + OpenBranch getBranch(Branch.NameKey branch) throws IntegrationException { + OpenBranch ob = branches.get(branch); + if (ob == null) { + ob = new OpenBranch(this, branch); + branches.put(branch, ob); + } + return ob; + } + + public Repository getRepo() { + return repo; + } + + Project.NameKey getProjectName() { + return project.getNameKey(); + } + + public CodeReviewRevWalk getCodeReviewRevWalk() { + return rw; + } + + public BatchUpdate getUpdate() { + checkState(db != null, "call setContext before getUpdate"); + if (update == null) { + update = + batchUpdateFactory + .create(db, getProjectName(), caller, ts) + .setRepository(repo, rw, ins) + .setOnSubmitValidators(onSubmitValidatorsFactory.create()); + } + return update; + } + + private void close() { + if (update != null) { + update.close(); + } + rw.close(); + reader.close(); + ins.close(); + repo.close(); + } + } + + public static class OpenBranch { + final RefUpdate update; + final CodeReviewCommit oldTip; + MergeTip mergeTip; + + OpenBranch(OpenRepo or, Branch.NameKey name) throws IntegrationException { + try { + update = or.repo.updateRef(name.get()); + if (update.getOldObjectId() != null) { + oldTip = or.rw.parseCommit(update.getOldObjectId()); + } else if (Objects.equals(or.repo.getFullBranch(), name.get()) + || Objects.equals(RefNames.REFS_CONFIG, name.get())) { + oldTip = null; + update.setExpectedOldObjectId(ObjectId.zeroId()); + } else { + throw new IntegrationException( + "The destination branch " + name + " does not exist anymore."); + } + } catch (IOException e) { + throw new IntegrationException("Cannot open branch " + name, e); + } + } + } + + private final Map<Project.NameKey, OpenRepo> openRepos; + private final BatchUpdate.Factory batchUpdateFactory; + private final OnSubmitValidators.Factory onSubmitValidatorsFactory; + private final GitRepositoryManager repoManager; + private final ProjectCache projectCache; + + private ReviewDb db; + private Timestamp ts; + private IdentifiedUser caller; + + @Inject + MergeOpRepoManager( + GitRepositoryManager repoManager, + ProjectCache projectCache, + BatchUpdate.Factory batchUpdateFactory, + OnSubmitValidators.Factory onSubmitValidatorsFactory) { + this.repoManager = repoManager; + this.projectCache = projectCache; + this.batchUpdateFactory = batchUpdateFactory; + this.onSubmitValidatorsFactory = onSubmitValidatorsFactory; + + openRepos = new HashMap<>(); + } + + public void setContext(ReviewDb db, Timestamp ts, IdentifiedUser caller) { + this.db = db; + this.ts = ts; + this.caller = caller; + } + + public OpenRepo getRepo(Project.NameKey project) throws NoSuchProjectException, IOException { + if (openRepos.containsKey(project)) { + return openRepos.get(project); + } + + ProjectState projectState = projectCache.get(project); + if (projectState == null) { + throw new NoSuchProjectException(project); + } + try { + OpenRepo or = new OpenRepo(repoManager.openRepository(project), projectState); + openRepos.put(project, or); + return or; + } catch (RepositoryNotFoundException e) { + throw new NoSuchProjectException(project, e); + } + } + + public List<BatchUpdate> batchUpdates(Collection<Project.NameKey> projects) + throws NoSuchProjectException, IOException { + List<BatchUpdate> updates = new ArrayList<>(projects.size()); + for (Project.NameKey project : projects) { + updates.add(getRepo(project).getUpdate().setRefLogMessage("merged")); + } + return updates; + } + + @Override + public void close() { + for (OpenRepo repo : openRepos.values()) { + repo.close(); + } + openRepos.clear(); + } +} |