Merge "Merge branch 'stable-3.2'"
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 3b3a5fc..3364fc1 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -339,7 +339,7 @@
* @return comments as a list; comments have the {@code revision} field set to indicate their
* patch set.
* @throws RestApiException
- * @deprecate Callers should use {@link #commentsRequest()} instead
+ * @deprecated Callers should use {@link #commentsRequest()} instead
*/
@Deprecated
default List<CommentInfo> commentsAsList() throws RestApiException {
@@ -351,7 +351,6 @@
*
* @return A {@link CommentsRequest} entity that can be used to retrieve the comments using the
* {@link CommentsRequest#get()} or {@link CommentsRequest#getAsList()}.
- * @throws RestApiException
*/
CommentsRequest commentsRequest() throws RestApiException;
@@ -429,7 +428,6 @@
*
* @return comments as a list; comments have the {@code revision} field set to indicate their
* patch set.
- * @throws RestApiException
*/
public abstract List<CommentInfo> getAsList() throws RestApiException;
diff --git a/java/com/google/gerrit/server/CommentContextLoader.java b/java/com/google/gerrit/server/CommentContextLoader.java
index 813dad7..7f84693 100644
--- a/java/com/google/gerrit/server/CommentContextLoader.java
+++ b/java/com/google/gerrit/server/CommentContextLoader.java
@@ -55,7 +55,7 @@
}
@Inject
- public CommentContextLoader(GitRepositoryManager repoManager, @Assisted Project.NameKey project) {
+ CommentContextLoader(GitRepositoryManager repoManager, @Assisted Project.NameKey project) {
this.repoManager = repoManager;
this.project = project;
this.candidates = new HashMap<>();
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 0023395..22d02d2 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -187,9 +187,11 @@
import com.google.gerrit.server.rules.RulesCache;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.gerrit.server.ssh.SshAddressesModule;
+import com.google.gerrit.server.submit.ConfiguredSubscriptionGraphFactory;
import com.google.gerrit.server.submit.GitModules;
import com.google.gerrit.server.submit.MergeSuperSetComputation;
import com.google.gerrit.server.submit.SubmitStrategy;
+import com.google.gerrit.server.submit.SubscriptionGraph;
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.util.IdGenerator;
@@ -453,6 +455,7 @@
factory(VersionedAuthorizedKeys.Factory.class);
bind(AccountManager.class);
+ bind(SubscriptionGraph.Factory.class).to(ConfiguredSubscriptionGraphFactory.class);
bind(new TypeLiteral<List<CommentLinkInfo>>() {}).toProvider(CommentLinkProvider.class);
DynamicSet.bind(binder(), GerritConfigListener.class).to(CommentLinkProvider.class);
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
index fec7bdc..e3b433c 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
@@ -26,7 +26,6 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.CommentContextLoader;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -42,7 +41,6 @@
private final ChangeData.Factory changeDataFactory;
private final Provider<CommentJson> commentJson;
private final CommentsUtil commentsUtil;
- private final CommentContextLoader.Factory commentContextFactory;
private boolean includeContext;
@@ -62,13 +60,11 @@
ChangeData.Factory changeDataFactory,
Provider<CommentJson> commentJson,
CommentsUtil commentsUtil,
- ChangeMessagesUtil changeMessagesUtil,
- CommentContextLoader.Factory commentContextFactory) {
+ ChangeMessagesUtil changeMessagesUtil) {
this.changeDataFactory = changeDataFactory;
this.commentJson = commentJson;
this.commentsUtil = commentsUtil;
this.changeMessagesUtil = changeMessagesUtil;
- this.commentContextFactory = commentContextFactory;
}
@Override
diff --git a/java/com/google/gerrit/server/submit/CherryPick.java b/java/com/google/gerrit/server/submit/CherryPick.java
index b66006a..a09ba63 100644
--- a/java/com/google/gerrit/server/submit/CherryPick.java
+++ b/java/com/google/gerrit/server/submit/CherryPick.java
@@ -193,7 +193,7 @@
// was configured.
MergeTip mergeTip = args.mergeTip;
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)
- && !args.submoduleOp.hasSubscription(args.destBranch)) {
+ && !args.subscriptionGraph.hasSubscription(args.destBranch)) {
mergeTip.moveTipTo(toMerge, toMerge);
} else {
PersonIdent myIdent = new PersonIdent(args.serverIdent, ctx.getWhen());
diff --git a/java/com/google/gerrit/server/submit/ConfiguredSubscriptionGraphFactory.java b/java/com/google/gerrit/server/submit/ConfiguredSubscriptionGraphFactory.java
new file mode 100644
index 0000000..00e2443
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/ConfiguredSubscriptionGraphFactory.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2020 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 com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import java.util.Set;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Wrap a {@link SubscriptionGraph.Factory} to honor the gerrit configuration.
+ *
+ * <p>If superproject subscriptions are disabled in the conf, return an empty graph.
+ */
+public class ConfiguredSubscriptionGraphFactory implements SubscriptionGraph.Factory {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final SubscriptionGraph.Factory subscriptionGraphFactory;
+ private final Config cfg;
+
+ @Inject
+ ConfiguredSubscriptionGraphFactory(
+ @VanillaSubscriptionGraph SubscriptionGraph.Factory subscriptionGraphFactory,
+ @GerritServerConfig Config cfg) {
+ this.subscriptionGraphFactory = subscriptionGraphFactory;
+ this.cfg = cfg;
+ }
+
+ @Override
+ public SubscriptionGraph compute(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
+ throws SubmoduleConflictException {
+ if (cfg.getBoolean("submodule", "enableSuperProjectSubscriptions", true)) {
+ return subscriptionGraphFactory.compute(updatedBranches, orm);
+ } else {
+ logger.atFine().log("Updating superprojects disabled");
+ return SubscriptionGraph.createEmptyGraph(ImmutableSet.copyOf(updatedBranches));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/GitlinkOp.java b/java/com/google/gerrit/server/submit/GitlinkOp.java
new file mode 100644
index 0000000..70a52b6
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/GitlinkOp.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2020 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 com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.SubmoduleSubscription;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.update.RepoContext;
+import com.google.gerrit.server.update.RepoOnlyOp;
+import java.util.Collection;
+import java.util.Optional;
+
+/** Only used for branches without code review changes */
+public class GitlinkOp implements RepoOnlyOp {
+
+ static class Factory {
+ private SubmoduleCommits submoduleCommits;
+ private SubscriptionGraph subscriptionGraph;
+
+ Factory(SubmoduleCommits submoduleCommits, SubscriptionGraph subscriptionGraph) {
+ this.submoduleCommits = submoduleCommits;
+ this.subscriptionGraph = subscriptionGraph;
+ }
+
+ GitlinkOp create(BranchNameKey branch) {
+ return new GitlinkOp(branch, submoduleCommits, subscriptionGraph.getSubscriptions(branch));
+ }
+ }
+
+ private final BranchNameKey branch;
+ private final SubmoduleCommits commitHelper;
+ private final Collection<SubmoduleSubscription> branchTargets;
+
+ GitlinkOp(
+ BranchNameKey branch,
+ SubmoduleCommits commitHelper,
+ Collection<SubmoduleSubscription> branchTargets) {
+ this.branch = branch;
+ this.commitHelper = commitHelper;
+ this.branchTargets = branchTargets;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx) throws Exception {
+ Optional<CodeReviewCommit> commit = commitHelper.composeGitlinksCommit(branch, branchTargets);
+ if (commit.isPresent()) {
+ CodeReviewCommit c = commit.get();
+ ctx.addRefUpdate(c.getParent(0), c, branch.branch());
+ commitHelper.addBranchTip(branch, c);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/MergeIfNecessary.java b/java/com/google/gerrit/server/submit/MergeIfNecessary.java
index 82499b3..30f1661 100644
--- a/java/com/google/gerrit/server/submit/MergeIfNecessary.java
+++ b/java/com/google/gerrit/server/submit/MergeIfNecessary.java
@@ -30,7 +30,7 @@
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
if (args.mergeTip.getInitialTip() == null
- || !args.submoduleOp.hasSubscription(args.destBranch)) {
+ || !args.subscriptionGraph.hasSubscription(args.destBranch)) {
CodeReviewCommit firstFastForward =
args.mergeUtil.getFirstFastForward(args.mergeTip.getInitialTip(), args.rw, sorted);
if (firstFastForward != null && !firstFastForward.equals(args.mergeTip.getInitialTip())) {
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 4dc2c1c..8d3324f 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -226,7 +226,8 @@
private final MergeValidators.Factory mergeValidatorsFactory;
private final Provider<InternalChangeQuery> queryProvider;
private final SubmitStrategyFactory submitStrategyFactory;
- private final SubmoduleOp.Factory subOpFactory;
+ private final SubscriptionGraph.Factory subscriptionGraphFactory;
+ private final SubmoduleCommits.Factory submoduleCommitsFactory;
private final Provider<MergeOpRepoManager> ormProvider;
private final NotifyResolver notifyResolver;
private final RetryHelper retryHelper;
@@ -256,7 +257,8 @@
MergeValidators.Factory mergeValidatorsFactory,
Provider<InternalChangeQuery> queryProvider,
SubmitStrategyFactory submitStrategyFactory,
- SubmoduleOp.Factory subOpFactory,
+ SubmoduleCommits.Factory submoduleCommitsFactory,
+ SubscriptionGraph.Factory subscriptionGraphFactory,
Provider<MergeOpRepoManager> ormProvider,
NotifyResolver notifyResolver,
TopicMetrics topicMetrics,
@@ -269,7 +271,8 @@
this.mergeValidatorsFactory = mergeValidatorsFactory;
this.queryProvider = queryProvider;
this.submitStrategyFactory = submitStrategyFactory;
- this.subOpFactory = subOpFactory;
+ this.submoduleCommitsFactory = submoduleCommitsFactory;
+ this.subscriptionGraphFactory = subscriptionGraphFactory;
this.ormProvider = ormProvider;
this.notifyResolver = notifyResolver;
this.retryHelper = retryHelper;
@@ -605,9 +608,13 @@
commitStatus.maybeFailVerbose();
try {
- SubmoduleOp submoduleOp = subOpFactory.create(branches, orm);
- List<SubmitStrategy> strategies = getSubmitStrategies(toSubmit, submoduleOp, dryrun);
- this.allProjects = submoduleOp.getProjectsInOrder();
+ SubscriptionGraph subscriptionGraph = subscriptionGraphFactory.compute(branches, orm);
+ SubmoduleCommits submoduleCommits = submoduleCommitsFactory.create(orm);
+ UpdateOrderCalculator updateOrderCalculator = new UpdateOrderCalculator(subscriptionGraph);
+ List<SubmitStrategy> strategies =
+ getSubmitStrategies(
+ toSubmit, updateOrderCalculator, submoduleCommits, subscriptionGraph, dryrun);
+ this.allProjects = updateOrderCalculator.getProjectsInOrder();
try {
BatchUpdate.execute(
orm.batchUpdates(allProjects),
@@ -658,12 +665,19 @@
}
private List<SubmitStrategy> getSubmitStrategies(
- Map<BranchNameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, boolean dryrun)
+ Map<BranchNameKey, BranchBatch> toSubmit,
+ UpdateOrderCalculator updateOrderCalculator,
+ SubmoduleCommits submoduleCommits,
+ SubscriptionGraph subscriptionGraph,
+ boolean dryrun)
throws IntegrationConflictException, NoSuchProjectException, IOException {
List<SubmitStrategy> strategies = new ArrayList<>();
- Set<BranchNameKey> allBranches = submoduleOp.getBranchesInOrder();
+ Set<BranchNameKey> allBranches = updateOrderCalculator.getBranchesInOrder();
Set<CodeReviewCommit> allCommits =
toSubmit.values().stream().map(BranchBatch::commits).flatMap(Set::stream).collect(toSet());
+
+ GitlinkOp.Factory gitlinkOpFactory = new GitlinkOp.Factory(submoduleCommits, subscriptionGraph);
+
for (BranchNameKey branch : allBranches) {
OpenRepo or = orm.getRepo(branch.project());
if (toSubmit.containsKey(branch)) {
@@ -688,18 +702,19 @@
commitStatus,
submissionId,
submitInput,
- submoduleOp,
+ submoduleCommits,
+ subscriptionGraph,
dryrun);
strategies.add(strategy);
strategy.addOps(or.getUpdate(), commitsToSubmit);
if (submitting.submitType().equals(SubmitType.FAST_FORWARD_ONLY)
- && submoduleOp.hasSubscription(branch)) {
- submoduleOp.addOp(or.getUpdate(), branch);
+ && subscriptionGraph.hasSubscription(branch)) {
+ or.getUpdate().addRepoOnlyOp(gitlinkOpFactory.create(branch));
}
} else {
// no open change for this branch
// add submodule triggered op into BatchUpdate
- submoduleOp.addOp(or.getUpdate(), branch);
+ or.getUpdate().addRepoOnlyOp(gitlinkOpFactory.create(branch));
}
}
return strategies;
@@ -736,7 +751,7 @@
@Nullable
abstract SubmitType submitType();
- abstract Set<CodeReviewCommit> commits();
+ abstract ImmutableSet<CodeReviewCommit> commits();
}
private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted) {
@@ -851,7 +866,7 @@
toSubmit.add(commit);
}
logger.atFine().log("Submitting on this run: %s", toSubmit);
- return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit);
+ return new AutoValue_MergeOp_BranchBatch(submitType, ImmutableSet.copyOf(toSubmit));
}
private SetMultimap<ObjectId, PatchSet.Id> getRevisions(OpenRepo or, Collection<ChangeData> cds) {
diff --git a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
index edc3725..db48cce 100644
--- a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
@@ -298,7 +298,7 @@
// merge commits.
MergeTip mergeTip = args.mergeTip;
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)
- && !args.submoduleOp.hasSubscription(args.destBranch)) {
+ && !args.subscriptionGraph.hasSubscription(args.destBranch)) {
mergeTip.moveTipTo(toMerge, toMerge);
} else {
PersonIdent caller =
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategy.java b/java/com/google/gerrit/server/submit/SubmitStrategy.java
index 4010ad7..21ff2fc 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategy.java
@@ -97,7 +97,8 @@
Set<CodeReviewCommit> incoming,
SubmissionId submissionId,
SubmitInput submitInput,
- SubmoduleOp submoduleOp,
+ SubmoduleCommits submoduleCommits,
+ SubscriptionGraph subscriptionGraph,
boolean dryrun);
}
@@ -129,7 +130,8 @@
final SubmissionId submissionId;
final SubmitType submitType;
final SubmitInput submitInput;
- final SubmoduleOp submoduleOp;
+ final SubscriptionGraph subscriptionGraph;
+ final SubmoduleCommits submoduleCommits;
final ProjectState project;
final MergeSorter mergeSorter;
@@ -168,7 +170,8 @@
@Assisted SubmissionId submissionId,
@Assisted SubmitType submitType,
@Assisted SubmitInput submitInput,
- @Assisted SubmoduleOp submoduleOp,
+ @Assisted SubscriptionGraph subscriptionGraph,
+ @Assisted SubmoduleCommits submoduleCommits,
@Assisted boolean dryrun) {
this.accountCache = accountCache;
this.approvalsUtil = approvalsUtil;
@@ -197,7 +200,8 @@
this.submissionId = submissionId;
this.submitType = submitType;
this.submitInput = submitInput;
- this.submoduleOp = submoduleOp;
+ this.submoduleCommits = submoduleCommits;
+ this.subscriptionGraph = subscriptionGraph;
this.dryrun = dryrun;
this.project =
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
index 1cc78ff..2e66ae2 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
@@ -55,7 +55,8 @@
CommitStatus commitStatus,
SubmissionId submissionId,
SubmitInput submitInput,
- SubmoduleOp submoduleOp,
+ SubmoduleCommits submoduleCommits,
+ SubscriptionGraph subscriptionGraph,
boolean dryrun) {
SubmitStrategy.Arguments args =
argsFactory.create(
@@ -70,7 +71,8 @@
incoming,
submissionId,
submitInput,
- submoduleOp,
+ submoduleCommits,
+ subscriptionGraph,
dryrun);
switch (submitType) {
case CHERRY_PICK:
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index 3cc566b..3b77dd9 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -133,7 +133,7 @@
new ReceiveCommand(
firstNonNull(tipBefore, ObjectId.zeroId()), tipAfter, getDest().branch());
ctx.addRefUpdate(command);
- args.submoduleOp.addBranchTip(getDest(), tipAfter);
+ args.submoduleCommits.addBranchTip(getDest(), tipAfter);
}
private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit) {
@@ -535,13 +535,14 @@
*/
protected CodeReviewCommit amendGitlink(CodeReviewCommit commit)
throws IntegrationConflictException {
- if (!args.submoduleOp.hasSubscription(args.destBranch)) {
+ if (!args.subscriptionGraph.hasSubscription(args.destBranch)) {
return commit;
}
// Modify the commit with gitlink update
try {
- return args.submoduleOp.amendGitlinksCommit(args.destBranch, commit);
+ return args.submoduleCommits.amendGitlinksCommit(
+ args.destBranch, commit, args.subscriptionGraph.getSubscriptions(args.destBranch));
} catch (IOException e) {
throw new StorageException(
String.format("cannot update gitlink for the commit at branch %s", args.destBranch), e);
diff --git a/java/com/google/gerrit/server/submit/SubmoduleCommits.java b/java/com/google/gerrit/server/submit/SubmoduleCommits.java
new file mode 100644
index 0000000..1312a4b
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/SubmoduleCommits.java
@@ -0,0 +1,356 @@
+// Copyright (C) 2020 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 java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.SubmoduleSubscription;
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Create commit or amend existing one updating gitlinks. */
+class SubmoduleCommits {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final PersonIdent myIdent;
+ private final VerboseSuperprojectUpdate verboseSuperProject;
+ private final MergeOpRepoManager orm;
+ private final long maxCombinedCommitMessageSize;
+ private final long maxCommitMessages;
+ private final BranchTips branchTips = new BranchTips();
+
+ @Singleton
+ public static class Factory {
+ private final Provider<PersonIdent> serverIdent;
+ private final Config cfg;
+
+ @Inject
+ Factory(@GerritPersonIdent Provider<PersonIdent> serverIdent, @GerritServerConfig Config cfg) {
+ this.serverIdent = serverIdent;
+ this.cfg = cfg;
+ }
+
+ public SubmoduleCommits create(MergeOpRepoManager orm) {
+ return new SubmoduleCommits(orm, serverIdent.get(), cfg);
+ }
+ }
+
+ SubmoduleCommits(MergeOpRepoManager orm, PersonIdent myIdent, Config cfg) {
+ this.orm = orm;
+ this.myIdent = myIdent;
+ this.verboseSuperProject =
+ cfg.getEnum("submodule", null, "verboseSuperprojectUpdate", VerboseSuperprojectUpdate.TRUE);
+ this.maxCombinedCommitMessageSize =
+ cfg.getLong("submodule", "maxCombinedCommitMessageSize", 256 << 10);
+ this.maxCommitMessages = cfg.getLong("submodule", "maxCommitMessages", 1000);
+ }
+
+ /**
+ * Use the commit as tip of the branch
+ *
+ * <p>This keeps track of the tip of the branch as the submission progresses.
+ */
+ void addBranchTip(BranchNameKey branch, CodeReviewCommit tip) {
+ branchTips.put(branch, tip);
+ }
+
+ /**
+ * Create a separate gitlink commit
+ *
+ * @param subscriber superproject (and branch)
+ * @param subscriptions subprojects the superproject is subscribed to
+ * @return a new commit on top of subscriber with gitlinks update to the tips of the subprojects;
+ * empty if nothing has changed. Subproject tips are read from the cached branched tips
+ * (defaulting to the mergeOpRepoManager).
+ */
+ Optional<CodeReviewCommit> composeGitlinksCommit(
+ BranchNameKey subscriber, Collection<SubmoduleSubscription> subscriptions)
+ throws IOException, SubmoduleConflictException {
+ OpenRepo or;
+ try {
+ or = orm.getRepo(subscriber.project());
+ } catch (NoSuchProjectException | IOException e) {
+ throw new StorageException("Cannot access superproject", e);
+ }
+
+ CodeReviewCommit currentCommit =
+ branchTips
+ .getTip(subscriber, or)
+ .orElseThrow(
+ () ->
+ new SubmoduleConflictException(
+ "The branch was probably deleted from the subscriber repository"));
+
+ StringBuilder msgbuf = new StringBuilder();
+ PersonIdent author = null;
+ DirCache dc = readTree(or.getCodeReviewRevWalk(), currentCommit);
+ DirCacheEditor ed = dc.editor();
+ int count = 0;
+
+ for (SubmoduleSubscription s : sortByPath(subscriptions)) {
+ if (count > 0) {
+ msgbuf.append("\n\n");
+ }
+ RevCommit newCommit = updateSubmodule(dc, ed, msgbuf, s);
+ count++;
+ if (newCommit != null) {
+ PersonIdent newCommitAuthor = newCommit.getAuthorIdent();
+ if (author == null) {
+ author = new PersonIdent(newCommitAuthor, myIdent.getWhen());
+ } else if (!author.getName().equals(newCommitAuthor.getName())
+ || !author.getEmailAddress().equals(newCommitAuthor.getEmailAddress())) {
+ author = myIdent;
+ }
+ }
+ }
+ ed.finish();
+ ObjectId newTreeId = dc.writeTree(or.ins);
+
+ // Gitlinks are already in the branch, return null
+ if (newTreeId.equals(currentCommit.getTree())) {
+ return Optional.empty();
+ }
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(newTreeId);
+ commit.setParentId(currentCommit);
+ StringBuilder commitMsg = new StringBuilder("Update git submodules\n\n");
+ if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
+ commitMsg.append(msgbuf);
+ }
+ commit.setMessage(commitMsg.toString());
+ commit.setAuthor(author);
+ commit.setCommitter(myIdent);
+ ObjectId id = or.ins.insert(commit);
+ return Optional.of(or.getCodeReviewRevWalk().parseCommit(id));
+ }
+
+ /** Amend an existing commit with gitlink updates */
+ CodeReviewCommit amendGitlinksCommit(
+ BranchNameKey subscriber,
+ CodeReviewCommit currentCommit,
+ Collection<SubmoduleSubscription> subscriptions)
+ throws IOException, SubmoduleConflictException {
+ OpenRepo or;
+ try {
+ or = orm.getRepo(subscriber.project());
+ } catch (NoSuchProjectException | IOException e) {
+ throw new StorageException("Cannot access superproject", e);
+ }
+
+ StringBuilder msgbuf = new StringBuilder();
+ DirCache dc = readTree(or.rw, currentCommit);
+ DirCacheEditor ed = dc.editor();
+ for (SubmoduleSubscription s : sortByPath(subscriptions)) {
+ updateSubmodule(dc, ed, msgbuf, s);
+ }
+ ed.finish();
+ ObjectId newTreeId = dc.writeTree(or.ins);
+
+ // Gitlinks are already updated, just return the commit
+ if (newTreeId.equals(currentCommit.getTree())) {
+ return currentCommit;
+ }
+ or.rw.parseBody(currentCommit);
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(newTreeId);
+ commit.setParentIds(currentCommit.getParents());
+ if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
+ // TODO(czhen): handle cherrypick footer
+ commit.setMessage(currentCommit.getFullMessage() + "\n\n* submodules:\n" + msgbuf.toString());
+ } else {
+ commit.setMessage(currentCommit.getFullMessage());
+ }
+ commit.setAuthor(currentCommit.getAuthorIdent());
+ commit.setCommitter(myIdent);
+ ObjectId id = or.ins.insert(commit);
+ CodeReviewCommit newCommit = or.getCodeReviewRevWalk().parseCommit(id);
+ newCommit.copyFrom(currentCommit);
+ return newCommit;
+ }
+
+ private RevCommit updateSubmodule(
+ DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
+ throws SubmoduleConflictException, IOException {
+ logger.atFine().log("Updating gitlink for %s", s);
+ OpenRepo subOr;
+ try {
+ subOr = orm.getRepo(s.getSubmodule().project());
+ } catch (NoSuchProjectException | IOException e) {
+ throw new StorageException("Cannot access submodule", e);
+ }
+
+ DirCacheEntry dce = dc.getEntry(s.getPath());
+ RevCommit oldCommit = null;
+ if (dce != null) {
+ if (!dce.getFileMode().equals(FileMode.GITLINK)) {
+ String errMsg =
+ "Requested to update gitlink "
+ + s.getPath()
+ + " in "
+ + s.getSubmodule().project().get()
+ + " but entry "
+ + "doesn't have gitlink file mode.";
+ throw new SubmoduleConflictException(errMsg);
+ }
+ // Parse the current gitlink entry commit in the subproject repo. This is used to add a
+ // shortlog for this submodule to the commit message in the superproject.
+ //
+ // Even if we don't strictly speaking need that commit message, parsing the commit is a sanity
+ // check that the old gitlink is a commit that actually exists. If not, then there is an
+ // inconsistency between the superproject and subproject state, and we don't want to risk
+ // making things worse by updating the gitlink to something else.
+ try {
+ oldCommit = subOr.getCodeReviewRevWalk().parseCommit(dce.getObjectId());
+ } catch (IOException e) {
+ // Broken gitlink; sanity check failed. Warn and continue so the submit operation can
+ // proceed, it will just skip this gitlink update.
+ logger.atSevere().withCause(e).log("Failed to read commit %s", dce.getObjectId().name());
+ return null;
+ }
+ }
+
+ Optional<CodeReviewCommit> maybeNewCommit = branchTips.getTip(s.getSubmodule(), subOr);
+ if (!maybeNewCommit.isPresent()) {
+ // For whatever reason, this submodule was not updated as part of this submit batch, but the
+ // superproject is still subscribed to this branch. Re-read the ref to see if anything has
+ // changed since the last time the gitlink was updated, and roll that update into the same
+ // commit as all other submodule updates.
+ ed.add(new DeletePath(s.getPath()));
+ return null;
+ }
+
+ CodeReviewCommit newCommit = maybeNewCommit.get();
+ if (Objects.equals(newCommit, oldCommit)) {
+ // gitlink have already been updated for this submodule
+ return null;
+ }
+ ed.add(
+ new PathEdit(s.getPath()) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(newCommit.getId());
+ }
+ });
+
+ if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
+ createSubmoduleCommitMsg(msgbuf, s, subOr, newCommit, oldCommit);
+ }
+ subOr.getCodeReviewRevWalk().parseBody(newCommit);
+ return newCommit;
+ }
+
+ private void createSubmoduleCommitMsg(
+ StringBuilder msgbuf,
+ SubmoduleSubscription s,
+ OpenRepo subOr,
+ RevCommit newCommit,
+ RevCommit oldCommit) {
+ msgbuf.append("* Update ");
+ msgbuf.append(s.getPath());
+ msgbuf.append(" from branch '");
+ msgbuf.append(s.getSubmodule().shortName());
+ msgbuf.append("'");
+ msgbuf.append("\n to ");
+ msgbuf.append(newCommit.getName());
+
+ // newly created submodule gitlink, do not append whole history
+ if (oldCommit == null) {
+ return;
+ }
+
+ try {
+ subOr.rw.resetRetain(subOr.canMergeFlag);
+ subOr.rw.markStart(newCommit);
+ subOr.rw.markUninteresting(oldCommit);
+ int numMessages = 0;
+ for (Iterator<RevCommit> iter = subOr.rw.iterator(); iter.hasNext(); ) {
+ RevCommit c = iter.next();
+ subOr.rw.parseBody(c);
+
+ String message =
+ verboseSuperProject == VerboseSuperprojectUpdate.SUBJECT_ONLY
+ ? c.getShortMessage()
+ : StringUtils.replace(c.getFullMessage(), "\n", "\n ");
+
+ String bullet = "\n - ";
+ String ellipsis = "\n\n[...]";
+ int newSize = msgbuf.length() + bullet.length() + message.length();
+ if (++numMessages > maxCommitMessages
+ || newSize > maxCombinedCommitMessageSize
+ || (iter.hasNext() && (newSize + ellipsis.length()) > maxCombinedCommitMessageSize)) {
+ msgbuf.append(ellipsis);
+ break;
+ }
+ msgbuf.append(bullet);
+ msgbuf.append(message);
+ }
+ } catch (IOException e) {
+ throw new StorageException(
+ "Could not perform a revwalk to create superproject commit message", e);
+ }
+ }
+
+ private static DirCache readTree(RevWalk rw, ObjectId base) throws IOException {
+ final DirCache dc = DirCache.newInCore();
+ final DirCacheBuilder b = dc.builder();
+ b.addTree(
+ new byte[0], // no prefix path
+ DirCacheEntry.STAGE_0, // standard stage
+ rw.getObjectReader(),
+ rw.parseTree(base));
+ b.finish();
+ return dc;
+ }
+
+ private static List<SubmoduleSubscription> sortByPath(
+ Collection<SubmoduleSubscription> subscriptions) {
+ return subscriptions.stream()
+ .sorted(comparing(SubmoduleSubscription::getPath))
+ .collect(toList());
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/SubmoduleOp.java b/java/com/google/gerrit/server/submit/SubmoduleOp.java
index a1ed373..8bfcd51 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleOp.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleOp.java
@@ -14,130 +14,60 @@
package com.google.gerrit.server.submit;
-import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toList;
-
import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.SubmoduleSubscription;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
-import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateListener;
-import com.google.gerrit.server.update.RepoContext;
-import com.google.gerrit.server.update.RepoOnlyOp;
import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
-import org.apache.commons.lang.StringUtils;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEditor;
-import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
-import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
public class SubmoduleOp {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- /** Only used for branches without code review changes */
- public class GitlinkOp implements RepoOnlyOp {
- private final BranchNameKey branch;
- private final BranchTips currentBranchTips;
-
- GitlinkOp(BranchNameKey branch, BranchTips branchTips) {
- this.branch = branch;
- this.currentBranchTips = branchTips;
- }
-
- @Override
- public void updateRepo(RepoContext ctx) throws Exception {
- CodeReviewCommit c = composeGitlinksCommit(branch);
- if (c != null) {
- ctx.addRefUpdate(c.getParent(0), c, branch.branch());
- currentBranchTips.put(branch, c);
- }
- }
- }
@Singleton
public static class Factory {
private final SubscriptionGraph.Factory subscriptionGraphFactory;
- private final Provider<PersonIdent> serverIdent;
- private final Config cfg;
+ private final SubmoduleCommits.Factory submoduleCommitsFactory;
@Inject
Factory(
SubscriptionGraph.Factory subscriptionGraphFactory,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
- @GerritServerConfig Config cfg) {
+ SubmoduleCommits.Factory submoduleCommitsFactory) {
this.subscriptionGraphFactory = subscriptionGraphFactory;
- this.serverIdent = serverIdent;
- this.cfg = cfg;
+ this.submoduleCommitsFactory = submoduleCommitsFactory;
}
public SubmoduleOp create(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
throws SubmoduleConflictException {
- SubscriptionGraph subscriptionGraph;
- if (cfg.getBoolean("submodule", "enableSuperProjectSubscriptions", true)) {
- subscriptionGraph = subscriptionGraphFactory.compute(updatedBranches, orm);
- } else {
- logger.atFine().log("Updating superprojects disabled");
- subscriptionGraph =
- SubscriptionGraph.createEmptyGraph(ImmutableSet.copyOf(updatedBranches));
- }
- return new SubmoduleOp(serverIdent.get(), cfg, orm, subscriptionGraph);
+ return new SubmoduleOp(
+ orm,
+ subscriptionGraphFactory.compute(updatedBranches, orm),
+ submoduleCommitsFactory.create(orm));
}
}
- private final PersonIdent myIdent;
- private final VerboseSuperprojectUpdate verboseSuperProject;
- private final long maxCombinedCommitMessageSize;
- private final long maxCommitMessages;
private final MergeOpRepoManager orm;
private final SubscriptionGraph subscriptionGraph;
-
- private final BranchTips branchTips = new BranchTips();
+ private final SubmoduleCommits submoduleCommits;
+ private final UpdateOrderCalculator updateOrderCalculator;
private SubmoduleOp(
- PersonIdent myIdent,
- Config cfg,
MergeOpRepoManager orm,
- SubscriptionGraph subscriptionGraph) {
- this.myIdent = myIdent;
- this.verboseSuperProject =
- cfg.getEnum("submodule", null, "verboseSuperprojectUpdate", VerboseSuperprojectUpdate.TRUE);
- this.maxCombinedCommitMessageSize =
- cfg.getLong("submodule", "maxCombinedCommitMessageSize", 256 << 10);
- this.maxCommitMessages = cfg.getLong("submodule", "maxCommitMessages", 1000);
+ SubscriptionGraph subscriptionGraph,
+ SubmoduleCommits submoduleCommits) {
this.orm = orm;
this.subscriptionGraph = subscriptionGraph;
+ this.submoduleCommits = submoduleCommits;
+ this.updateOrderCalculator = new UpdateOrderCalculator(subscriptionGraph);
}
@UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
@@ -146,13 +76,15 @@
}
public void updateSuperProjects() throws RestApiException {
- ImmutableSet<Project.NameKey> projects = getProjectsInOrder();
+ ImmutableSet<Project.NameKey> projects = updateOrderCalculator.getProjectsInOrder();
if (projects == null) {
return;
}
LinkedHashSet<Project.NameKey> superProjects = new LinkedHashSet<>();
try {
+ GitlinkOp.Factory gitlinkOpFactory =
+ new GitlinkOp.Factory(submoduleCommits, subscriptionGraph);
for (Project.NameKey project : projects) {
// only need superprojects
if (subscriptionGraph.isAffectedSuperProject(project)) {
@@ -160,7 +92,7 @@
// get a new BatchUpdate for the super project
OpenRepo or = orm.getRepo(project);
for (BranchNameKey branch : subscriptionGraph.getAffectedSuperBranches(project)) {
- addOp(or.getUpdate(), branch);
+ or.getUpdate().addRepoOnlyOp(gitlinkOpFactory.create(branch));
}
}
}
@@ -169,306 +101,4 @@
throw new StorageException("Cannot update gitlinks", e);
}
}
-
- /** Create a separate gitlink commit */
- private CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber)
- throws IOException, SubmoduleConflictException {
- OpenRepo or;
- try {
- or = orm.getRepo(subscriber.project());
- } catch (NoSuchProjectException | IOException e) {
- throw new StorageException("Cannot access superproject", e);
- }
-
- CodeReviewCommit currentCommit =
- branchTips
- .getTip(subscriber, or)
- .orElseThrow(
- () ->
- new SubmoduleConflictException(
- "The branch was probably deleted from the subscriber repository"));
-
- StringBuilder msgbuf = new StringBuilder();
- PersonIdent author = null;
- DirCache dc = readTree(or.rw, currentCommit);
- DirCacheEditor ed = dc.editor();
- int count = 0;
-
- List<SubmoduleSubscription> subscriptions =
- subscriptionGraph.getSubscriptions(subscriber).stream()
- .sorted(comparing(SubmoduleSubscription::getPath))
- .collect(toList());
- for (SubmoduleSubscription s : subscriptions) {
- if (count > 0) {
- msgbuf.append("\n\n");
- }
- RevCommit newCommit = updateSubmodule(dc, ed, msgbuf, s);
- count++;
- if (newCommit != null) {
- PersonIdent newCommitAuthor = newCommit.getAuthorIdent();
- if (author == null) {
- author = new PersonIdent(newCommitAuthor, myIdent.getWhen());
- } else if (!author.getName().equals(newCommitAuthor.getName())
- || !author.getEmailAddress().equals(newCommitAuthor.getEmailAddress())) {
- author = myIdent;
- }
- }
- }
- ed.finish();
- ObjectId newTreeId = dc.writeTree(or.ins);
-
- // Gitlinks are already in the branch, return null
- if (newTreeId.equals(currentCommit.getTree())) {
- return null;
- }
- CommitBuilder commit = new CommitBuilder();
- commit.setTreeId(newTreeId);
- commit.setParentId(currentCommit);
- StringBuilder commitMsg = new StringBuilder("Update git submodules\n\n");
- if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
- commitMsg.append(msgbuf);
- }
- commit.setMessage(commitMsg.toString());
- commit.setAuthor(author);
- commit.setCommitter(myIdent);
- ObjectId id = or.ins.insert(commit);
- return or.rw.parseCommit(id);
- }
-
- /** Amend an existing commit with gitlink updates */
- CodeReviewCommit amendGitlinksCommit(BranchNameKey subscriber, CodeReviewCommit currentCommit)
- throws IOException, SubmoduleConflictException {
- OpenRepo or;
- try {
- or = orm.getRepo(subscriber.project());
- } catch (NoSuchProjectException | IOException e) {
- throw new StorageException("Cannot access superproject", e);
- }
-
- StringBuilder msgbuf = new StringBuilder();
- DirCache dc = readTree(or.rw, currentCommit);
- DirCacheEditor ed = dc.editor();
- for (SubmoduleSubscription s : subscriptionGraph.getSubscriptions(subscriber)) {
- updateSubmodule(dc, ed, msgbuf, s);
- }
- ed.finish();
- ObjectId newTreeId = dc.writeTree(or.ins);
-
- // Gitlinks are already updated, just return the commit
- if (newTreeId.equals(currentCommit.getTree())) {
- return currentCommit;
- }
- or.rw.parseBody(currentCommit);
- CommitBuilder commit = new CommitBuilder();
- commit.setTreeId(newTreeId);
- commit.setParentIds(currentCommit.getParents());
- if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
- // TODO(czhen): handle cherrypick footer
- commit.setMessage(currentCommit.getFullMessage() + "\n\n* submodules:\n" + msgbuf.toString());
- } else {
- commit.setMessage(currentCommit.getFullMessage());
- }
- commit.setAuthor(currentCommit.getAuthorIdent());
- commit.setCommitter(myIdent);
- ObjectId id = or.ins.insert(commit);
- CodeReviewCommit newCommit = or.rw.parseCommit(id);
- newCommit.copyFrom(currentCommit);
- return newCommit;
- }
-
- private RevCommit updateSubmodule(
- DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
- throws SubmoduleConflictException, IOException {
- logger.atFine().log("Updating gitlink for %s", s);
- OpenRepo subOr;
- try {
- subOr = orm.getRepo(s.getSubmodule().project());
- } catch (NoSuchProjectException | IOException e) {
- throw new StorageException("Cannot access submodule", e);
- }
-
- DirCacheEntry dce = dc.getEntry(s.getPath());
- RevCommit oldCommit = null;
- if (dce != null) {
- if (!dce.getFileMode().equals(FileMode.GITLINK)) {
- String errMsg =
- "Requested to update gitlink "
- + s.getPath()
- + " in "
- + s.getSubmodule().project().get()
- + " but entry "
- + "doesn't have gitlink file mode.";
- throw new SubmoduleConflictException(errMsg);
- }
- // Parse the current gitlink entry commit in the subproject repo. This is used to add a
- // shortlog for this submodule to the commit message in the superproject.
- //
- // Even if we don't strictly speaking need that commit message, parsing the commit is a sanity
- // check that the old gitlink is a commit that actually exists. If not, then there is an
- // inconsistency between the superproject and subproject state, and we don't want to risk
- // making things worse by updating the gitlink to something else.
- try {
- oldCommit = subOr.rw.parseCommit(dce.getObjectId());
- } catch (IOException e) {
- // Broken gitlink; sanity check failed. Warn and continue so the submit operation can
- // proceed, it will just skip this gitlink update.
- logger.atSevere().withCause(e).log("Failed to read commit %s", dce.getObjectId().name());
- return null;
- }
- }
-
- Optional<CodeReviewCommit> maybeNewCommit = branchTips.getTip(s.getSubmodule(), subOr);
- if (!maybeNewCommit.isPresent()) {
- // This submodule branch is neither in the submit set nor in the repository itself
- ed.add(new DeletePath(s.getPath()));
- return null;
- }
-
- CodeReviewCommit newCommit = maybeNewCommit.get();
-
- if (Objects.equals(newCommit, oldCommit)) {
- // gitlink have already been updated for this submodule
- return null;
- }
- ed.add(
- new PathEdit(s.getPath()) {
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(FileMode.GITLINK);
- ent.setObjectId(newCommit.getId());
- }
- });
-
- if (verboseSuperProject != VerboseSuperprojectUpdate.FALSE) {
- createSubmoduleCommitMsg(msgbuf, s, subOr, newCommit, oldCommit);
- }
- subOr.rw.parseBody(newCommit);
- return newCommit;
- }
-
- private void createSubmoduleCommitMsg(
- StringBuilder msgbuf,
- SubmoduleSubscription s,
- OpenRepo subOr,
- RevCommit newCommit,
- RevCommit oldCommit) {
- msgbuf.append("* Update ");
- msgbuf.append(s.getPath());
- msgbuf.append(" from branch '");
- msgbuf.append(s.getSubmodule().shortName());
- msgbuf.append("'");
- msgbuf.append("\n to ");
- msgbuf.append(newCommit.getName());
-
- // newly created submodule gitlink, do not append whole history
- if (oldCommit == null) {
- return;
- }
-
- try {
- subOr.rw.resetRetain(subOr.canMergeFlag);
- subOr.rw.markStart(newCommit);
- subOr.rw.markUninteresting(oldCommit);
- int numMessages = 0;
- for (Iterator<RevCommit> iter = subOr.rw.iterator(); iter.hasNext(); ) {
- RevCommit c = iter.next();
- subOr.rw.parseBody(c);
-
- String message =
- verboseSuperProject == VerboseSuperprojectUpdate.SUBJECT_ONLY
- ? c.getShortMessage()
- : StringUtils.replace(c.getFullMessage(), "\n", "\n ");
-
- String bullet = "\n - ";
- String ellipsis = "\n\n[...]";
- int newSize = msgbuf.length() + bullet.length() + message.length();
- if (++numMessages > maxCommitMessages
- || newSize > maxCombinedCommitMessageSize
- || (iter.hasNext() && (newSize + ellipsis.length()) > maxCombinedCommitMessageSize)) {
- msgbuf.append(ellipsis);
- break;
- }
- msgbuf.append(bullet);
- msgbuf.append(message);
- }
- } catch (IOException e) {
- throw new StorageException(
- "Could not perform a revwalk to create superproject commit message", e);
- }
- }
-
- private static DirCache readTree(RevWalk rw, ObjectId base) throws IOException {
- final DirCache dc = DirCache.newInCore();
- final DirCacheBuilder b = dc.builder();
- b.addTree(
- new byte[0], // no prefix path
- DirCacheEntry.STAGE_0, // standard stage
- rw.getObjectReader(),
- rw.parseTree(base));
- b.finish();
- return dc;
- }
-
- ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleConflictException {
- LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
- for (Project.NameKey project : subscriptionGraph.getAffectedSuperProjects()) {
- addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
- }
-
- for (BranchNameKey branch : subscriptionGraph.getUpdatedBranches()) {
- projects.add(branch.project());
- }
- return ImmutableSet.copyOf(projects);
- }
-
- private void addAllSubmoduleProjects(
- Project.NameKey project,
- LinkedHashSet<Project.NameKey> current,
- LinkedHashSet<Project.NameKey> projects)
- throws SubmoduleConflictException {
- if (current.contains(project)) {
- throw new SubmoduleConflictException(
- "Project level circular subscriptions detected: "
- + CircularPathFinder.printCircularPath(current, project));
- }
-
- if (projects.contains(project)) {
- return;
- }
-
- current.add(project);
- Set<Project.NameKey> subprojects = new HashSet<>();
- for (BranchNameKey branch : subscriptionGraph.getAffectedSuperBranches(project)) {
- Collection<SubmoduleSubscription> subscriptions = subscriptionGraph.getSubscriptions(branch);
- for (SubmoduleSubscription s : subscriptions) {
- subprojects.add(s.getSubmodule().project());
- }
- }
-
- for (Project.NameKey p : subprojects) {
- addAllSubmoduleProjects(p, current, projects);
- }
-
- current.remove(project);
- projects.add(project);
- }
-
- ImmutableSet<BranchNameKey> getBranchesInOrder() {
- LinkedHashSet<BranchNameKey> branches = new LinkedHashSet<>();
- branches.addAll(subscriptionGraph.getSortedSuperprojectAndSubmoduleBranches());
- branches.addAll(subscriptionGraph.getUpdatedBranches());
- return ImmutableSet.copyOf(branches);
- }
-
- boolean hasSubscription(BranchNameKey branch) {
- return subscriptionGraph.hasSubscription(branch);
- }
-
- void addBranchTip(BranchNameKey branch, CodeReviewCommit tip) {
- branchTips.put(branch, tip);
- }
-
- void addOp(BatchUpdate bu, BranchNameKey branch) {
- bu.addRepoOnlyOp(new GitlinkOp(branch, branchTips));
- }
}
diff --git a/java/com/google/gerrit/server/submit/SubscriptionGraph.java b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
index b08e46d1..b11b2be 100644
--- a/java/com/google/gerrit/server/submit/SubscriptionGraph.java
+++ b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
@@ -159,7 +159,7 @@
public static class Module extends AbstractModule {
@Override
protected void configure() {
- bind(Factory.class).to(DefaultFactory.class);
+ bind(Factory.class).annotatedWith(VanillaSubscriptionGraph.class).to(DefaultFactory.class);
}
}
diff --git a/java/com/google/gerrit/server/submit/UpdateOrderCalculator.java b/java/com/google/gerrit/server/submit/UpdateOrderCalculator.java
new file mode 100644
index 0000000..517c708
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/UpdateOrderCalculator.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2020 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 com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmoduleSubscription;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Sorts the projects or branches affected by the update.
+ *
+ * <p>The subscription graph contains all branches (and projects) affected by the update, but the
+ * updates must be executed in the right order, so no superproject reference is updated before its
+ * target.
+ */
+class UpdateOrderCalculator {
+
+ private final SubscriptionGraph subscriptionGraph;
+
+ UpdateOrderCalculator(SubscriptionGraph subscriptionGraph) {
+ this.subscriptionGraph = subscriptionGraph;
+ }
+
+ ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleConflictException {
+ LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
+ for (Project.NameKey project : subscriptionGraph.getAffectedSuperProjects()) {
+ addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
+ }
+
+ for (BranchNameKey branch : subscriptionGraph.getUpdatedBranches()) {
+ projects.add(branch.project());
+ }
+ return ImmutableSet.copyOf(projects);
+ }
+
+ private void addAllSubmoduleProjects(
+ Project.NameKey project,
+ LinkedHashSet<Project.NameKey> current,
+ LinkedHashSet<Project.NameKey> projects)
+ throws SubmoduleConflictException {
+ if (current.contains(project)) {
+ throw new SubmoduleConflictException(
+ "Project level circular subscriptions detected: "
+ + CircularPathFinder.printCircularPath(current, project));
+ }
+
+ if (projects.contains(project)) {
+ return;
+ }
+
+ current.add(project);
+ Set<Project.NameKey> subprojects = new HashSet<>();
+ for (BranchNameKey branch : subscriptionGraph.getAffectedSuperBranches(project)) {
+ Collection<SubmoduleSubscription> subscriptions = subscriptionGraph.getSubscriptions(branch);
+ for (SubmoduleSubscription s : subscriptions) {
+ subprojects.add(s.getSubmodule().project());
+ }
+ }
+
+ for (Project.NameKey p : subprojects) {
+ addAllSubmoduleProjects(p, current, projects);
+ }
+
+ current.remove(project);
+ projects.add(project);
+ }
+
+ ImmutableSet<BranchNameKey> getBranchesInOrder() {
+ LinkedHashSet<BranchNameKey> branches = new LinkedHashSet<>();
+ branches.addAll(subscriptionGraph.getSortedSuperprojectAndSubmoduleBranches());
+ branches.addAll(subscriptionGraph.getUpdatedBranches());
+ return ImmutableSet.copyOf(branches);
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/VanillaSubscriptionGraph.java b/java/com/google/gerrit/server/submit/VanillaSubscriptionGraph.java
new file mode 100644
index 0000000..a88157e
--- /dev/null
+++ b/java/com/google/gerrit/server/submit/VanillaSubscriptionGraph.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 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 java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+/**
+ * Marker on a {@link SubscriptionGraph.Factory} without gerrit configuration.
+ *
+ * <p>See {@link ConfiguredSubscriptionGraphFactory}.
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface VanillaSubscriptionGraph {}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 72453fd..903ce1a 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -1775,7 +1775,7 @@
}
private void addComment(PushOneCommit.Result r, String message) throws Exception {
- addComment(r, message, false, false, null, 1, null);
+ addComment(r, message, false, false, null, null, null);
}
private void addCommentOnLine(PushOneCommit.Result r, String message, int line) throws Exception {
@@ -1801,7 +1801,7 @@
Boolean unresolved,
String inReplyTo)
throws Exception {
- addComment(r, message, omitDuplicateComments, unresolved, inReplyTo, 1, null);
+ addComment(r, message, omitDuplicateComments, unresolved, inReplyTo, null, null);
}
private void addComment(
@@ -1814,7 +1814,7 @@
Comment.Range range)
throws Exception {
CommentInput c = new CommentInput();
- c.line = 1;
+ c.line = line == null ? 1 : line;
c.message = message;
c.path = FILE_NAME;
c.unresolved = unresolved;
diff --git a/javatests/com/google/gerrit/server/submit/BUILD b/javatests/com/google/gerrit/server/submit/BUILD
new file mode 100644
index 0000000..7425bc8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/submit/BUILD
@@ -0,0 +1,21 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+ name = "submit_tests",
+ size = "small",
+ srcs = glob(
+ ["**/*.java"],
+ ),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:jgit",
+ "//lib/mockito",
+ "//lib/truth",
+ "//lib/truth:truth-java8-extension",
+ "//lib/truth:truth-proto-extension",
+ "@jgit//org.eclipse.jgit.junit:junit",
+ ],
+)
diff --git a/javatests/com/google/gerrit/server/submit/SubmoduleCommitsTest.java b/javatests/com/google/gerrit/server/submit/SubmoduleCommitsTest.java
new file mode 100644
index 0000000..313e697
--- /dev/null
+++ b/javatests/com/google/gerrit/server/submit/SubmoduleCommitsTest.java
@@ -0,0 +1,225 @@
+// Copyright (C) 2020 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.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmoduleSubscription;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
+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.submit.MergeOpRepoManager.OpenRepo;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.IOException;
+import java.util.Optional;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+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.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class SubmoduleCommitsTest {
+
+ private static final String MASTER = "refs/heads/master";
+ private static final Project.NameKey superProject = Project.nameKey("superproject");
+ private static final Project.NameKey subProject = Project.nameKey("subproject");
+
+ private static final PersonIdent ident = new PersonIdent("submodule-test", "a@b.com");
+
+ private InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
+ private MergeOpRepoManager mergeOpRepoManager;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock private ProjectCache mockProjectCache;
+ @Mock private ProjectState mockProjectState;
+
+ @Test
+ public void createGitlinksCommit_subprojectMoved() throws Exception {
+ createRepo(subProject, MASTER);
+ createRepo(superProject, MASTER);
+
+ when(mockProjectCache.get(any())).thenReturn(Optional.of(mockProjectState));
+ mergeOpRepoManager = new MergeOpRepoManager(repoManager, mockProjectCache, null, null);
+
+ ObjectId subprojectCommit = getTip(subProject, MASTER);
+ RevCommit superprojectTip =
+ directUpdateSubmodule(superProject, MASTER, Project.nameKey("dir-x"), subprojectCommit);
+ assertThat(readGitLink(superProject, superprojectTip, "dir-x")).isEqualTo(subprojectCommit);
+
+ RevCommit newSubprojectCommit = addCommit(subProject, MASTER);
+
+ BranchNameKey superBranch = BranchNameKey.create(superProject, MASTER);
+ BranchNameKey subBranch = BranchNameKey.create(subProject, MASTER);
+ SubmoduleSubscription ss = new SubmoduleSubscription(superBranch, subBranch, "dir-x");
+ SubmoduleCommits helper = new SubmoduleCommits(mergeOpRepoManager, ident, new Config());
+ Optional<CodeReviewCommit> newGitLinksCommit =
+ helper.composeGitlinksCommit(
+ BranchNameKey.create(superProject, MASTER), ImmutableList.of(ss));
+
+ assertThat(newGitLinksCommit).isPresent();
+ assertThat(newGitLinksCommit.get().getParent(0)).isEqualTo(superprojectTip);
+ assertThat(readGitLink(superProject, newGitLinksCommit.get(), "dir-x"))
+ .isEqualTo(newSubprojectCommit);
+ }
+
+ @Test
+ public void amendGitlinksCommit_subprojectMoved() throws Exception {
+ createRepo(subProject, MASTER);
+ createRepo(superProject, MASTER);
+
+ when(mockProjectCache.get(any())).thenReturn(Optional.of(mockProjectState));
+ mergeOpRepoManager = new MergeOpRepoManager(repoManager, mockProjectCache, null, null);
+
+ ObjectId subprojectCommit = getTip(subProject, MASTER);
+ CodeReviewCommit superprojectTip =
+ directUpdateSubmodule(superProject, MASTER, Project.nameKey("dir-x"), subprojectCommit);
+ assertThat(readGitLink(superProject, superprojectTip, "dir-x")).isEqualTo(subprojectCommit);
+
+ RevCommit newSubprojectCommit = addCommit(subProject, MASTER);
+
+ BranchNameKey superBranch = BranchNameKey.create(superProject, MASTER);
+ BranchNameKey subBranch = BranchNameKey.create(subProject, MASTER);
+ SubmoduleSubscription ss = new SubmoduleSubscription(superBranch, subBranch, "dir-x");
+ SubmoduleCommits helper = new SubmoduleCommits(mergeOpRepoManager, ident, new Config());
+ CodeReviewCommit amendedCommit =
+ helper.amendGitlinksCommit(
+ BranchNameKey.create(superProject, MASTER), superprojectTip, ImmutableList.of(ss));
+
+ assertThat(amendedCommit.getParent(0)).isEqualTo(superprojectTip.getParent(0));
+ assertThat(readGitLink(superProject, amendedCommit, "dir-x")).isEqualTo(newSubprojectCommit);
+ }
+
+ /** Create repo with a commit on refName */
+ private void createRepo(Project.NameKey projectKey, String refName) throws Exception {
+ Repository repo = repoManager.createRepository(projectKey);
+ try (TestRepository<Repository> git = new TestRepository<>(repo)) {
+ RevCommit newCommit = git.commit().message("Initial commit for " + projectKey).create();
+ git.update(refName, newCommit);
+ }
+ }
+
+ private ObjectId getTip(Project.NameKey projectKey, String refName)
+ throws RepositoryNotFoundException, IOException {
+ return repoManager.openRepository(projectKey).exactRef(refName).getObjectId();
+ }
+
+ private RevCommit addCommit(Project.NameKey projectKey, String refName) throws Exception {
+ try (Repository serverRepo = repoManager.openRepository(projectKey);
+ RevWalk rw = new RevWalk(serverRepo);
+ TestRepository<Repository> git = new TestRepository<>(serverRepo, rw)) {
+ Ref ref = serverRepo.exactRef(refName);
+ assertWithMessage(refName).that(ref).isNotNull();
+
+ RevCommit originalTip = rw.parseCommit(ref.getObjectId());
+ RevCommit newTip =
+ git.commit().parent(originalTip).message("Added commit to " + projectKey).create();
+ git.update(refName, newTip);
+ return newTip;
+ }
+ }
+
+ private CodeReviewCommit directUpdateSubmodule(
+ Project.NameKey project, String refName, Project.NameKey path, AnyObjectId id)
+ throws Exception {
+ OpenRepo or = mergeOpRepoManager.getRepo(project);
+ Repository serverRepo = or.repo;
+ ObjectInserter ins = or.ins;
+ CodeReviewRevWalk rw = or.rw;
+ Ref ref = serverRepo.exactRef(refName);
+ assertWithMessage(refName).that(ref).isNotNull();
+ ObjectId oldCommitId = ref.getObjectId();
+
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(new byte[0], DirCacheEntry.STAGE_0, rw.getObjectReader(), rw.parseTree(oldCommitId));
+ b.finish();
+ DirCacheEditor e = dc.editor();
+ e.add(
+ new PathEdit(path.get()) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(id);
+ }
+ });
+ e.finish();
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.addParentId(oldCommitId);
+ cb.setTreeId(dc.writeTree(ins));
+
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ cb.setMessage("Direct update submodule " + path);
+ ObjectId newCommitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate ru = serverRepo.updateRef(refName);
+ ru.setExpectedOldObjectId(oldCommitId);
+ ru.setNewObjectId(newCommitId);
+ assertThat(ru.update()).isEqualTo(RefUpdate.Result.FAST_FORWARD);
+ return rw.parseCommit(newCommitId);
+ }
+
+ private ObjectId readGitLink(Project.NameKey projectKey, RevCommit commit, String path)
+ throws IOException, NoSuchProjectException {
+ // SubmoduleCommitHelper used mergeOpRepoManager to create the commit
+ // Read the repo from mergeOpRepoManager to get also the RevWalk that created the commit
+ return readGitLinkInCommit(mergeOpRepoManager.getRepo(projectKey).rw, commit, path);
+ }
+
+ private ObjectId readGitLinkInCommit(RevWalk rw, RevCommit commit, String path)
+ throws IOException {
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(
+ new byte[0], // no prefix path
+ DirCacheEntry.STAGE_0, // standard stage
+ rw.getObjectReader(),
+ commit.getTree());
+ b.finish();
+ DirCacheEntry entry = dc.getEntry(path);
+ assertThat(entry.getFileMode()).isEqualTo(FileMode.GITLINK);
+ return entry.getObjectId();
+ }
+}