Merge "Use /changes/{id}/revisions/{sha1}/submit to submit changes"
This commit is contained in:
@@ -25,10 +25,6 @@ import com.google.gwtjsonrpc.common.RpcImpl.Version;
|
||||
|
||||
@RpcImpl(version = Version.V2_0)
|
||||
public interface ChangeManageService extends RemoteJsonService {
|
||||
@Audit
|
||||
@SignInRequired
|
||||
void submit(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
||||
|
||||
@Audit
|
||||
@SignInRequired
|
||||
void createNewPatchSet(final PatchSet.Id patchSetId, final String newCommitMessage,
|
||||
|
@@ -60,23 +60,46 @@ public class ChangeApi {
|
||||
}
|
||||
}
|
||||
|
||||
/** Submit a specific revision of a change. */
|
||||
public static void submit(int id, String commit, AsyncCallback<SubmitInfo> cb) {
|
||||
SubmitInput in = SubmitInput.create();
|
||||
in.wait_for_merge(true);
|
||||
api(id, commit, "submit").data(in).post(cb);
|
||||
}
|
||||
|
||||
private static class Input extends JavaScriptObject {
|
||||
final native void topic(String t) /*-{ if(t)this.topic=t; }-*/;
|
||||
final native void message(String m) /*-{ if(m)this.message=m; }-*/;
|
||||
|
||||
static Input create() {
|
||||
return (Input) JavaScriptObject.createObject();
|
||||
return (Input) createObject();
|
||||
}
|
||||
|
||||
protected Input() {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SubmitInput extends JavaScriptObject {
|
||||
final native void wait_for_merge(boolean b) /*-{ this.wait_for_merge=b; }-*/;
|
||||
|
||||
static SubmitInput create() {
|
||||
return (SubmitInput) createObject();
|
||||
}
|
||||
|
||||
protected SubmitInput() {
|
||||
}
|
||||
}
|
||||
|
||||
private static RestApi api(int id, String action) {
|
||||
// TODO Switch to triplet project~branch~id format in URI.
|
||||
return new RestApi("/changes/" + id + "/" + action);
|
||||
}
|
||||
|
||||
private static RestApi api(int id, String commit, String action) {
|
||||
// TODO Switch to triplet project~branch~id format in URI.
|
||||
return new RestApi("/changes/" + id + "/revisions/" + commit + "/" + action);
|
||||
}
|
||||
|
||||
public static String emptyToNull(String str) {
|
||||
return str == null || str.isEmpty() ? null : str;
|
||||
}
|
||||
|
@@ -30,13 +30,12 @@ import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.common.data.PatchSetDetail;
|
||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
|
||||
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.UserIdentity;
|
||||
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
|
||||
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.logical.shared.OpenEvent;
|
||||
@@ -308,11 +307,29 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel
|
||||
@Override
|
||||
public void onClick(final ClickEvent event) {
|
||||
b.setEnabled(false);
|
||||
Util.MANAGE_SVC.submit(patchSet.getId(),
|
||||
new ChangeDetailCache.GerritWidgetCallback(b) {
|
||||
public void onSuccess(ChangeDetail result) {
|
||||
onSubmitResult(result);
|
||||
}
|
||||
ChangeApi.submit(
|
||||
patchSet.getId().getParentKey().get(),
|
||||
patchSet.getRevision().get(),
|
||||
new GerritCallback<SubmitInfo>() {
|
||||
public void onSuccess(SubmitInfo result) {
|
||||
redisplay();
|
||||
}
|
||||
|
||||
public void onFailure(Throwable err) {
|
||||
if (SubmitFailureDialog.isConflict(err)) {
|
||||
new SubmitFailureDialog(err.getMessage()).center();
|
||||
redisplay();
|
||||
} else {
|
||||
b.setEnabled(true);
|
||||
super.onFailure(err);
|
||||
}
|
||||
}
|
||||
|
||||
private void redisplay() {
|
||||
Gerrit.display(
|
||||
PageLinks.toChange(patchSet.getId().getParentKey()),
|
||||
new ChangeScreen(patchSet.getId().getParentKey()));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -588,28 +605,6 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel
|
||||
Gerrit.RESOURCES.css().header());
|
||||
}
|
||||
|
||||
private void onSubmitResult(final ChangeDetail result) {
|
||||
if (result.getChange().getStatus() == Change.Status.NEW) {
|
||||
// The submit failed. Try to locate the message and display
|
||||
// it to the user, it should be the last one created by Gerrit.
|
||||
//
|
||||
ChangeMessage msg = null;
|
||||
if (result.getMessages() != null && result.getMessages().size() > 0) {
|
||||
for (int i = result.getMessages().size() - 1; i >= 0; i--) {
|
||||
if (result.getMessages().get(i).getAuthor() == null) {
|
||||
msg = result.getMessages().get(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg != null) {
|
||||
new SubmitFailureDialog(result, msg).center();
|
||||
}
|
||||
}
|
||||
detailCache.set(result);
|
||||
}
|
||||
|
||||
public PatchSet getPatchSet() {
|
||||
return patchSet;
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ import com.google.gerrit.client.ui.SmallHeading;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.data.ApprovalType;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.common.data.PatchSetPublishDetail;
|
||||
import com.google.gerrit.common.data.PermissionRange;
|
||||
import com.google.gerrit.reviewdb.client.ApprovalCategory;
|
||||
@@ -399,17 +398,21 @@ public class PublishCommentScreen extends AccountScreen implements
|
||||
}
|
||||
|
||||
private void submit() {
|
||||
Util.MANAGE_SVC.submit(patchSetId,
|
||||
new GerritCallback<ChangeDetail>() {
|
||||
public void onSuccess(ChangeDetail result) {
|
||||
ChangeApi.submit(patchSetId.getParentKey().get(), revision,
|
||||
new GerritCallback<SubmitInfo>() {
|
||||
public void onSuccess(SubmitInfo result) {
|
||||
saveStateOnUnload = false;
|
||||
goChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
public void onFailure(Throwable err) {
|
||||
if (SubmitFailureDialog.isConflict(err)) {
|
||||
new SubmitFailureDialog(err.getMessage()).center();
|
||||
} else {
|
||||
super.onFailure(err);
|
||||
}
|
||||
goChange();
|
||||
super.onFailure(caught);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -15,13 +15,17 @@
|
||||
package com.google.gerrit.client.changes;
|
||||
|
||||
import com.google.gerrit.client.ErrorDialog;
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
||||
import com.google.gwtjsonrpc.client.RemoteJsonException;
|
||||
|
||||
class SubmitFailureDialog extends ErrorDialog {
|
||||
SubmitFailureDialog(final ChangeDetail result, final ChangeMessage msg) {
|
||||
super(new SafeHtmlBuilder().append(msg.getMessage().trim()).wikify());
|
||||
static boolean isConflict(Throwable err) {
|
||||
return err instanceof RemoteJsonException
|
||||
&& 409 == ((RemoteJsonException) err).getCode();
|
||||
}
|
||||
|
||||
SubmitFailureDialog(String msg) {
|
||||
super(new SafeHtmlBuilder().append(msg.trim()).wikify());
|
||||
setText(Util.C.submitFailed());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2012 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.client.changes;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
|
||||
class SubmitInfo extends JavaScriptObject {
|
||||
final Change.Status status() {
|
||||
return Change.Status.valueOf(statusRaw());
|
||||
}
|
||||
|
||||
private final native String statusRaw() /*-{ return this.status; }-*/;
|
||||
|
||||
protected SubmitInfo() {
|
||||
}
|
||||
}
|
@@ -23,30 +23,23 @@ import com.google.gwtjsonrpc.common.VoidResult;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
class ChangeManageServiceImpl implements ChangeManageService {
|
||||
private final SubmitAction.Factory submitAction;
|
||||
private final RebaseChangeHandler.Factory rebaseChangeFactory;
|
||||
private final PublishAction.Factory publishAction;
|
||||
private final DeleteDraftChange.Factory deleteDraftChangeFactory;
|
||||
private final EditCommitMessageHandler.Factory editCommitMessageHandlerFactory;
|
||||
|
||||
@Inject
|
||||
ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
|
||||
ChangeManageServiceImpl(
|
||||
final RebaseChangeHandler.Factory rebaseChangeFactory,
|
||||
final PublishAction.Factory publishAction,
|
||||
final DeleteDraftChange.Factory deleteDraftChangeFactory,
|
||||
final EditCommitMessageHandler.Factory editCommitMessageHandler) {
|
||||
this.submitAction = patchSetAction;
|
||||
this.rebaseChangeFactory = rebaseChangeFactory;
|
||||
this.publishAction = publishAction;
|
||||
this.deleteDraftChangeFactory = deleteDraftChangeFactory;
|
||||
this.editCommitMessageHandlerFactory = editCommitMessageHandler;
|
||||
}
|
||||
|
||||
public void submit(final PatchSet.Id patchSetId,
|
||||
final AsyncCallback<ChangeDetail> cb) {
|
||||
submitAction.create(patchSetId).to(cb);
|
||||
}
|
||||
|
||||
public void rebaseChange(final PatchSet.Id patchSetId,
|
||||
final AsyncCallback<ChangeDetail> callback) {
|
||||
rebaseChangeFactory.create(patchSetId).to(callback);
|
||||
|
@@ -34,7 +34,6 @@ public class ChangeModule extends RpcServletModule {
|
||||
factory(IncludedInDetailFactory.Factory.class);
|
||||
factory(PatchSetDetailFactory.Factory.class);
|
||||
factory(PatchSetPublishDetailFactory.Factory.class);
|
||||
factory(SubmitAction.Factory.class);
|
||||
factory(PublishAction.Factory.class);
|
||||
factory(DeleteDraftChange.Factory.class);
|
||||
}
|
||||
|
@@ -1,67 +0,0 @@
|
||||
// Copyright (C) 2009 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.httpd.rpc.changedetail;
|
||||
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.common.data.ReviewResult;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.changedetail.Submit;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class SubmitAction extends Handler<ChangeDetail> {
|
||||
interface Factory {
|
||||
SubmitAction create(PatchSet.Id patchSetId);
|
||||
}
|
||||
|
||||
private final Submit.Factory submitFactory;
|
||||
private final ChangeDetailFactory.Factory changeDetailFactory;
|
||||
|
||||
private final PatchSet.Id patchSetId;
|
||||
|
||||
@Inject
|
||||
SubmitAction(final Submit.Factory submitFactory,
|
||||
final ChangeDetailFactory.Factory changeDetailFactory,
|
||||
@Assisted final PatchSet.Id patchSetId) {
|
||||
this.submitFactory = submitFactory;
|
||||
this.changeDetailFactory = changeDetailFactory;
|
||||
|
||||
this.patchSetId = patchSetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeDetail call() throws OrmException, NoSuchEntityException,
|
||||
IllegalStateException, InvalidChangeOperationException,
|
||||
PatchSetInfoNotAvailableException, NoSuchChangeException,
|
||||
RepositoryNotFoundException, IOException {
|
||||
final ReviewResult result =
|
||||
submitFactory.create(patchSetId).call();
|
||||
if (result.getErrors().size() > 0) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot submit " + result.getErrors().get(0).getMessageOrType());
|
||||
}
|
||||
return changeDetailFactory.create(result.getChangeId()).call();
|
||||
}
|
||||
}
|
@@ -428,6 +428,10 @@ public final class Change {
|
||||
return lastUpdatedOn;
|
||||
}
|
||||
|
||||
public void setLastUpdatedOn(Timestamp now) {
|
||||
lastUpdatedOn = now;
|
||||
}
|
||||
|
||||
public void resetLastUpdatedOn() {
|
||||
lastUpdatedOn = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
@@ -43,12 +43,14 @@ public class Module extends RestApiModule {
|
||||
post(CHANGE_KIND, "restore").to(Restore.class);
|
||||
child(CHANGE_KIND, "reviewers").to(Reviewers.class);
|
||||
post(CHANGE_KIND, "revert").to(Revert.class);
|
||||
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
|
||||
|
||||
get(REVIEWER_KIND).to(GetReviewer.class);
|
||||
delete(REVIEWER_KIND).to(DeleteReviewer.class);
|
||||
|
||||
child(CHANGE_KIND, "revisions").to(Revisions.class);
|
||||
post(REVISION_KIND, "review").to(PostReview.class);
|
||||
post(REVISION_KIND, "submit").to(Submit.class);
|
||||
|
||||
child(REVISION_KIND, "drafts").to(Drafts.class);
|
||||
put(REVISION_KIND, "drafts").to(CreateDraft.class);
|
||||
|
@@ -0,0 +1,328 @@
|
||||
// Copyright (C) 2012 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.change;
|
||||
|
||||
import static com.google.gerrit.common.data.SubmitRecord.Status.OK;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.common.data.SubmitRecord;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.reviewdb.client.ApprovalCategory;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.ProjectUtil;
|
||||
import com.google.gerrit.server.change.Submit.Input;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gwtorm.server.AtomicUpdate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Submit implements RestModifyView<RevisionResource, Input> {
|
||||
public static class Input {
|
||||
public boolean waitForMerge;
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
SUBMITTED, MERGED;
|
||||
}
|
||||
|
||||
public static class Output {
|
||||
public Status status;
|
||||
transient Change change;
|
||||
|
||||
private Output(Status s, Change c) {
|
||||
status = s;
|
||||
change = c;
|
||||
}
|
||||
}
|
||||
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final MergeQueue mergeQueue;
|
||||
|
||||
@Inject
|
||||
Submit(Provider<ReviewDb> dbProvider,
|
||||
GitRepositoryManager repoManager,
|
||||
MergeQueue mergeQueue) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.repoManager = repoManager;
|
||||
this.mergeQueue = mergeQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Input> inputType() {
|
||||
return Input.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Output apply(RevisionResource rsrc, Input input) throws AuthException,
|
||||
ResourceConflictException, RepositoryNotFoundException, IOException,
|
||||
OrmException {
|
||||
ChangeControl control = rsrc.getControl();
|
||||
IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
|
||||
Change change = rsrc.getChange();
|
||||
if (!control.canSubmit()) {
|
||||
throw new AuthException("submit not permitted");
|
||||
} else if (!change.getStatus().isOpen()) {
|
||||
throw new ResourceConflictException("change is " + status(change));
|
||||
} else if (!ProjectUtil.branchExists(repoManager, change.getDest())) {
|
||||
throw new ResourceConflictException(String.format(
|
||||
"destination branch \"%s\" not found.",
|
||||
change.getDest().get()));
|
||||
} else if (!rsrc.getPatchSet().getId().equals(change.currentPatchSetId())) {
|
||||
// TODO Allow submitting non-current revision by changing the current.
|
||||
throw new ResourceConflictException(String.format(
|
||||
"revision %s is not current revision",
|
||||
rsrc.getPatchSet().getRevision().get()));
|
||||
}
|
||||
|
||||
checkSubmitRule(rsrc);
|
||||
change = submit(rsrc, caller);
|
||||
|
||||
if (input.waitForMerge) {
|
||||
mergeQueue.merge(change.getDest());
|
||||
change = dbProvider.get().changes().get(change.getId());
|
||||
} else {
|
||||
mergeQueue.schedule(change.getDest());
|
||||
}
|
||||
|
||||
if (change == null) {
|
||||
throw new ResourceConflictException("change is deleted");
|
||||
}
|
||||
switch (change.getStatus()) {
|
||||
case SUBMITTED:
|
||||
return new Output(Status.SUBMITTED, change);
|
||||
case MERGED:
|
||||
return new Output(Status.MERGED, change);
|
||||
case NEW:
|
||||
// If the merge was attempted and it failed the system usually
|
||||
// writes a comment as a ChangeMessage and sets status to NEW.
|
||||
// Find the relevant message and report that as the conflict.
|
||||
final Timestamp before = rsrc.getChange().getLastUpdatedOn();
|
||||
ChangeMessage msg = Iterables.getFirst(Iterables.filter(
|
||||
Lists.reverse(dbProvider.get().changeMessages()
|
||||
.byChange(change.getId())
|
||||
.toList()),
|
||||
new Predicate<ChangeMessage>() {
|
||||
@Override
|
||||
public boolean apply(ChangeMessage input) {
|
||||
return input.getAuthor() == null
|
||||
&& input.getWrittenOn().getTime() >= before.getTime();
|
||||
}
|
||||
}), null);
|
||||
if (msg != null) {
|
||||
throw new ResourceConflictException(msg.getMessage());
|
||||
}
|
||||
default:
|
||||
throw new ResourceConflictException("change is " + status(change));
|
||||
}
|
||||
}
|
||||
|
||||
private Change submit(RevisionResource rsrc, IdentifiedUser caller)
|
||||
throws OrmException, ResourceConflictException {
|
||||
final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
|
||||
Change change = rsrc.getChange();
|
||||
ReviewDb db = dbProvider.get();
|
||||
db.changes().beginTransaction(change.getId());
|
||||
try {
|
||||
approve(rsrc.getPatchSet(), caller, timestamp);
|
||||
change = db.changes().atomicUpdate(
|
||||
change.getId(),
|
||||
new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus().isOpen()) {
|
||||
change.setStatus(Change.Status.SUBMITTED);
|
||||
change.setLastUpdatedOn(timestamp);
|
||||
ChangeUtil.computeSortKey(change);
|
||||
return change;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (change == null) {
|
||||
throw new ResourceConflictException("change is "
|
||||
+ status(db.changes().get(rsrc.getChange().getId())));
|
||||
}
|
||||
db.commit();
|
||||
} finally {
|
||||
db.rollback();
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
private void approve(PatchSet rev, IdentifiedUser caller, Timestamp timestamp)
|
||||
throws OrmException {
|
||||
PatchSetApproval submit = Iterables.getFirst(Iterables.filter(
|
||||
dbProvider.get().patchSetApprovals()
|
||||
.byPatchSetUser(rev.getId(), caller.getAccountId()),
|
||||
new Predicate<PatchSetApproval>() {
|
||||
@Override
|
||||
public boolean apply(PatchSetApproval input) {
|
||||
return ApprovalCategory.SUBMIT.equals(input.getCategoryId());
|
||||
}
|
||||
}), null);
|
||||
if (submit == null) {
|
||||
submit = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
rev.getId(),
|
||||
caller.getAccountId(),
|
||||
ApprovalCategory.SUBMIT),
|
||||
(short) 1);
|
||||
}
|
||||
submit.setValue((short) 1);
|
||||
submit.setGranted(timestamp);
|
||||
dbProvider.get().patchSetApprovals().upsert(Collections.singleton(submit));
|
||||
}
|
||||
|
||||
private void checkSubmitRule(RevisionResource rsrc)
|
||||
throws ResourceConflictException {
|
||||
List<SubmitRecord> results = rsrc.getControl().canSubmit(
|
||||
dbProvider.get(),
|
||||
rsrc.getPatchSet());
|
||||
Optional<SubmitRecord> ok = findOkRecord(results);
|
||||
if (ok.isPresent()) {
|
||||
// Rules supplied a valid solution.
|
||||
return;
|
||||
} else if (results.isEmpty()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"ChangeControl.canSubmit returned empty list for %s in %s",
|
||||
rsrc.getPatchSet().getId(),
|
||||
rsrc.getChange().getProject().get()));
|
||||
}
|
||||
|
||||
for (SubmitRecord record : results) {
|
||||
switch (record.status) {
|
||||
case CLOSED:
|
||||
throw new ResourceConflictException("change is closed");
|
||||
|
||||
case RULE_ERROR:
|
||||
throw new ResourceConflictException(String.format(
|
||||
"rule error: %s",
|
||||
record.errorMessage));
|
||||
|
||||
case NOT_READY:
|
||||
StringBuilder msg = new StringBuilder();
|
||||
for (SubmitRecord.Label lbl : record.labels) {
|
||||
switch (lbl.status) {
|
||||
case OK:
|
||||
case MAY:
|
||||
continue;
|
||||
|
||||
case REJECT:
|
||||
if (msg.length() > 0) msg.append("; ");
|
||||
msg.append("blocked by " + lbl.label);
|
||||
continue;
|
||||
|
||||
case NEED:
|
||||
if (msg.length() > 0) msg.append("; ");
|
||||
msg.append("needs " + lbl.label);
|
||||
continue;
|
||||
|
||||
case IMPOSSIBLE:
|
||||
if (msg.length() > 0) msg.append("; ");
|
||||
msg.append("needs " + lbl.label + " (check project access)");
|
||||
continue;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unsupported SubmitRecord.Label %s for %s in %s",
|
||||
lbl.toString(),
|
||||
rsrc.getPatchSet().getId(),
|
||||
rsrc.getChange().getProject().get()));
|
||||
}
|
||||
}
|
||||
throw new ResourceConflictException(msg.toString());
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unsupported SubmitRecord %s for %s in %s",
|
||||
record,
|
||||
rsrc.getPatchSet().getId(),
|
||||
rsrc.getChange().getProject().get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
|
||||
return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
|
||||
@Override
|
||||
public boolean apply(SubmitRecord input) {
|
||||
return input.status == OK;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String status(Change change) {
|
||||
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
|
||||
}
|
||||
|
||||
public static class CurrentRevision implements
|
||||
RestModifyView<ChangeResource, Input> {
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final Submit submit;
|
||||
private final ChangeJson json;
|
||||
|
||||
@Inject
|
||||
CurrentRevision(Provider<ReviewDb> dbProvider,
|
||||
Submit submit,
|
||||
ChangeJson json) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.submit = submit;
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Input> inputType() {
|
||||
return Input.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(ChangeResource rsrc, Input input) throws AuthException,
|
||||
ResourceConflictException, RepositoryNotFoundException, IOException,
|
||||
OrmException {
|
||||
PatchSet ps = dbProvider.get().patchSets()
|
||||
.get(rsrc.getChange().currentPatchSetId());
|
||||
if (ps == null) {
|
||||
throw new ResourceConflictException("current revision is missing");
|
||||
} else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
|
||||
throw new AuthException("current revision not accessible");
|
||||
}
|
||||
Output out = submit.apply(new RevisionResource(rsrc, ps), input);
|
||||
return json.format(out.change);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,207 +0,0 @@
|
||||
// Copyright (C) 2012 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.changedetail;
|
||||
|
||||
import static com.google.gerrit.reviewdb.client.ApprovalCategory.SUBMIT;
|
||||
|
||||
import com.google.gerrit.common.data.ReviewResult;
|
||||
import com.google.gerrit.common.data.SubmitRecord;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.ProjectUtil;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.AtomicUpdate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class Submit implements Callable<ReviewResult> {
|
||||
|
||||
public interface Factory {
|
||||
Submit create(PatchSet.Id patchSetId);
|
||||
}
|
||||
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final MergeOp.Factory opFactory;
|
||||
private final MergeQueue merger;
|
||||
private final ReviewDb db;
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final IdentifiedUser currentUser;
|
||||
|
||||
private final PatchSet.Id patchSetId;
|
||||
|
||||
@Inject
|
||||
Submit(final ChangeControl.Factory changeControlFactory,
|
||||
final MergeOp.Factory opFactory, final MergeQueue merger,
|
||||
final ReviewDb db, final GitRepositoryManager repoManager,
|
||||
final IdentifiedUser currentUser, @Assisted final PatchSet.Id patchSetId) {
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.opFactory = opFactory;
|
||||
this.merger = merger;
|
||||
this.db = db;
|
||||
this.repoManager = repoManager;
|
||||
this.currentUser = currentUser;
|
||||
|
||||
this.patchSetId = patchSetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewResult call() throws IllegalStateException,
|
||||
InvalidChangeOperationException, NoSuchChangeException, OrmException,
|
||||
IOException {
|
||||
final ReviewResult result = new ReviewResult();
|
||||
|
||||
final PatchSet patch = db.patchSets().get(patchSetId);
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final ChangeControl control = changeControlFactory.validateFor(changeId);
|
||||
result.setChangeId(changeId);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
List<SubmitRecord> submitResult = control.canSubmit(db, patch);
|
||||
if (submitResult.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"ChangeControl.canSubmit returned empty list");
|
||||
}
|
||||
|
||||
for (SubmitRecord submitRecord : submitResult) {
|
||||
switch (submitRecord.status) {
|
||||
case OK:
|
||||
if (!control.getRefControl().canSubmit()) {
|
||||
result.addError(new ReviewResult.Error(
|
||||
ReviewResult.Error.Type.SUBMIT_NOT_PERMITTED));
|
||||
}
|
||||
break;
|
||||
|
||||
case NOT_READY:
|
||||
StringBuilder errMsg = new StringBuilder();
|
||||
for (SubmitRecord.Label lbl : submitRecord.labels) {
|
||||
switch (lbl.status) {
|
||||
case OK:
|
||||
break;
|
||||
|
||||
case REJECT:
|
||||
if (errMsg.length() > 0) errMsg.append("; ");
|
||||
errMsg.append("change " + changeId + ": blocked by "
|
||||
+ lbl.label);
|
||||
break;
|
||||
|
||||
case NEED:
|
||||
if (errMsg.length() > 0) errMsg.append("; ");
|
||||
errMsg.append("change " + changeId + ": needs " + lbl.label);
|
||||
break;
|
||||
|
||||
case MAY:
|
||||
// The MAY label didn't cause the NOT_READY status
|
||||
break;
|
||||
|
||||
case IMPOSSIBLE:
|
||||
if (errMsg.length() > 0) errMsg.append("; ");
|
||||
errMsg.append("change " + changeId + ": needs " + lbl.label
|
||||
+ " (check project access)");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported SubmitRecord.Label.status (" + lbl.status
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
result.addError(new ReviewResult.Error(
|
||||
ReviewResult.Error.Type.SUBMIT_NOT_READY, errMsg.toString()));
|
||||
break;
|
||||
|
||||
case CLOSED:
|
||||
result.addError(new ReviewResult.Error(
|
||||
ReviewResult.Error.Type.CHANGE_IS_CLOSED));
|
||||
break;
|
||||
|
||||
case RULE_ERROR:
|
||||
result.addError(new ReviewResult.Error(
|
||||
ReviewResult.Error.Type.RULE_ERROR,
|
||||
submitResult.get(0).errorMessage));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Unsupported SubmitRecord.status + (" + submitRecord.status
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ProjectUtil.branchExists(repoManager, control.getChange().getDest())) {
|
||||
result.addError(new ReviewResult.Error(
|
||||
ReviewResult.Error.Type.DEST_BRANCH_NOT_FOUND,
|
||||
"Destination branch \"" + control.getChange().getDest().get()
|
||||
+ "\" not found."));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Submit the change if we can
|
||||
if (result.getErrors().isEmpty()) {
|
||||
final List<PatchSetApproval> allApprovals =
|
||||
new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet(
|
||||
patchSetId).toList());
|
||||
|
||||
final PatchSetApproval.Key akey =
|
||||
new PatchSetApproval.Key(patchSetId, currentUser.getAccountId(),
|
||||
SUBMIT);
|
||||
|
||||
PatchSetApproval approval = new PatchSetApproval(akey, (short) 1);
|
||||
for (final PatchSetApproval candidateApproval : allApprovals) {
|
||||
if (akey.equals(candidateApproval.getKey())) {
|
||||
candidateApproval.setValue((short) 1);
|
||||
candidateApproval.setGranted();
|
||||
approval = candidateApproval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
db.patchSetApprovals().upsert(Collections.singleton(approval));
|
||||
|
||||
final Change updatedChange = db.changes().atomicUpdate(changeId,
|
||||
new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus() == Change.Status.NEW) {
|
||||
change.setStatus(Change.Status.SUBMITTED);
|
||||
ChangeUtil.updated(change);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
});
|
||||
|
||||
if (updatedChange.getStatus() == Change.Status.SUBMITTED) {
|
||||
merger.merge(opFactory, updatedChange.getDest());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -28,7 +28,6 @@ import com.google.gerrit.server.account.VisibleGroups;
|
||||
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
|
||||
import com.google.gerrit.server.changedetail.PublishDraft;
|
||||
import com.google.gerrit.server.changedetail.RebaseChange;
|
||||
import com.google.gerrit.server.changedetail.Submit;
|
||||
import com.google.gerrit.server.git.AsyncReceiveCommits;
|
||||
import com.google.gerrit.server.git.BanCommit;
|
||||
import com.google.gerrit.server.git.CreateCodeReviewNotes;
|
||||
@@ -92,7 +91,6 @@ public class GerritRequestModule extends FactoryModule {
|
||||
factory(GroupDetailFactory.Factory.class);
|
||||
factory(GroupMembers.Factory.class);
|
||||
factory(CreateProject.Factory.class);
|
||||
factory(Submit.Factory.class);
|
||||
factory(SuggestParentCandidates.Factory.class);
|
||||
factory(BanCommit.Factory.class);
|
||||
}
|
||||
|
@@ -119,9 +119,9 @@ public class ChangeMergeQueue implements MergeQueue {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
|
||||
public void merge(Branch.NameKey branch) {
|
||||
if (start(branch)) {
|
||||
mergeImpl(mof, branch);
|
||||
mergeImpl(branch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,16 +199,6 @@ public class ChangeMergeQueue implements MergeQueue {
|
||||
e.needMerge = false;
|
||||
}
|
||||
|
||||
private void mergeImpl(MergeOp.Factory opFactory, Branch.NameKey branch) {
|
||||
try {
|
||||
opFactory.create(branch).merge();
|
||||
} catch (Throwable e) {
|
||||
log.error("Merge attempt for " + branch + " failed", e);
|
||||
} finally {
|
||||
finish(branch);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeImpl(final Branch.NameKey branch) {
|
||||
try {
|
||||
threadScoper.scope(new Callable<Void>(){
|
||||
|
@@ -19,9 +19,7 @@ import com.google.gerrit.reviewdb.client.Branch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface MergeQueue {
|
||||
void merge(MergeOp.Factory mof, Branch.NameKey branch);
|
||||
|
||||
void merge(Branch.NameKey branch);
|
||||
void schedule(Branch.NameKey branch);
|
||||
|
||||
void recheckAfter(Branch.NameKey branch, long delay, TimeUnit delayUnit);
|
||||
}
|
||||
|
@@ -307,6 +307,10 @@ public class ChangeControl {
|
||||
return canSubmit(db, patchSet, null, false, true);
|
||||
}
|
||||
|
||||
public boolean canSubmit() {
|
||||
return getRefControl().canSubmit();
|
||||
}
|
||||
|
||||
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
|
||||
return canSubmit(db, patchSet, null, false, false);
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ import com.google.gerrit.server.change.RevisionResource;
|
||||
import com.google.gerrit.server.change.Restore;
|
||||
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
|
||||
import com.google.gerrit.server.changedetail.PublishDraft;
|
||||
import com.google.gerrit.server.changedetail.Submit;
|
||||
import com.google.gerrit.server.change.Submit;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
@@ -137,7 +137,7 @@ public class ReviewCommand extends SshCommand {
|
||||
private Provider<Restore> restoreProvider;
|
||||
|
||||
@Inject
|
||||
private Submit.Factory submitFactory;
|
||||
private Provider<Submit> submitProvider;
|
||||
|
||||
private List<ApproveOption> optionList;
|
||||
|
||||
@@ -242,8 +242,13 @@ public class ReviewCommand extends SshCommand {
|
||||
}
|
||||
}
|
||||
if (submitChange) {
|
||||
final ReviewResult result = submitFactory.create(patchSetId).call();
|
||||
handleReviewResultErrors(result);
|
||||
Submit submit = submitProvider.get();
|
||||
Submit.Input input = new Submit.Input();
|
||||
input.waitForMerge = true;
|
||||
submit.apply(new RevisionResource(
|
||||
new ChangeResource(ctl),
|
||||
db.patchSets().get(patchSetId)),
|
||||
input);
|
||||
}
|
||||
} catch (InvalidChangeOperationException e) {
|
||||
throw error(e.getMessage());
|
||||
@@ -253,6 +258,8 @@ public class ReviewCommand extends SshCommand {
|
||||
throw error(e.getMessage());
|
||||
} catch (BadRequestException e) {
|
||||
throw error(e.getMessage());
|
||||
} catch (ResourceConflictException e) {
|
||||
throw error(e.getMessage());
|
||||
}
|
||||
|
||||
if (publishPatchSet) {
|
||||
|
Reference in New Issue
Block a user