summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/patch/AutoMerger.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/patch/AutoMerger.java')
-rw-r--r--java/com/google/gerrit/server/patch/AutoMerger.java198
1 files changed, 198 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
new file mode 100644
index 0000000000..285c37d103
--- /dev/null
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -0,0 +1,198 @@
+// 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.patch;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.UsedAt;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.InMemoryInserter;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.inject.Inject;
+import java.io.IOException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class AutoMerger {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public static boolean cacheAutomerge(Config cfg) {
+ return cfg.getBoolean("change", null, "cacheAutomerge", true);
+ }
+
+ private final PersonIdent gerritIdent;
+ private final boolean save;
+
+ @Inject
+ AutoMerger(@GerritServerConfig Config cfg, @GerritPersonIdent PersonIdent gerritIdent) {
+ save = cacheAutomerge(cfg);
+ this.gerritIdent = gerritIdent;
+ }
+
+ /**
+ * Perform an auto-merge of the parents of the given merge commit.
+ *
+ * @return auto-merge commit or {@code null} if an auto-merge commit couldn't be created. Headers
+ * of the returned RevCommit are parsed.
+ */
+ public RevCommit merge(
+ Repository repo,
+ RevWalk rw,
+ ObjectInserter ins,
+ RevCommit merge,
+ ThreeWayMergeStrategy mergeStrategy)
+ throws IOException {
+ checkArgument(rw.getObjectReader().getCreatedFromInserter() == ins);
+ InMemoryInserter tmpIns = null;
+ if (ins instanceof InMemoryInserter) {
+ // Caller gave us an in-memory inserter, so ensure anything we write from
+ // this method is visible to them.
+ tmpIns = (InMemoryInserter) ins;
+ } else if (!save) {
+ // If we don't plan on saving results, use a fully in-memory inserter.
+ // Using just a non-flushing wrapper is not sufficient, since in
+ // particular DfsInserter might try to write to storage after exceeding an
+ // internal buffer size.
+ tmpIns = new InMemoryInserter(rw.getObjectReader());
+ }
+
+ rw.parseHeaders(merge);
+ String refName = RefNames.refsCacheAutomerge(merge.name());
+ Ref ref = repo.getRefDatabase().exactRef(refName);
+ if (ref != null && ref.getObjectId() != null) {
+ RevObject obj = rw.parseAny(ref.getObjectId());
+ if (obj instanceof RevCommit) {
+ return (RevCommit) obj;
+ }
+ return commit(repo, rw, tmpIns, ins, refName, obj, merge);
+ }
+
+ ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(repo, true);
+ DirCache dc = DirCache.newInCore();
+ m.setDirCache(dc);
+ m.setObjectInserter(tmpIns == null ? new NonFlushingWrapper(ins) : tmpIns);
+
+ boolean couldMerge;
+ try {
+ couldMerge = m.merge(merge.getParents());
+ } catch (IOException | RuntimeException e) {
+ // It is not safe to continue further down in this method as throwing
+ // an exception most likely means that the merge tree was not created
+ // and m.getMergeResults() is empty. This would mean that all paths are
+ // unmerged and Gerrit UI would show all paths in the patch list.
+ logger.atWarning().withCause(e).log("Error attempting automerge %s", refName);
+ return null;
+ }
+
+ ObjectId treeId;
+ if (couldMerge) {
+ treeId = m.getResultTreeId();
+ } else {
+ treeId =
+ MergeUtil.mergeWithConflicts(
+ rw,
+ ins,
+ dc,
+ "HEAD",
+ merge.getParent(0),
+ "BRANCH",
+ merge.getParent(1),
+ m.getMergeResults());
+ }
+
+ return commit(repo, rw, tmpIns, ins, refName, treeId, merge);
+ }
+
+ private RevCommit commit(
+ Repository repo,
+ RevWalk rw,
+ @Nullable InMemoryInserter tmpIns,
+ ObjectInserter ins,
+ String refName,
+ ObjectId tree,
+ RevCommit merge)
+ throws IOException {
+ rw.parseHeaders(merge);
+ // For maximum stability, choose a single ident using the committer time of
+ // the input commit, using the server name and timezone.
+ PersonIdent ident =
+ new PersonIdent(
+ gerritIdent, merge.getCommitterIdent().getWhen(), gerritIdent.getTimeZone());
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ cb.setTreeId(tree);
+ cb.setMessage("Auto-merge of " + merge.name() + '\n');
+ for (RevCommit p : merge.getParents()) {
+ cb.addParentId(p);
+ }
+
+ if (!save) {
+ checkArgument(tmpIns != null);
+ try (ObjectReader tmpReader = tmpIns.newReader();
+ RevWalk tmpRw = new RevWalk(tmpReader)) {
+ return tmpRw.parseCommit(tmpIns.insert(cb));
+ }
+ }
+
+ checkArgument(tmpIns == null);
+ checkArgument(!(ins instanceof InMemoryInserter));
+ ObjectId commitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setNewObjectId(commitId);
+ ru.disableRefLog();
+ ru.forceUpdate();
+ return rw.parseCommit(commitId);
+ }
+
+ private static class NonFlushingWrapper extends ObjectInserter.Filter {
+ private final ObjectInserter ins;
+
+ private NonFlushingWrapper(ObjectInserter ins) {
+ this.ins = ins;
+ }
+
+ @Override
+ protected ObjectInserter delegate() {
+ return ins;
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void close() {}
+ }
+}