summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/submit/MergeOpRepoManager.java')
-rw-r--r--java/com/google/gerrit/server/submit/MergeOpRepoManager.java218
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();
+ }
+}