Merge "Allow to set base_change for CreateMergePatchSet"

This commit is contained in:
Changcheng Xiao
2017-10-25 13:41:32 +00:00
committed by Gerrit Code Review
6 changed files with 132 additions and 14 deletions

View File

@@ -6446,7 +6446,10 @@ The new subject for the change, if not specified, will reuse the current patch
set's subject set's subject
|`inheritParent` |optional, default to `false`| |`inheritParent` |optional, default to `false`|
Use the current patch set's first parent as the merge tip when set to `true`. Use the current patch set's first parent as the merge tip when set to `true`.
Otherwise, use the current branch tip of the destination branch. |`base_change` |optional|
A link:#change-id[\{change-id\}] that identifies a change. When `inheritParent`
is `false`, the merge tip will be the current patch set of the `base_change` if
it's set. Otherwise, the current branch tip of the destination branch will be used.
|`merge` || |`merge` ||
The detail of the source commit for merge as a link:#merge-input[MergeInput] The detail of the source commit for merge as a link:#merge-input[MergeInput]
entity. entity.

View File

@@ -655,6 +655,10 @@ public abstract class AbstractDaemonTest {
return push.to("refs/for/" + branch + "/" + name(topic)); return push.to("refs/for/" + branch + "/" + name(topic));
} }
protected BranchApi createBranch(String branch) throws Exception {
return createBranch(new Branch.NameKey(project, branch));
}
protected BranchApi createBranch(Branch.NameKey branch) throws Exception { protected BranchApi createBranch(Branch.NameKey branch) throws Exception {
return gApi.projects() return gApi.projects()
.name(branch.getParentKey().get()) .name(branch.getParentKey().get())

View File

@@ -117,6 +117,7 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
@@ -2552,7 +2553,7 @@ public class ChangeIT extends AbstractDaemonTest {
String parent = currentMaster.getCommit().getName(); String parent = currentMaster.getCommit().getName();
// push a commit into dev branch // push a commit into dev branch
createBranch(new Branch.NameKey(project, "dev")); createBranch("dev");
PushOneCommit.Result changeA = PushOneCommit.Result changeA =
pushFactory pushFactory
.create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content") .create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
@@ -2588,7 +2589,7 @@ public class ChangeIT extends AbstractDaemonTest {
currentMaster.assertOkStatus(); currentMaster.assertOkStatus();
// push a commit into dev branch // push a commit into dev branch
createBranch(new Branch.NameKey(project, "dev")); createBranch("dev");
PushOneCommit.Result changeA = PushOneCommit.Result changeA =
pushFactory pushFactory
.create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content") .create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
@@ -2612,6 +2613,70 @@ public class ChangeIT extends AbstractDaemonTest {
.isNotEqualTo(currentMaster.getCommit().getName()); .isNotEqualTo(currentMaster.getCommit().getName());
} }
@Test
public void createMergePatchSetCannotBaseOnInvisibleChange() throws Exception {
RevCommit initialHead = getRemoteHead();
createBranch("foo");
createBranch("bar");
// Create a merged commit on 'foo' branch.
merge(createChange("refs/for/foo"));
// Create the base change on 'bar' branch.
testRepo.reset(initialHead);
String baseChange = createChange("refs/for/bar").getChangeId();
gApi.changes().id(baseChange).setPrivate(true, "set private");
// Create the destination change on 'master' branch.
setApiUser(user);
testRepo.reset(initialHead);
String changeId = createChange().getChangeId();
exception.expect(UnprocessableEntityException.class);
exception.expectMessage("Read not permitted for " + baseChange);
gApi.changes().id(changeId).createMergePatchSet(createMergePatchSetInput(baseChange));
}
@Test
public void createMergePatchSetBaseOnChange() throws Exception {
RevCommit initialHead = getRemoteHead();
createBranch("foo");
createBranch("bar");
// Create a merged commit on 'foo' branch.
merge(createChange("refs/for/foo"));
// Create the base change on 'bar' branch.
testRepo.reset(initialHead);
PushOneCommit.Result result = createChange("refs/for/bar");
String baseChange = result.getChangeId();
String expectedParent = result.getCommit().getName();
// Create the destination change on 'master' branch.
testRepo.reset(initialHead);
String changeId = createChange().getChangeId();
gApi.changes().id(changeId).createMergePatchSet(createMergePatchSetInput(baseChange));
ChangeInfo changeInfo =
gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
assertThat(changeInfo.revisions.size()).isEqualTo(2);
assertThat(changeInfo.subject).isEqualTo("create ps2");
assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
.isEqualTo(expectedParent);
}
private MergePatchSetInput createMergePatchSetInput(String baseChange) {
MergeInput mergeInput = new MergeInput();
mergeInput.source = "foo";
MergePatchSetInput in = new MergePatchSetInput();
in.merge = mergeInput;
in.subject = "create ps2";
in.inheritParent = false;
in.baseChange = baseChange;
return in;
}
@Test @Test
public void checkLabelsForUnsubmittedChange() throws Exception { public void checkLabelsForUnsubmittedChange() throws Exception {
PushOneCommit.Result r = createChange(); PushOneCommit.Result r = createChange();

View File

@@ -17,5 +17,6 @@ package com.google.gerrit.extensions.common;
public class MergePatchSetInput { public class MergePatchSetInput {
public String subject; public String subject;
public boolean inheritParent; public boolean inheritParent;
public String baseChange;
public MergeInput merge; public MergeInput merge;
} }

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput; import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.MergeInput; import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
@@ -194,8 +195,10 @@ public class CreateChange
throw new UnprocessableEntityException("Base change not found: " + input.baseChange); throw new UnprocessableEntityException("Base change not found: " + input.baseChange);
} }
ChangeNotes change = Iterables.getOnlyElement(notes); ChangeNotes change = Iterables.getOnlyElement(notes);
if (!permissionBackend.user(user).change(change).database(db).test(ChangePermission.READ)) { try {
throw new UnprocessableEntityException("Base change not found: " + input.baseChange); permissionBackend.user(user).change(change).database(db).check(ChangePermission.READ);
} catch (AuthException e) {
throw new UnprocessableEntityException("Read not permitted for " + input.baseChange);
} }
PatchSet ps = psUtil.current(db.get(), change); PatchSet ps = psUtil.current(db.get(), change);
parentCommit = ObjectId.fromString(ps.getRevision().get()); parentCommit = ObjectId.fromString(ps.getRevision().get());

View File

@@ -16,22 +16,26 @@ package com.google.gerrit.server.change;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.MergeInput; import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.common.MergePatchSetInput; import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException; import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeFinder;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
@@ -40,7 +44,9 @@ import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeIdenticalTreeException; import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.git.MergeUtil; import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission; import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.CommitsCollection; import com.google.gerrit.server.project.CommitsCollection;
import com.google.gerrit.server.project.InvalidChangeOperationException; import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -56,6 +62,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
@@ -80,6 +87,8 @@ public class CreateMergePatchSet
private final MergeUtil.Factory mergeUtilFactory; private final MergeUtil.Factory mergeUtilFactory;
private final PatchSetInserter.Factory patchSetInserterFactory; private final PatchSetInserter.Factory patchSetInserterFactory;
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final ChangeFinder changeFinder;
private final PermissionBackend permissionBackend;
@Inject @Inject
CreateMergePatchSet( CreateMergePatchSet(
@@ -93,7 +102,9 @@ public class CreateMergePatchSet
MergeUtil.Factory mergeUtilFactory, MergeUtil.Factory mergeUtilFactory,
RetryHelper retryHelper, RetryHelper retryHelper,
PatchSetInserter.Factory patchSetInserterFactory, PatchSetInserter.Factory patchSetInserterFactory,
ProjectCache projectCache) { ProjectCache projectCache,
ChangeFinder changeFinder,
PermissionBackend permissionBackend) {
super(retryHelper); super(retryHelper);
this.db = db; this.db = db;
this.gitManager = gitManager; this.gitManager = gitManager;
@@ -105,6 +116,8 @@ public class CreateMergePatchSet
this.mergeUtilFactory = mergeUtilFactory; this.mergeUtilFactory = mergeUtilFactory;
this.patchSetInserterFactory = patchSetInserterFactory; this.patchSetInserterFactory = patchSetInserterFactory;
this.projectCache = projectCache; this.projectCache = projectCache;
this.changeFinder = changeFinder;
this.permissionBackend = permissionBackend;
} }
@Override @Override
@@ -118,6 +131,7 @@ public class CreateMergePatchSet
if (merge == null || Strings.isNullOrEmpty(merge.source)) { if (merge == null || Strings.isNullOrEmpty(merge.source)) {
throw new BadRequestException("merge.source must be non-empty"); throw new BadRequestException("merge.source must be non-empty");
} }
in.baseChange = Strings.nullToEmpty(in.baseChange).trim();
PatchSet ps = psUtil.current(db.get(), rsrc.getNotes()); PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
ProjectState projectState = projectCache.checkedGet(rsrc.getProject()); ProjectState projectState = projectCache.checkedGet(rsrc.getProject());
@@ -135,7 +149,16 @@ public class CreateMergePatchSet
"cannot find source commit: " + merge.source + " to merge."); "cannot find source commit: " + merge.source + " to merge.");
} }
RevCommit currentPsCommit = rw.parseCommit(ObjectId.fromString(ps.getRevision().get())); RevCommit currentPsCommit;
List<String> groups = null;
if (!in.inheritParent && !in.baseChange.isEmpty()) {
PatchSet basePS = findBasePatchSet(in.baseChange);
currentPsCommit = rw.parseCommit(ObjectId.fromString(basePS.getRevision().get()));
groups = basePS.getGroups();
} else {
currentPsCommit = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
}
Timestamp now = TimeUtil.nowTs(); Timestamp now = TimeUtil.nowTs();
IdentifiedUser me = user.get().asIdentifiedUser(); IdentifiedUser me = user.get().asIdentifiedUser();
PersonIdent author = me.newCommitterIdent(now, serverTimeZone); PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
@@ -157,13 +180,15 @@ public class CreateMergePatchSet
patchSetInserterFactory.create(rsrc.getNotes(), nextPsId, newCommit); patchSetInserterFactory.create(rsrc.getNotes(), nextPsId, newCommit);
try (BatchUpdate bu = updateFactory.create(db.get(), project, me, now)) { try (BatchUpdate bu = updateFactory.create(db.get(), project, me, now)) {
bu.setRepository(git, rw, oi); bu.setRepository(git, rw, oi);
bu.addOp(
rsrc.getId(),
psInserter psInserter
.setMessage("Uploaded patch set " + nextPsId.get() + ".") .setMessage("Uploaded patch set " + nextPsId.get() + ".")
.setNotify(NotifyHandling.NONE) .setNotify(NotifyHandling.NONE)
.setCheckAddPatchSetPermission(false) .setCheckAddPatchSetPermission(false)
.setNotify(NotifyHandling.NONE)); .setNotify(NotifyHandling.NONE);
if (groups != null) {
psInserter.setGroups(groups);
}
bu.addOp(rsrc.getId(), psInserter);
bu.execute(); bu.execute();
} }
@@ -172,6 +197,21 @@ public class CreateMergePatchSet
} }
} }
private PatchSet findBasePatchSet(String baseChange)
throws PermissionBackendException, OrmException, UnprocessableEntityException {
List<ChangeNotes> notes = changeFinder.find(baseChange);
if (notes.size() != 1) {
throw new UnprocessableEntityException("Base change not found: " + baseChange);
}
ChangeNotes change = Iterables.getOnlyElement(notes);
try {
permissionBackend.user(user).change(change).database(db).check(ChangePermission.READ);
} catch (AuthException e) {
throw new UnprocessableEntityException("Read not permitted for " + baseChange);
}
return psUtil.current(db.get(), change);
}
private RevCommit createMergeCommit( private RevCommit createMergeCommit(
MergePatchSetInput in, MergePatchSetInput in,
ProjectState projectState, ProjectState projectState,
@@ -190,6 +230,8 @@ public class CreateMergePatchSet
if (in.inheritParent) { if (in.inheritParent) {
// inherit first parent from previous patch set // inherit first parent from previous patch set
parentCommit = currentPsCommit.getParent(0); parentCommit = currentPsCommit.getParent(0);
} else if (!in.baseChange.isEmpty()) {
parentCommit = currentPsCommit.getId();
} else { } else {
// get the current branch tip of destination branch // get the current branch tip of destination branch
Ref destRef = git.getRefDatabase().exactRef(dest.get()); Ref destRef = git.getRefDatabase().exactRef(dest.get());