Merge changes from topic 'submodule-mergeop'

* changes:
  Refactor composeGitlinksCommit in SubmoduleOp
  Add gitlink update for superproject changes in MergeOp
  Add more tests for submodule subscriptions
  Update projects by topological order.
This commit is contained in:
Dave Borowitz
2016-07-22 18:36:59 +00:00
committed by Gerrit Code Review
14 changed files with 760 additions and 237 deletions

View File

@@ -42,6 +42,7 @@ import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.EditInfo;
@@ -415,15 +416,28 @@ public abstract class AbstractDaemonTest {
protected Project.NameKey createProject(String nameSuffix,
Project.NameKey parent) throws RestApiException {
// Default for createEmptyCommit should match TestProjectConfig.
return createProject(nameSuffix, parent, true);
return createProject(nameSuffix, parent, true, null);
}
protected Project.NameKey createProject(String nameSuffix,
Project.NameKey parent, boolean createEmptyCommit)
Project.NameKey parent, boolean createEmptyCommit) throws RestApiException {
// Default for createEmptyCommit should match TestProjectConfig.
return createProject(nameSuffix, parent, createEmptyCommit, null);
}
protected Project.NameKey createProject(String nameSuffix,
Project.NameKey parent, SubmitType submitType) throws RestApiException {
// Default for createEmptyCommit should match TestProjectConfig.
return createProject(nameSuffix, parent, true, submitType);
}
protected Project.NameKey createProject(String nameSuffix,
Project.NameKey parent, boolean createEmptyCommit, SubmitType submitType)
throws RestApiException {
ProjectInput in = new ProjectInput();
in.name = name(nameSuffix);
in.parent = parent != null ? parent.get() : null;
in.submitType = submitType;
in.createEmptyCommit = createEmptyCommit;
return createProject(in);
}

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
@@ -40,14 +41,52 @@ import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
protected SubmitType getSubmitType() {
return cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
}
protected static Config submitByMergeAlways() {
Config cfg = new Config();
cfg.setBoolean("change", null, "submitWholeTopic", true);
cfg.setEnum("project", null, "submitType", SubmitType.MERGE_ALWAYS);
return cfg;
}
protected static Config submitByMergeIfNecessary() {
Config cfg = new Config();
cfg.setBoolean("change", null, "submitWholeTopic", true);
cfg.setEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
return cfg;
}
protected static Config submitByCherryPickConifg() {
Config cfg = new Config();
cfg.setBoolean("change", null, "submitWholeTopic", true);
cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
return cfg;
}
protected static Config submitByRebaseConifg() {
Config cfg = new Config();
cfg.setBoolean("change", null, "submitWholeTopic", true);
cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
return cfg;
}
protected TestRepository<?> createProjectWithPush(String name,
@Nullable Project.NameKey parent) throws Exception {
Project.NameKey project = createProject(name, parent);
@Nullable Project.NameKey parent, SubmitType submitType) throws Exception {
Project.NameKey project = createProject(name, parent, submitType);
grant(Permission.PUSH, project, "refs/heads/*");
grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
return cloneProject(project);
}
protected TestRepository<?> createProjectWithPush(String name,
@Nullable Project.NameKey parent) throws Exception {
return createProjectWithPush(name, parent, getSubmitType());
}
protected TestRepository<?> createProjectWithPush(String name)
throws Exception {
return createProjectWithPush(name, null);
@@ -56,10 +95,11 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
private static AtomicInteger contentCounter = new AtomicInteger(0);
protected ObjectId pushChangeTo(TestRepository<?> repo, String ref,
String message, String topic) throws Exception {
String file, String content, String message, String topic)
throws Exception {
ObjectId ret = repo.branch("HEAD").commit().insertChangeId()
.message(message)
.add("a.txt", "a contents: " + contentCounter.incrementAndGet())
.add(file, content)
.create();
String pushedRef = ref;
@@ -79,6 +119,12 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
return ret;
}
protected ObjectId pushChangeTo(TestRepository<?> repo, String ref,
String message, String topic) throws Exception {
return pushChangeTo(repo, ref, "a.txt",
"a contents: " + contentCounter.incrementAndGet(), message, topic);
}
protected ObjectId pushChangeTo(TestRepository<?> repo, String branch)
throws Exception {
return pushChangeTo(repo, "refs/heads/" + branch, "some change", "");
@@ -135,16 +181,25 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
protected void prepareSubmoduleConfigEntry(Config config,
String subscribeToRepo, String subscribeToBranch) {
// The submodule subscription module checks for gerrit.canonicalWebUrl to
// detect if it's configured for automatic updates. It doesn't matter if
// it serves from that URL.
prepareSubmoduleConfigEntry(config, subscribeToRepo, subscribeToRepo, subscribeToBranch);
}
protected void prepareSubmoduleConfigEntry(Config config,
String subscribeToRepo, String subscribeToRepoPath, String subscribeToBranch) {
subscribeToRepo = name(subscribeToRepo);
subscribeToRepoPath = name(subscribeToRepoPath);
// The submodule subscription module checks for gerrit.canonicalWebUrl to
// detect if it's configured for automatic updates. It doesn't matter if
// it serves from that URL.
String url = cfg.getString("gerrit", null, "canonicalWebUrl") + "/"
+ subscribeToRepo;
config.setString("submodule", subscribeToRepo, "path", subscribeToRepo);
config.setString("submodule", subscribeToRepo, "url", url);
config.setString("submodule", subscribeToRepoPath, "path", subscribeToRepoPath);
config.setString("submodule", subscribeToRepoPath, "url", url);
if (subscribeToBranch != null) {
config.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
config.setString("submodule", subscribeToRepoPath, "branch", subscribeToBranch);
}
}
@@ -160,6 +215,27 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
new RefSpec("HEAD:refs/heads/" + branch)).call();
}
protected void expectToHaveSubmoduleState(TestRepository<?> repo,
String branch, String submodule, TestRepository<?> subRepo,
String subBranch) throws Exception {
submodule = name(submodule);
ObjectId commitId = repo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/" + branch).getObjectId();
ObjectId subHead = subRepo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/" + subBranch).getObjectId();
RevWalk rw = repo.getRevWalk();
RevCommit c = rw.parseCommit(commitId);
rw.parseBody(c.getTree());
RevTree tree = c.getTree();
RevObject actualId = repo.get(tree, submodule);
assertThat(actualId).isEqualTo(subHead);
}
protected void expectToHaveSubmoduleState(TestRepository<?> repo,
String branch, String submodule, ObjectId expectedId) throws Exception {

View File

@@ -215,21 +215,18 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
// The first update doesn't include the rev log
RevWalk rw = subRepo.getRevWalk();
RevCommit subCommitMsg = rw.parseCommit(subHEAD);
expectToHaveCommitMessage(superRepo, "master",
"Update git submodules\n\n" +
"Project: " + name("subscribed-to-project")
+ " master " + subHEAD.name() + "\n\n");
"* Update " + name("subscribed-to-project") + " from branch 'master'");
// The next commit should generate only its commit message,
// omitting previous commit logs
subHEAD = pushChangeTo(subRepo, "master");
subCommitMsg = rw.parseCommit(subHEAD);
RevCommit subCommitMsg = rw.parseCommit(subHEAD);
expectToHaveCommitMessage(superRepo, "master",
"Update git submodules\n\n" +
"Project: " + name("subscribed-to-project")
+ " master " + subHEAD.name() + "\n\n" +
subCommitMsg.getFullMessage() + "\n\n");
"* Update " + name("subscribed-to-project") + " from branch 'master'"
+ "\n - " + subCommitMsg.getFullMessage().replace("\n", "\n "));
}
@Test
@@ -302,7 +299,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
}
@Test
public void testCircularSubscriptionIsDetected() throws Exception {
public void testBranchCircularSubscription() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
@@ -325,6 +322,37 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
"subscribed-to-project")).isFalse();
}
@Test
public void testProjectCircularSubscription() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
"super-project", "refs/heads/master");
allowSubmoduleSubscription("super-project", "refs/heads/dev",
"subscribed-to-project", "refs/heads/dev");
pushChangeTo(subRepo, "master");
pushChangeTo(superRepo, "master");
pushChangeTo(subRepo, "dev");
pushChangeTo(superRepo, "dev");
createSubmoduleSubscription(superRepo, "master",
"subscribed-to-project", "master");
createSubmoduleSubscription(subRepo, "dev", "super-project", "dev");
ObjectId subMasterHead = pushChangeTo(subRepo, "master");
ObjectId superDevHead = pushChangeTo(superRepo, "dev");
assertThat(hasSubmodule(superRepo, "master",
"subscribed-to-project")).isTrue();
assertThat(hasSubmodule(subRepo, "dev",
"super-project")).isTrue();
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subMasterHead);
expectToHaveSubmoduleState(subRepo, "dev",
"super-project", superDevHead);
}
@Test
public void testSubscriptionFailOnMissingACL() throws Exception {

View File

@@ -14,11 +14,13 @@
package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.testutil.ConfigSuite;
import org.eclipse.jgit.junit.TestRepository;
@@ -33,8 +35,23 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
extends AbstractSubmoduleSubscription {
@ConfigSuite.Default
public static Config submitWholeTopicEnabled() {
return submitWholeTopicEnabledConfig();
public static Config mergeIfNecessary() {
return submitByMergeIfNecessary();
}
@ConfigSuite.Config
public static Config mergeAlways() {
return submitByMergeAlways();
}
@ConfigSuite.Config
public static Config cherryPick() {
return submitByCherryPickConifg();
}
@ConfigSuite.Config
public static Config rebase() {
return submitByRebaseConifg();
}
@Test
@@ -192,9 +209,9 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
gApi.changes().id(getChangeId(sub1, sub1Id).get()).current().submit();
expectToHaveSubmoduleState(superRepo, "master", "sub1", sub1Id);
expectToHaveSubmoduleState(superRepo, "master", "sub2", sub2Id);
expectToHaveSubmoduleState(superRepo, "master", "sub3", sub3Id);
expectToHaveSubmoduleState(superRepo, "master", "sub1", sub1, "master");
expectToHaveSubmoduleState(superRepo, "master", "sub2", sub2, "master");
expectToHaveSubmoduleState(superRepo, "master", "sub3", sub3, "master");
superRepo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/master").getObjectId();
@@ -204,4 +221,216 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
.that(superRepo.getRepository().resolve("origin/master^"))
.isEqualTo(superPreviousId);
}
@Test
public void testDifferentPaths() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> sub = createProjectWithPush("sub");
allowSubmoduleSubscription("sub", "refs/heads/master",
"super-project", "refs/heads/master");
Config config = new Config();
prepareSubmoduleConfigEntry(config, "sub", "master");
prepareSubmoduleConfigEntry(config, "sub", "sub-copy", "master");
pushSubmoduleConfig(superRepo, "master", config);
ObjectId superPreviousId = pushChangeTo(superRepo, "master");
ObjectId subId = pushChangeTo(sub, "refs/for/master", "some message", "");
approve(getChangeId(sub, subId).get());
gApi.changes().id(getChangeId(sub, subId).get()).current().submit();
expectToHaveSubmoduleState(superRepo, "master", "sub", sub, "master");
expectToHaveSubmoduleState(superRepo, "master", "sub-copy", sub, "master");
superRepo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/master").getObjectId();
assertWithMessage("submodule subscription update "
+ "should have made one commit")
.that(superRepo.getRepository().resolve("origin/master^"))
.isEqualTo(superPreviousId);
}
@Test
public void testNonSubmoduleInSameTopic() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> sub = createProjectWithPush("sub");
TestRepository<?> standAlone = createProjectWithPush("standalone");
allowSubmoduleSubscription("sub", "refs/heads/master",
"super-project", "refs/heads/master");
createSubmoduleSubscription(superRepo, "master", "sub", "master");
ObjectId superPreviousId = pushChangeTo(superRepo, "master");
ObjectId subId =
pushChangeTo(sub, "refs/for/master", "some message", "same-topic");
ObjectId standAloneId =
pushChangeTo(standAlone, "refs/for/master", "some message",
"same-topic");
String subChangeId = getChangeId(sub, subId).get();
String standAloneChangeId = getChangeId(standAlone, standAloneId).get();
approve(subChangeId);
approve(standAloneChangeId);
gApi.changes().id(subChangeId).current().submit();
expectToHaveSubmoduleState(superRepo, "master", "sub", sub, "master");
ChangeStatus status = gApi.changes().id(standAloneChangeId).info().status;
assertThat(status).isEqualTo(ChangeStatus.MERGED);
superRepo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/master").getObjectId();
assertWithMessage("submodule subscription update "
+ "should have made one commit")
.that(superRepo.getRepository().resolve("origin/master^"))
.isEqualTo(superPreviousId);
}
@Test
public void testRecursiveSubmodules() throws Exception {
TestRepository<?> topRepo = createProjectWithPush("top-project");
TestRepository<?> midRepo = createProjectWithPush("mid-project");
TestRepository<?> bottomRepo = createProjectWithPush("bottom-project");
allowSubmoduleSubscription("mid-project", "refs/heads/master",
"top-project", "refs/heads/master");
allowSubmoduleSubscription("bottom-project", "refs/heads/master",
"mid-project", "refs/heads/master");
createSubmoduleSubscription(topRepo, "master", "mid-project", "master");
createSubmoduleSubscription(midRepo, "master", "bottom-project", "master");
ObjectId bottomHead =
pushChangeTo(bottomRepo, "refs/for/master", "some message", "same-topic");
ObjectId topHead =
pushChangeTo(topRepo, "refs/for/master", "some message", "same-topic");
String id1 = getChangeId(bottomRepo, bottomHead).get();
String id2 = getChangeId(topRepo, topHead).get();
gApi.changes().id(id1).current().review(ReviewInput.approve());
gApi.changes().id(id2).current().review(ReviewInput.approve());
gApi.changes().id(id1).current().submit();
expectToHaveSubmoduleState(midRepo, "master", "bottom-project", bottomRepo, "master");
expectToHaveSubmoduleState(topRepo, "master", "mid-project", midRepo, "master");
}
@Test
public void testTriangleSubmodules() throws Exception {
TestRepository<?> topRepo = createProjectWithPush("top-project");
TestRepository<?> midRepo = createProjectWithPush("mid-project");
TestRepository<?> bottomRepo = createProjectWithPush("bottom-project");
allowSubmoduleSubscription("mid-project", "refs/heads/master",
"top-project", "refs/heads/master");
allowSubmoduleSubscription("bottom-project", "refs/heads/master",
"mid-project", "refs/heads/master");
allowSubmoduleSubscription("bottom-project", "refs/heads/master",
"top-project", "refs/heads/master");
createSubmoduleSubscription(midRepo, "master", "bottom-project", "master");
Config config = new Config();
prepareSubmoduleConfigEntry(config, "bottom-project", "master");
prepareSubmoduleConfigEntry(config, "mid-project", "master");
pushSubmoduleConfig(topRepo, "master", config);
ObjectId bottomHead =
pushChangeTo(bottomRepo, "refs/for/master", "some message", "same-topic");
ObjectId topHead =
pushChangeTo(topRepo, "refs/for/master", "some message", "same-topic");
String id1 = getChangeId(bottomRepo, bottomHead).get();
String id2 = getChangeId(topRepo, topHead).get();
gApi.changes().id(id1).current().review(ReviewInput.approve());
gApi.changes().id(id2).current().review(ReviewInput.approve());
gApi.changes().id(id1).current().submit();
expectToHaveSubmoduleState(midRepo, "master", "bottom-project", bottomRepo, "master");
expectToHaveSubmoduleState(topRepo, "master", "mid-project", midRepo, "master");
expectToHaveSubmoduleState(topRepo, "master", "bottom-project", bottomRepo, "master");
}
@Test
public void testBranchCircularSubscription() throws Exception {
TestRepository<?> topRepo = createProjectWithPush("top-project");
TestRepository<?> midRepo = createProjectWithPush("mid-project");
TestRepository<?> bottomRepo = createProjectWithPush("bottom-project");
createSubmoduleSubscription(midRepo, "master", "bottom-project", "master");
createSubmoduleSubscription(topRepo, "master", "mid-project", "master");
createSubmoduleSubscription(bottomRepo, "master", "top-project", "master");
allowSubmoduleSubscription("bottom-project", "refs/heads/master",
"mid-project", "refs/heads/master");
allowSubmoduleSubscription("mid-project", "refs/heads/master",
"top-project", "refs/heads/master");
allowSubmoduleSubscription("top-project", "refs/heads/master",
"bottom-project", "refs/heads/master");
ObjectId bottomMasterHead =
pushChangeTo(bottomRepo, "refs/for/master", "some message", "");
String changeId = getChangeId(bottomRepo, bottomMasterHead).get();
approve(changeId);
exception.expectMessage("Branch level circular subscriptions detected");
exception.expectMessage("top-project,refs/heads/master");
exception.expectMessage("mid-project,refs/heads/master");
exception.expectMessage("bottom-project,refs/heads/master");
gApi.changes().id(changeId).current().submit();
assertThat(hasSubmodule(midRepo, "master", "bottom-project")).isFalse();
assertThat(hasSubmodule(topRepo, "master", "mid-project")).isFalse();
}
@Test
public void testProjectCircularSubscriptionWholeTopic() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
"super-project", "refs/heads/master");
allowSubmoduleSubscription("super-project", "refs/heads/dev",
"subscribed-to-project", "refs/heads/dev");
pushChangeTo(subRepo, "dev");
pushChangeTo(superRepo, "dev");
createSubmoduleSubscription(superRepo, "master",
"subscribed-to-project", "master");
createSubmoduleSubscription(subRepo, "dev", "super-project", "dev");
ObjectId subMasterHead =
pushChangeTo(subRepo, "refs/for/master", "b.txt", "content b",
"some message", "same-topic");
ObjectId superDevHead =
pushChangeTo(superRepo, "refs/for/dev",
"some message", "same-topic");
approve(getChangeId(subRepo, subMasterHead).get());
approve(getChangeId(superRepo, superDevHead).get());
exception.expectMessage("Project level circular subscriptions detected");
exception.expectMessage("subscribed-to-project");
exception.expectMessage("super-project");
gApi.changes().id(getChangeId(subRepo, subMasterHead).get()).current()
.submit();
assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project"))
.isFalse();
assertThat(hasSubmodule(subRepo, "dev", "super-project")).isFalse();
}
}

View File

@@ -479,7 +479,7 @@ public class MergeOp implements AutoCloseable {
throw new IntegrationException("Error reading changes to submit", e);
}
Set<Project.NameKey> projects = br.keySet();
Collection<Branch.NameKey> branches = cbb.keySet();
Set<Branch.NameKey> branches = cbb.keySet();
openRepos(projects);
for (Branch.NameKey branch : branches) {
@@ -488,28 +488,18 @@ public class MergeOp implements AutoCloseable {
}
// Done checks that don't involve running submit strategies.
commits.maybeFailVerbose();
List<SubmitStrategy> strategies = new ArrayList<>(branches.size());
for (Branch.NameKey branch : branches) {
OpenRepo or = orm.getRepo(branch.getParentKey());
OpenBranch ob = or.getBranch(branch);
BranchBatch submitting = toSubmit.get(branch);
checkNotNull(submitting.submitType(),
"null submit type for %s; expected to previously fail fast",
submitting);
Set<CodeReviewCommit> commitsToSubmit = commits(submitting.changes());
ob.mergeTip = new MergeTip(ob.oldTip, commitsToSubmit);
SubmitStrategy strategy = createStrategy(or, ob.mergeTip, branch,
submitting.submitType(), ob.oldTip);
strategies.add(strategy);
strategy.addOps(or.getUpdate(), commitsToSubmit);
}
SubmoduleOp submoduleOp = subOpFactory.create(branches, orm);
try {
List<SubmitStrategy> strategies = getSubmitStrategies(toSubmit, submoduleOp);
Set<Project.NameKey> allProjects = submoduleOp.getProjectsInOrder();
// in case superproject subscription is disabled, allProjects would be null
if (allProjects == null) {
allProjects = projects;
}
BatchUpdate.execute(
batchUpdates(projects),
orm.batchUpdates(allProjects),
new SubmitStrategyListener(submitInput, strategies, commits));
} catch (UpdateException e) {
} catch (UpdateException | SubmoduleException e) {
// BatchUpdate may have inadvertently wrapped an IntegrationException
// thrown by some legacy SubmitStrategyOp code that intended the error
// message to be user-visible. Copy the message from the wrapped
@@ -521,20 +511,44 @@ public class MergeOp implements AutoCloseable {
if (e.getCause() instanceof IntegrationException) {
msg = e.getCause().getMessage();
} else {
msg = "Error submitting change" + (cs.size() != 1 ? "s" : "");
msg = "Error submitting change" + (cs.size() != 1 ? "s" : "") + ": \n"
+ e.getMessage();
}
throw new IntegrationException(msg, e);
}
updateSuperProjects(br.values());
}
private List<BatchUpdate> batchUpdates(Collection<Project.NameKey> projects) {
List<BatchUpdate> updates = new ArrayList<>(projects.size());
for (Project.NameKey project : projects) {
updates.add(orm.getRepo(project).getUpdate());
private List<SubmitStrategy> getSubmitStrategies(
Map<Branch.NameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp)
throws IntegrationException {
List<SubmitStrategy> strategies = new ArrayList<>();
Set<Branch.NameKey> allBranches = submoduleOp.getBranchesInOrder();
// in case superproject subscription is disabled, allBranches would be null
if (allBranches == null) {
allBranches = toSubmit.keySet();
}
return updates;
for (Branch.NameKey branch : allBranches) {
OpenRepo or = orm.getRepo(branch.getParentKey());
if (toSubmit.containsKey(branch)) {
BranchBatch submitting = toSubmit.get(branch);
OpenBranch ob = or.getBranch(branch);
checkNotNull(submitting.submitType(),
"null submit type for %s; expected to previously fail fast",
submitting);
Set<CodeReviewCommit> commitsToSubmit = commits(submitting.changes());
ob.mergeTip = new MergeTip(ob.oldTip, commitsToSubmit);
SubmitStrategy strategy = createStrategy(or, ob.mergeTip, branch,
submitting.submitType(), ob.oldTip, submoduleOp);
strategies.add(strategy);
strategy.addOps(or.getUpdate(), commitsToSubmit);
} else {
// no open change for this branch
// add submodule triggered op into BatchUpdate
submoduleOp.addOp(or.getUpdate(), branch);
}
}
return strategies;
}
private Set<CodeReviewCommit> commits(List<ChangeData> cds) {
@@ -551,10 +565,10 @@ public class MergeOp implements AutoCloseable {
private SubmitStrategy createStrategy(OpenRepo or,
MergeTip mergeTip, Branch.NameKey destBranch, SubmitType submitType,
CodeReviewCommit branchTip) throws IntegrationException {
CodeReviewCommit branchTip, SubmoduleOp submoduleOp) throws IntegrationException {
return submitStrategyFactory.create(submitType, db, or.repo, or.rw, or.ins,
or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller,
mergeTip, commits, submissionId, submitInput.notify);
mergeTip, commits, submissionId, submitInput.notify, submoduleOp);
}
private Set<RevCommit> getAlreadyAccepted(OpenRepo or,
@@ -729,18 +743,6 @@ public class MergeOp implements AutoCloseable {
}
}
private void updateSuperProjects(Collection<Branch.NameKey> branches) {
logDebug("Updating superprojects");
SubmoduleOp subOp = subOpFactory.create(branches, orm);
try {
subOp.updateSuperProjects();
logDebug("Updating superprojects done");
} catch (SubmoduleException e) {
logError("The gitlinks were not updated according to the "
+ "subscriptions", e);
}
}
private void openRepos(Collection<Project.NameKey> projects)
throws IntegrationException {
for (Project.NameKey project : projects) {

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.git;
/** Indicates the gitlink's update cannot be processed at this time. */
class SubmoduleException extends Exception {
public class SubmoduleException extends Exception {
private static final long serialVersionUID = 1L;
SubmoduleException(final String msg) {

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.data.SubscribeSection;
@@ -27,7 +28,6 @@ import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.BatchUpdate.Listener;
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
@@ -45,10 +45,8 @@ 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.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -57,11 +55,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class SubmoduleOp {
@@ -78,15 +80,17 @@ public class SubmoduleOp {
@Override
public void updateRepo(RepoContext ctx) throws Exception {
CodeReviewCommit c = composeGitlinksCommit(branch, null);
ctx.addRefUpdate(new ReceiveCommand(c.getParent(0), c, branch.get()));
addBranchTip(branch, c);
CodeReviewCommit c = composeGitlinksCommit(branch);
if (c != null) {
ctx.addRefUpdate(new ReceiveCommand(c.getParent(0), c, branch.get()));
addBranchTip(branch, c);
}
}
}
public interface Factory {
SubmoduleOp create(
Collection<Branch.NameKey> updatedBranches, MergeOpRepoManager orm);
Set<Branch.NameKey> updatedBranches, MergeOpRepoManager orm);
}
private static final Logger log = LoggerFactory.getLogger(SubmoduleOp.class);
@@ -98,10 +102,11 @@ public class SubmoduleOp {
private final boolean verboseSuperProject;
private final boolean enableSuperProjectSubscriptions;
private final Multimap<Branch.NameKey, SubmoduleSubscription> targets;
private final Collection<Branch.NameKey> updatedBranches;
private final Set<Branch.NameKey> updatedBranches;
private final MergeOpRepoManager orm;
private final Map<Branch.NameKey, CodeReviewCommit> branchTips;
private final Map<Branch.NameKey, GitModules> branchGitModules;
private final ImmutableSet<Branch.NameKey> sortedBranches;
@AssistedInject
public SubmoduleOp(
@@ -110,7 +115,7 @@ public class SubmoduleOp {
@GerritServerConfig Config cfg,
ProjectCache projectCache,
ProjectState.Factory projectStateFactory,
@Assisted Collection<Branch.NameKey> updatedBranches,
@Assisted Set<Branch.NameKey> updatedBranches,
@Assisted MergeOpRepoManager orm) throws SubmoduleException {
this.gitmodulesFactory = gitmodulesFactory;
this.myIdent = myIdent;
@@ -124,51 +129,93 @@ public class SubmoduleOp {
this.updatedBranches = updatedBranches;
this.targets = HashMultimap.create();
this.branchTips = new HashMap<>();
calculateSubscriptionMap();
this.branchGitModules = new HashMap<>();
this.sortedBranches = calculateSubscriptionMap();
}
private void calculateSubscriptionMap() throws SubmoduleException {
private ImmutableSet<Branch.NameKey> calculateSubscriptionMap()
throws SubmoduleException {
if (!enableSuperProjectSubscriptions) {
logDebug("Updating superprojects disabled");
return;
return null;
}
logDebug("Calculating superprojects - submodules map");
LinkedHashSet<Branch.NameKey> allVisited = new LinkedHashSet<>();
for (Branch.NameKey updatedBranch : updatedBranches) {
logDebug("Now processing " + updatedBranch);
Set<Branch.NameKey> checkedTargets = new HashSet<>();
Set<Branch.NameKey> targetsToProcess = new HashSet<>();
targetsToProcess.add(updatedBranch);
if (allVisited.contains(updatedBranch)) {
continue;
}
while (!targetsToProcess.isEmpty()) {
Set<Branch.NameKey> newTargets = new HashSet<>();
for (Branch.NameKey b : targetsToProcess) {
try {
Collection<SubmoduleSubscription> subs =
superProjectSubscriptionsForSubmoduleBranch(b);
for (SubmoduleSubscription sub : subs) {
Branch.NameKey dst = sub.getSuperProject();
targets.put(dst, sub);
newTargets.add(dst);
}
} catch (IOException e) {
throw new SubmoduleException("Cannot find superprojects for " + b, e);
}
}
logDebug("adding to done " + targetsToProcess);
checkedTargets.addAll(targetsToProcess);
logDebug("completely done with " + checkedTargets);
searchForSuperprojects(updatedBranch, new LinkedHashSet<Branch.NameKey>(),
allVisited);
}
Set<Branch.NameKey> intersection = new HashSet<>(checkedTargets);
intersection.retainAll(newTargets);
if (!intersection.isEmpty()) {
throw new SubmoduleException(
"Possible circular subscription involving " + updatedBranch);
}
// Since the searchForSuperprojects will add the superprojects before one
// submodule in sortedBranches, need reverse the order of it
reverse(allVisited);
return ImmutableSet.copyOf(allVisited);
}
targetsToProcess = newTargets;
private void searchForSuperprojects(Branch.NameKey current,
LinkedHashSet<Branch.NameKey> currentVisited,
LinkedHashSet<Branch.NameKey> allVisited)
throws SubmoduleException {
logDebug("Now processing " + current);
if (currentVisited.contains(current)) {
throw new SubmoduleException(
"Branch level circular subscriptions detected: " +
printCircularPath(currentVisited, current));
}
if (allVisited.contains(current)) {
return;
}
currentVisited.add(current);
try {
Collection<SubmoduleSubscription> subscriptions =
superProjectSubscriptionsForSubmoduleBranch(current);
for (SubmoduleSubscription sub : subscriptions) {
Branch.NameKey superProject = sub.getSuperProject();
searchForSuperprojects(superProject, currentVisited, allVisited);
targets.put(superProject, sub);
}
} catch (IOException e) {
throw new SubmoduleException("Cannot find superprojects for " + current,
e);
}
currentVisited.remove(current);
allVisited.add(current);
}
private static <T> void reverse(LinkedHashSet<T> set) {
if (set == null) {
return;
}
Deque<T> q = new ArrayDeque<>(set);
set.clear();
while (!q.isEmpty()) {
set.add(q.removeLast());
}
}
private <T> String printCircularPath(LinkedHashSet<T> p, T target) {
StringBuilder sb = new StringBuilder();
sb.append(target);
ArrayList<T> reverseP = new ArrayList<>(p);
Collections.reverse(reverseP);
for (T t : reverseP) {
sb.append("->");
sb.append(t);
if (t.equals(target)) {
break;
}
}
return sb.toString();
}
private Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src,
@@ -233,14 +280,13 @@ public class SubmoduleOp {
logDebug("The project " + targetProject + " doesn't exist");
continue;
}
GitModules m = gitmodulesFactory.create(targetBranch, orm);
for (SubmoduleSubscription ss : m.subscribedTo(srcBranch)) {
logDebug("Checking SubmoduleSubscription " + ss);
if (projectCache.get(ss.getSubmodule().getParentKey()) != null) {
logDebug("Adding SubmoduleSubscription " + ss);
ret.add(ss);
}
GitModules m = branchGitModules.get(targetBranch);
if (m == null) {
m = gitmodulesFactory.create(targetBranch, orm);
branchGitModules.put(targetBranch, m);
}
ret.addAll(m.subscribedTo(srcBranch));
}
}
logDebug("Calculated superprojects for " + srcBranch + " are " + ret);
@@ -248,20 +294,26 @@ public class SubmoduleOp {
}
public void updateSuperProjects() throws SubmoduleException {
ImmutableSet<Project.NameKey> projects = getProjectsInOrder();
if (projects == null) {
return;
}
SetMultimap<Project.NameKey, Branch.NameKey> dst = branchesByProject();
Set<Project.NameKey> projects = dst.keySet();
LinkedHashSet<Project.NameKey> superProjects = new LinkedHashSet<>();
try {
for (Project.NameKey project : projects) {
// get a new BatchUpdate for the project
orm.openRepo(project, false);
//TODO:czhen remove this when MergeOp combine this into BatchUpdate
orm.getRepo(project).resetUpdate();
for (Branch.NameKey branch : dst.get(project)) {
SubmoduleOp.GitlinkOp op = new SubmoduleOp.GitlinkOp(branch);
orm.getRepo(project).getUpdate().addRepoOnlyOp(op);
// only need superprojects
if (dst.containsKey(project)) {
superProjects.add(project);
// get a new BatchUpdate for the super project
orm.openRepo(project, false);
for (Branch.NameKey branch : dst.get(project)) {
addOp(orm.getRepo(project).getUpdate(), branch);
}
}
}
BatchUpdate.execute(orm.batchUpdates(projects), Listener.NONE);
BatchUpdate.execute(orm.batchUpdates(superProjects), Listener.NONE);
} catch (RestApiException | UpdateException | IOException |
NoSuchProjectException e) {
throw new SubmoduleException("Cannot update gitlinks", e);
@@ -269,81 +321,143 @@ public class SubmoduleOp {
}
/**
* Create a gitlink update commit on the tip of subscriber or modify the
* baseCommit with gitlink update patch
* Create a separate gitlink commit
*/
public CodeReviewCommit composeGitlinksCommit(
final Branch.NameKey subscriber, RevCommit baseCommit)
public CodeReviewCommit composeGitlinksCommit(final Branch.NameKey subscriber)
throws IOException, SubmoduleException {
PersonIdent author = null;
StringBuilder msgbuf = new StringBuilder("Update git submodules\n\n");
boolean sameAuthorForAll = true;
try {
orm.openRepo(subscriber.getParentKey(), false);
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access superproject", e);
}
OpenRepo or = orm.getRepo(subscriber.getParentKey());
CodeReviewCommit currentCommit;
Ref r = or.repo.exactRef(subscriber.get());
if (r == null) {
throw new SubmoduleException(
"The branch was probably deleted from the subscriber repository");
} else {
currentCommit = or.rw.parseCommit(r.getObjectId());
}
RevCommit currentCommit = (baseCommit != null) ? baseCommit :
or.rw.parseCommit(or.repo.exactRef(subscriber.get()).getObjectId());
or.rw.parseBody(currentCommit);
StringBuilder msgbuf = new StringBuilder("");
PersonIdent author = null;
DirCache dc = readTree(or.rw, currentCommit);
DirCacheEditor ed = dc.editor();
for (SubmoduleSubscription s : targets.get(subscriber)) {
try {
orm.openRepo(s.getSubmodule().getParentKey(), false);
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access submodule", e);
RevCommit newCommit = updateSubmodule(dc, ed, msgbuf, s);
if (newCommit != null) {
if (author == null) {
author = newCommit.getAuthorIdent();
} else if (!author.equals(newCommit.getAuthorIdent())) {
author = myIdent;
}
}
OpenRepo subOr = orm.getRepo(s.getSubmodule().getParentKey());
Repository subRepo = subOr.repo;
}
ed.finish();
ObjectId newTreeId = dc.writeTree(or.ins);
Ref ref = subRepo.getRefDatabase().exactRef(s.getSubmodule().get());
// Gitlinks are already in the branch, return null
if (newTreeId.equals(currentCommit.getTree())) {
return null;
} else {
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(newTreeId);
commit.setParentId(currentCommit);
StringBuilder commitMsg = new StringBuilder("Update git submodules\n\n");
if (verboseSuperProject) {
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
*/
public CodeReviewCommit composeGitlinksCommit(
final Branch.NameKey subscriber, CodeReviewCommit currentCommit)
throws IOException, SubmoduleException {
try {
orm.openRepo(subscriber.getParentKey(), false);
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access superproject", e);
}
OpenRepo or = orm.getRepo(subscriber.getParentKey());
StringBuilder msgbuf = new StringBuilder("");
DirCache dc = readTree(or.rw, currentCommit);
DirCacheEditor ed = dc.editor();
for (SubmoduleSubscription s : targets.get(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;
} else {
or.rw.parseBody(currentCommit);
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(newTreeId);
commit.setParentIds(currentCommit.getParents());
if (verboseSuperProject) {
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);
return or.rw.parseCommit(id);
}
}
private RevCommit updateSubmodule(DirCache dc, DirCacheEditor ed,
StringBuilder msgbuf, final SubmoduleSubscription s)
throws SubmoduleException, IOException {
try {
orm.openRepo(s.getSubmodule().getParentKey(), false);
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access submodule", e);
}
OpenRepo subOr = orm.getRepo(s.getSubmodule().getParentKey());
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().getParentKey().get() + " but entry "
+ "doesn't have gitlink file mode.";
throw new SubmoduleException(errMsg);
}
oldCommit = subOr.rw.parseCommit(dce.getObjectId());
}
final RevCommit newCommit;
if (branchTips.containsKey(s.getSubmodule())) {
newCommit = branchTips.get(s.getSubmodule());
} else {
Ref ref = subOr.repo.getRefDatabase().exactRef(s.getSubmodule().get());
if (ref == null) {
ed.add(new DeletePath(s.getPath()));
continue;
}
ObjectId updateTo = ref.getObjectId();
if (branchTips.containsKey(s.getSubmodule())) {
updateTo = branchTips.get(s.getSubmodule());
}
RevWalk subOrRw = subOr.rw;
final RevCommit newCommit = subOrRw.parseCommit(updateTo);
subOrRw.parseBody(newCommit);
if (author == null) {
author = newCommit.getAuthorIdent();
} else if (!author.equals(newCommit.getAuthorIdent())) {
sameAuthorForAll = false;
}
DirCacheEntry dce = dc.getEntry(s.getPath());
ObjectId oldId;
if (dce != null) {
if (!dce.getFileMode().equals(FileMode.GITLINK)) {
String errMsg = "Requested to update gitlink " + s.getPath() + " in "
+ s.getSubmodule().getParentKey().get() + " but entry "
+ "doesn't have gitlink file mode.";
throw new SubmoduleException(errMsg);
}
oldId = dce.getObjectId();
} else {
// This submodule did not exist before. We do not want to add
// the full submodule history to the commit message, so omit it.
oldId = updateTo;
return null;
}
newCommit = subOr.rw.parseCommit(ref.getObjectId());
}
if (Objects.equals(newCommit, oldCommit)) {
// gitlink have already been updated for this submodule
return null;
} else {
ed.add(new PathEdit(s.getPath()) {
@Override
public void apply(DirCacheEntry ent) {
@@ -351,54 +465,38 @@ public class SubmoduleOp {
ent.setObjectId(newCommit.getId());
}
});
if (verboseSuperProject) {
msgbuf.append("Project: " + s.getSubmodule().getParentKey().get());
msgbuf.append(" " + s.getSubmodule().getShortName());
msgbuf.append(" " + newCommit.getName());
msgbuf.append("\n\n");
try {
subOrRw.resetRetain(subOr.canMergeFlag);
subOrRw.markStart(newCommit);
subOrRw.markUninteresting(subOrRw.parseCommit(oldId));
for (RevCommit c : subOrRw) {
subOrRw.parseBody(c);
msgbuf.append(c.getFullMessage() + "\n\n");
}
} catch (IOException e) {
throw new SubmoduleException("Could not perform a revwalk to "
+ "create superproject commit message", e);
}
createSubmoduleCommitMsg(msgbuf, s, subOr, newCommit, oldCommit);
}
subOr.rw.parseBody(newCommit);
return newCommit;
}
ed.finish();
}
private void createSubmoduleCommitMsg(StringBuilder msgbuf,
SubmoduleSubscription s, OpenRepo subOr, RevCommit newCommit, RevCommit oldCommit)
throws SubmoduleException {
msgbuf.append("* Update " + s.getPath());
msgbuf.append(" from branch '" + s.getSubmodule().getShortName() + "'");
ObjectInserter oi = or.ins;
CodeReviewRevWalk rw = or.rw;
ObjectId tree = dc.writeTree(oi);
if (!sameAuthorForAll || author == null) {
author = myIdent;
// newly created submodule gitlink, do not append whole history
if (oldCommit == null) {
return;
}
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(tree);
if (baseCommit != null) {
// modify the baseCommit
commit.setParentIds(baseCommit.getParents());
commit.setMessage(baseCommit.getFullMessage() + "\n\n" + msgbuf.toString());
commit.setAuthor(baseCommit.getAuthorIdent());
} else {
// create a new commit
commit.setParentId(currentCommit);
commit.setMessage(msgbuf.toString());
commit.setAuthor(author);
try {
subOr.rw.resetRetain(subOr.canMergeFlag);
subOr.rw.markStart(newCommit);
subOr.rw.markUninteresting(oldCommit);
for (RevCommit c : subOr.rw) {
subOr.rw.parseBody(c);
msgbuf.append("\n - " + c.getFullMessage().replace("\n", "\n "));
}
} catch (IOException e) {
throw new SubmoduleException("Could not perform a revwalk to "
+ "create superproject commit message", e);
}
commit.setCommitter(myIdent);
ObjectId id = oi.insert(commit);
return rw.parseCommit(id);
}
private static DirCache readTree(RevWalk rw, ObjectId base)
@@ -421,10 +519,47 @@ public class SubmoduleOp {
return ret;
}
public ImmutableSet<Project.NameKey> getProjectsInOrder()
throws SubmoduleException {
if (sortedBranches == null) {
return null;
}
LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
Project.NameKey prev = null;
for (Branch.NameKey branch : sortedBranches) {
Project.NameKey project = branch.getParentKey();
if (!project.equals(prev)) {
if (projects.contains(project)) {
throw new SubmoduleException(
"Project level circular subscriptions detected: " +
printCircularPath(projects, project));
} else {
projects.add(project);
}
}
prev = project;
}
return ImmutableSet.copyOf(projects);
}
public ImmutableSet<Branch.NameKey> getBranchesInOrder() {
return sortedBranches;
}
public boolean hasSubscription(Branch.NameKey branch) {
return targets.containsKey(branch);
}
public void addBranchTip(Branch.NameKey branch, CodeReviewCommit tip) {
branchTips.put(branch, tip);
}
public void addOp(BatchUpdate bu, Branch.NameKey branch) {
bu.addRepoOnlyOp(new GitlinkOp(branch));
}
private void logDebug(String msg, Object... args) {
if (log.isDebugEnabled()) {
log.debug("[" + orm.getSubmissionId() + "]" + msg, args);

View File

@@ -74,11 +74,12 @@ public class CherryPick extends SubmitStrategy {
}
@Override
protected void updateRepoImpl(RepoContext ctx) {
protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
// The branch is unborn. Take fast-forward resolution to create the
// branch.
args.mergeTip.moveTipTo(toMerge, toMerge);
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
CodeReviewCommit newCommit = amendGitlink(toMerge);
args.mergeTip.moveTipTo(newCommit, toMerge);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
}
}
@@ -105,7 +106,8 @@ public class CherryPick extends SubmitStrategy {
}
@Override
protected void updateRepoImpl(RepoContext ctx) throws IOException {
protected void updateRepoImpl(RepoContext ctx)
throws IntegrationException, IOException {
// If there is only one parent, a cherry-pick can be done by taking the
// delta relative to that one parent and redoing that on the current merge
// tip.
@@ -132,6 +134,7 @@ public class CherryPick extends SubmitStrategy {
}
// Initial copy doesn't have new patch set ID since change hasn't been
// updated yet.
newCommit = amendGitlink(newCommit);
newCommit.copyFrom(toMerge);
newCommit.setPatchsetId(psId);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
@@ -189,12 +192,13 @@ public class CherryPick extends SubmitStrategy {
// was configured.
MergeTip mergeTip = args.mergeTip;
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
mergeTip.moveTipTo(toMerge, toMerge);
mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
} else {
PersonIdent myIdent = new PersonIdent(args.serverIdent, ctx.getWhen());
CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent,
myIdent, args.repo, args.rw, args.inserter, args.destBranch,
mergeTip.getCurrentTip(), toMerge);
result = amendGitlink(result);
mergeTip.moveTipTo(result, toMerge);
}
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,

View File

@@ -18,16 +18,13 @@ import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import java.io.IOException;
class FastForwardOp extends SubmitStrategyOp {
FastForwardOp(SubmitStrategy.Arguments args, CodeReviewCommit toMerge) {
super(args, toMerge);
}
@Override
public void updateRepoImpl(RepoContext ctx)
throws IntegrationException, IOException {
args.mergeTip.moveTipTo(toMerge, toMerge);
protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
}
}

View File

@@ -44,6 +44,6 @@ class MergeOneOp extends SubmitStrategyOp {
args.mergeUtil.mergeOneCommit(caller, args.serverIdent,
ctx.getRepository(), args.rw, ctx.getInserter(), args.destBranch,
args.mergeTip.getCurrentTip(), toMerge);
args.mergeTip.moveTipTo(merged, toMerge);
args.mergeTip.moveTipTo(amendGitlink(merged), toMerge);
}
}

View File

@@ -71,11 +71,12 @@ public class RebaseIfNecessary extends SubmitStrategy {
}
@Override
public void updateRepoImpl(RepoContext ctx) {
public void updateRepoImpl(RepoContext ctx) throws IntegrationException {
// The branch is unborn. Take fast-forward resolution to create the
// branch.
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
args.mergeTip.moveTipTo(toMerge, toMerge);
CodeReviewCommit newCommit = amendGitlink(toMerge);
args.mergeTip.moveTipTo(newCommit, toMerge);
acceptMergeTip(args.mergeTip);
}
}
@@ -110,8 +111,7 @@ public class RebaseIfNecessary extends SubmitStrategy {
// BatchUpdate how to produce CodeReviewRevWalks.
if (args.mergeUtil.canFastForward(args.mergeSorter,
args.mergeTip.getCurrentTip(), args.rw, toMerge)) {
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
args.mergeTip.moveTipTo(toMerge, toMerge);
args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
acceptMergeTip(args.mergeTip);
return;
}
@@ -134,6 +134,7 @@ public class RebaseIfNecessary extends SubmitStrategy {
"Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
}
newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
newCommit = amendGitlink(newCommit);
newCommit.copyFrom(toMerge);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
newCommit.setPatchsetId(rebaseOp.getPatchSetId());
@@ -182,13 +183,13 @@ public class RebaseIfNecessary extends SubmitStrategy {
// configured.
MergeTip mergeTip = args.mergeTip;
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
mergeTip.moveTipTo(toMerge, toMerge);
mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
acceptMergeTip(mergeTip);
} else {
CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit(
args.serverIdent, args.serverIdent, args.repo, args.rw,
args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
mergeTip.moveTipTo(newTip, toMerge);
mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
}
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
mergeTip.getCurrentTip(), args.alreadyAccepted);

View File

@@ -41,6 +41,7 @@ import com.google.gerrit.server.git.MergeOp.CommitStatus;
import com.google.gerrit.server.git.MergeSorter;
import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
@@ -93,7 +94,8 @@ public abstract class SubmitStrategy {
ReviewDb db,
Set<RevCommit> alreadyAccepted,
String submissionId,
NotifyHandling notifyHandling);
NotifyHandling notifyHandling,
SubmoduleOp submoduleOp);
}
final AccountCache accountCache;
@@ -125,6 +127,7 @@ public abstract class SubmitStrategy {
final String submissionId;
final SubmitType submitType;
final NotifyHandling notifyHandling;
final SubmoduleOp submoduleOp;
final ProjectState project;
final MergeSorter mergeSorter;
@@ -160,7 +163,8 @@ public abstract class SubmitStrategy {
@Assisted Set<RevCommit> alreadyAccepted,
@Assisted String submissionId,
@Assisted SubmitType submitType,
@Assisted NotifyHandling notifyHandling) {
@Assisted NotifyHandling notifyHandling,
@Assisted SubmoduleOp submoduleOp) {
this.accountCache = accountCache;
this.approvalsUtil = approvalsUtil;
this.batchUpdateFactory = batchUpdateFactory;
@@ -190,6 +194,7 @@ public abstract class SubmitStrategy {
this.submissionId = submissionId;
this.submitType = submitType;
this.notifyHandling = notifyHandling;
this.submoduleOp = submoduleOp;
this.project = checkNotNull(projectCache.get(destBranch.getParentKey()),
"project not found: %s", destBranch.getParentKey());

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeOp.CommitStatus;
import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -52,11 +53,12 @@ public class SubmitStrategyFactory {
Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter,
RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted,
Branch.NameKey destBranch, IdentifiedUser caller, MergeTip mergeTip,
CommitStatus commits, String submissionId, NotifyHandling notifyHandling)
CommitStatus commits, String submissionId, NotifyHandling notifyHandling,
SubmoduleOp submoduleOp)
throws IntegrationException {
SubmitStrategy.Arguments args = argsFactory.create(submitType, destBranch,
commits, rw, caller, mergeTip, inserter, repo, canMergeFlag, db,
alreadyAccepted, submissionId, notifyHandling);
alreadyAccepted, submissionId, notifyHandling, submoduleOp);
switch (submitType) {
case CHERRY_PICK:
return new CherryPick(args);

View File

@@ -46,6 +46,7 @@ import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.SubmoduleException;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
@@ -136,6 +137,7 @@ abstract class SubmitStrategyOp extends BatchUpdate.Op {
tipAfter,
getDest().get());
ctx.addRefUpdate(command);
args.submoduleOp.addBranchTip(getDest(), tipAfter);
}
private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit)
@@ -556,6 +558,34 @@ abstract class SubmitStrategyOp extends BatchUpdate.Op {
protected void postUpdateImpl(Context ctx) throws Exception {
}
/**
* Amend the commit with gitlink update
* @param commit
*/
protected CodeReviewCommit amendGitlink(CodeReviewCommit commit)
throws IntegrationException {
CodeReviewCommit newCommit = commit;
// Modify the commit with gitlink update
if (args.submoduleOp.hasSubscription(args.destBranch)) {
try {
newCommit =
args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
newCommit.copyFrom(commit);
if (commit.equals(toMerge)) {
newCommit.setPatchsetId(ChangeUtil.nextPatchSetId(
args.repo, toMerge.change().currentPatchSetId()));
args.commits.put(newCommit);
}
} catch (SubmoduleException | IOException e) {
throw new IntegrationException(
"cannot update gitlink for the commit at branch: "
+ args.destBranch);
}
}
return newCommit;
}
protected final void logDebug(String msg, Object... args) {
if (log.isDebugEnabled()) {
log.debug("[" + this.args.submissionId + "]" + msg, args);