Add REST endpoints for draft changes

Change-Id: Ia30917267cffdeb4013e1415bed5e2a17ffdef6d
This commit is contained in:
David Ostrovsky 2013-09-10 23:10:23 +02:00
parent 93e165ccc7
commit 0d69c23dd9
7 changed files with 535 additions and 12 deletions

View File

@ -932,6 +932,46 @@ message is contained in the response body.
blocked by Verified
----
[[publish-draft-change]]
Publish Draft Change
~~~~~~~~~~~~~~~~~~~~
[verse]
'POST /changes/link:#change-id[\{change-id\}]/publish'
Publishes a draft change.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
Content-Type: application/json;charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[delete-draft-change]]
Delete Draft Change
~~~~~~~~~~~~~~~~~~~
[verse]
'DELETE /changes/link:#change-id[\{change-id\}]'
or
'POST /changes/link:#change-id[\{change-id\}]/delete'
Deletes a draft change.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
Content-Type: application/json;charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[reviewer-endpoints]]
Reviewer Endpoints
------------------
@ -1504,6 +1544,46 @@ message is contained in the response body.
"revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
----
[[publish-draft-revision]]
Publish Draft Revision
~~~~~~~~~~~~~~~~~~~~~~
[verse]
'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
Publishes a draft revision.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
Content-Type: application/json;charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[delete-draft-revision]]
Delete Draft Revision
~~~~~~~~~~~~~~~~~~~~~
[verse]
'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
or
'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/delete'
Deletes a draft revision.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
Content-Type: application/json;charset=UTF-8
----
.Response
----
HTTP/1.1 204 No Content
----
[[get-patch]]
Get Patch
~~~~~~~~~

View File

@ -397,6 +397,13 @@ public class ChangeUtil {
final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
throws NoSuchChangeException, OrmException, IOException {
final Change.Id changeId = patchSetId.getParentKey();
deleteDraftChange(changeId, gitManager, gitRefUpdated, db);
}
public static void deleteDraftChange(final Change.Id changeId,
GitRepositoryManager gitManager,
final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
throws NoSuchChangeException, OrmException, IOException {
final Change change = db.changes().get(changeId);
if (change == null || change.getStatus() != Change.Status.DRAFT) {
throw new NoSuchChangeException(changeId);

View File

@ -0,0 +1,99 @@
// Copyright (C) 2013 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 com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.DeleteDraftChange.Input;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
public class DeleteDraftChange implements RestModifyView<ChangeResource, Input> {
public static class Input {
}
protected final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager gitManager;
private final GitReferenceUpdated gitRefUpdated;
@Inject
public DeleteDraftChange(Provider<ReviewDb> dbProvider,
GitRepositoryManager gitManager,
GitReferenceUpdated gitRefUpdated,
PatchSetInfoFactory patchSetInfoFactory) {
this.dbProvider = dbProvider;
this.gitManager = gitManager;
this.gitRefUpdated = gitRefUpdated;
}
@Override
public Object apply(ChangeResource rsrc, Input input)
throws ResourceConflictException, AuthException,
ResourceNotFoundException, OrmException, IOException {
if (rsrc.getChange().getStatus() != Status.DRAFT) {
throw new ResourceConflictException("Change is not a draft");
}
if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
throw new AuthException("Not permitted to delete this draft change");
}
try {
ChangeUtil.deleteDraftChange(rsrc.getChange().getId(),
gitManager, gitRefUpdated, dbProvider.get());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
return Response.none();
}
static class Action extends DeleteDraftChange implements UiAction<ChangeResource> {
@Inject
public Action(Provider<ReviewDb> dbProvider,
GitRepositoryManager gitManager,
GitReferenceUpdated gitRefUpdated,
PatchSetInfoFactory patchSetInfoFactory) {
super(dbProvider, gitManager, gitRefUpdated, patchSetInfoFactory);
}
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
try {
return new UiAction.Description()
.setTitle(String.format("Delete Draft Change %d",
rsrc.getChange().getChangeId()))
.setVisible(rsrc.getChange().getStatus() == Status.DRAFT
&& rsrc.getControl().canDeleteDraft(dbProvider.get()));
} catch (OrmException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@ -0,0 +1,170 @@
// Copyright (C) 2013 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 com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
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.Provider;
import java.io.IOException;
public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input> {
public static class Input {
}
protected final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager gitManager;
private final GitReferenceUpdated gitRefUpdated;
private final PatchSetInfoFactory patchSetInfoFactory;
@Inject
public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
GitRepositoryManager gitManager,
GitReferenceUpdated gitRefUpdated,
PatchSetInfoFactory patchSetInfoFactory) {
this.dbProvider = dbProvider;
this.gitManager = gitManager;
this.gitRefUpdated = gitRefUpdated;
this.patchSetInfoFactory = patchSetInfoFactory;
}
@Override
public Object apply(RevisionResource rsrc, Input input)
throws ResourceNotFoundException, AuthException, OrmException,
IOException, ResourceConflictException {
PatchSet patchSet = rsrc.getPatchSet();
PatchSet.Id patchSetId = patchSet.getId();
Change change = rsrc.getChange();
if (!patchSet.isDraft()) {
throw new ResourceConflictException("Patch set is not a draft.");
}
if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
throw new AuthException("Not permitted to delete this draft patch set");
}
deleteDraftPatchSet(patchSet, change);
deleteOrUpdateDraftChange(patchSetId, change);
return Response.none();
}
private void deleteDraftPatchSet(PatchSet patchSet, Change change)
throws ResourceNotFoundException, OrmException, IOException {
try {
ChangeUtil.deleteOnlyDraftPatchSet(patchSet,
change, gitManager, gitRefUpdated, dbProvider.get());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
private void deleteOrUpdateDraftChange(PatchSet.Id patchSetId,
Change change) throws OrmException, ResourceNotFoundException,
IOException {
if (dbProvider.get()
.patchSets()
.byChange(change.getId())
.toList().size() == 0) {
deleteDraftChange(patchSetId);
} else {
if (change.currentPatchSetId().equals(patchSetId)) {
updateCurrentPatchSet(dbProvider.get(), change,
previousPatchSetInfo(patchSetId));
}
}
}
private void deleteDraftChange(PatchSet.Id patchSetId)
throws OrmException, IOException, ResourceNotFoundException {
try {
ChangeUtil.deleteDraftChange(patchSetId,
gitManager, gitRefUpdated, dbProvider.get());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
private PatchSetInfo previousPatchSetInfo(PatchSet.Id patchSetId)
throws ResourceNotFoundException {
try {
return patchSetInfoFactory.get(dbProvider.get(),
new PatchSet.Id(patchSetId.getParentKey(),
patchSetId.get() - 1));
} catch (PatchSetInfoNotAvailableException e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
private static void updateCurrentPatchSet(final ReviewDb db,
final Change change, final PatchSetInfo psInfo)
throws OrmException {
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change c) {
c.setCurrentPatchSet(psInfo);
ChangeUtil.updated(c);
return c;
}
});
}
static class Action extends DeleteDraftPatchSet implements UiAction<RevisionResource> {
@Inject
public Action(Provider<ReviewDb> dbProvider,
GitRepositoryManager gitManager,
GitReferenceUpdated gitRefUpdated,
PatchSetInfoFactory patchSetInfoFactory) {
super(dbProvider, gitManager, gitRefUpdated, patchSetInfoFactory);
}
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
PatchSet.Id current = rsrc.getChange().currentPatchSetId();
try {
int psCount = dbProvider.get().patchSets()
.byChange(rsrc.getChange().getId()).toList().size();
return new UiAction.Description()
.setTitle(String.format("Delete Draft Revision %d",
rsrc.getPatchSet().getPatchSetId()))
.setVisible(rsrc.getPatchSet().isDraft()
&& rsrc.getPatchSet().getId().equals(current)
&& rsrc.getControl().canDeleteDraft(dbProvider.get())
&& psCount > 1);
} catch (OrmException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@ -49,7 +49,10 @@ public class Module extends RestApiModule {
get(CHANGE_KIND, "topic").to(GetTopic.class);
put(CHANGE_KIND, "topic").to(PutTopic.class);
delete(CHANGE_KIND, "topic").to(PutTopic.class);
delete(CHANGE_KIND).to(DeleteDraftChange.class);
post(CHANGE_KIND, "delete").to(DeleteDraftChange.Action.class);
post(CHANGE_KIND, "abandon").to(Abandon.class);
post(CHANGE_KIND, "publish").to(Publish.CurrentRevision.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@ -64,7 +67,10 @@ public class Module extends RestApiModule {
child(CHANGE_KIND, "revisions").to(Revisions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
post(REVISION_KIND, "delete").to(DeleteDraftPatchSet.Action.class);
get(REVISION_KIND, "mergeable").to(Mergeable.class);
post(REVISION_KIND, "publish").to(Publish.class);
get(REVISION_KIND, "related").to(GetRelated.class);
get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);

View File

@ -0,0 +1,161 @@
// Copyright (C) 2013 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 com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.mail.PatchSetNotificationSender;
import com.google.gerrit.server.change.Publish.Input;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
public class Publish implements RestModifyView<RevisionResource, Input>,
UiAction<RevisionResource> {
public static class Input {
}
private final Provider<ReviewDb> dbProvider;
private final PatchSetNotificationSender sender;
private final ChangeHooks hooks;
private final ChangeIndexer indexer;
@Inject
public Publish(Provider<ReviewDb> dbProvider,
PatchSetNotificationSender sender,
ChangeHooks hooks,
ChangeIndexer indexer) {
this.dbProvider = dbProvider;
this.sender = sender;
this.hooks = hooks;
this.indexer = indexer;
}
@Override
public Object apply(RevisionResource rsrc, Input input) throws IOException,
ResourceNotFoundException, ResourceConflictException,
OrmException, AuthException {
if (!rsrc.getPatchSet().isDraft()) {
throw new ResourceConflictException("Patch set is not a draft");
}
if (!rsrc.getControl().canPublish(dbProvider.get())) {
throw new AuthException("Cannot publish this draft patch set");
}
PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
Change updatedChange = updateDraftChange(rsrc);
try {
if (!updatedPatchSet.isDraft()
|| updatedChange.getStatus() == Change.Status.NEW) {
indexer.index(updatedChange);
hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, dbProvider.get());
sender.send(rsrc.getChange().getStatus() == Change.Status.DRAFT,
rsrc.getUser(), updatedChange, updatedPatchSet,
rsrc.getControl().getLabelTypes());
}
} catch (PatchSetInfoNotAvailableException e) {
throw new ResourceNotFoundException(e.getMessage());
}
return Response.none();
}
private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
Change updatedChange = dbProvider.get().changes()
.atomicUpdate(rsrc.getChange().getId(),
new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus() == Change.Status.DRAFT) {
change.setStatus(Change.Status.NEW);
ChangeUtil.updated(change);
}
return change;
}
});
return updatedChange;
}
private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
final PatchSet updatedPatchSet = dbProvider.get().patchSets()
.atomicUpdate(rsrc.getPatchSet().getId(),
new AtomicUpdate<PatchSet>() {
@Override
public PatchSet update(PatchSet patchset) {
patchset.setDraft(false);
return patchset;
}
});
return updatedPatchSet;
}
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
PatchSet.Id current = rsrc.getChange().currentPatchSetId();
try {
return new UiAction.Description()
.setTitle(String.format("Publish Revision %d",
rsrc.getPatchSet().getPatchSetId()))
.setVisible(rsrc.getPatchSet().isDraft()
&& rsrc.getPatchSet().getId().equals(current)
&& rsrc.getControl().canPublish(dbProvider.get()));
} catch (OrmException e) {
throw new IllegalStateException(e);
}
}
public static class CurrentRevision implements
RestModifyView<ChangeResource, Input> {
private final Provider<ReviewDb> dbProvider;
private final Publish publish;
@Inject
CurrentRevision(Provider<ReviewDb> dbProvider,
Publish publish) {
this.dbProvider = dbProvider;
this.publish = publish;
}
@Override
public Object apply(ChangeResource rsrc, Input input) throws AuthException,
ResourceConflictException, ResourceConflictException, IOException,
OrmException, ResourceNotFoundException, AuthException {
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");
}
return publish.apply(new RevisionResource(rsrc, ps), input);
}
}
}

View File

@ -31,11 +31,11 @@ import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.DeleteDraftPatchSet;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
import com.google.gerrit.server.changedetail.PublishDraft;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.project.ChangeControl;
@ -131,7 +131,7 @@ public class ReviewCommand extends SshCommand {
private ReviewDb db;
@Inject
private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
private DeleteDraftPatchSet deleteDraftPatchSetImpl;
@Inject
private ProjectControl.Factory projectControlFactory;
@ -285,6 +285,16 @@ public class ReviewCommand extends SshCommand {
new ChangeResource(ctl), patchSet),
input);
}
if (publishPatchSet) {
final ReviewResult result =
publishDraftFactory.create(patchSet.getId()).call();
handleReviewResultErrors(result);
} else if (deleteDraftPatchSet) {
deleteDraftPatchSetImpl.apply(new RevisionResource(
new ChangeResource(ctl), patchSet),
new DeleteDraftPatchSet.Input());
}
} catch (InvalidChangeOperationException e) {
throw error(e.getMessage());
} catch (IllegalStateException e) {
@ -296,16 +306,6 @@ public class ReviewCommand extends SshCommand {
} catch (ResourceConflictException e) {
throw error(e.getMessage());
}
if (publishPatchSet) {
final ReviewResult result =
publishDraftFactory.create(patchSet.getId()).call();
handleReviewResultErrors(result);
} else if (deleteDraftPatchSet) {
final ReviewResult result =
deleteDraftPatchSetFactory.create(patchSet.getId()).call();
handleReviewResultErrors(result);
}
}
private void handleReviewResultErrors(final ReviewResult result) {