Merge topic 'inline-3'

* changes:
  InlineEdit: Allow to list files in change edit
  InlineEdit: Add DELETE /changes/<id>/edit REST endpoint
  InlineEdit: Add POST /changes/<id>/rebase_edit REST endpoint
  InlineEdit: Add POST /changes/<id>/publish_edit REST endpoint
  InlineEdit: Add GET /changes/<id>/edit/path%2fto%2ffile REST endpoint
  InlineEdit: Add DELETE /changes/<id>/edit/path%2fto%2ffile REST endpoint
This commit is contained in:
Dave Borowitz 2014-08-18 20:13:38 +00:00 committed by Gerrit Code Review
commit 97aecabbb8
11 changed files with 707 additions and 22 deletions

View File

@ -1162,6 +1162,9 @@ As response an link:#edit-info[EditInfo] entity is returned that
describes the change edit, or "`204 No Content`" when change edit doesn't
exist for this change. Change edits are stored on special branches and there
can be max one edit per user per change. Edits aren't tracked in the database.
When request parameter `list` is provided the response also includes the file
list. When `base` request parameter is provided the file list is computed
against this base revision.
.Response
----
@ -1247,6 +1250,117 @@ response "`204 No Content`" is returned.
HTTP/1.1 204 No Content
----
[[delete-edit-file]]
=== Delete file in Change Edit
--
'DELETE /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile'
--
Deletes a file from a change edit. This deletes the file from the repository
completely. This is not the same as reverting or restoring a file to its
previous contents.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
----
When change edit doesn't exist for this change yet it is created.
.Response
----
HTTP/1.1 204 No Content
----
[[get-edit-file]]
=== Retrieve file content from Change Edit
--
'GET /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
--
Retrieves content of a file from a change edit.
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
----
The content of the file is returned as text encoded inside base64. When
specified file was deleted in the change edit "`204 No Content`" is returned.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: text/plain;charset=ISO-8859-1
X-FYI-Content-Encoding: base64
RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
----
[[publish-edit]]
=== Publish Change Edit
--
'POST /changes/link:#change-id[\{change-id\}]/publish_edit
--
Promotes change edit to a regular patch set.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish_edit HTTP/1.0
----
As response "`204 No Content`" is returned.
.Response
----
HTTP/1.1 204 No Content
----
[[rebase-edit]]
=== Rebase Change Edit
--
'POST /changes/link:#change-id[\{change-id\}]/rebase_edit
--
Rebases change edit on top of latest patch set.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/rebase_edit HTTP/1.0
----
When change was rebased on top of latest patch set, response
"`204 No Content`" is returned. When change edit is aready
based on top of the latest patch set, the response
"`409 Conflict`" is returned.
.Response
----
HTTP/1.1 204 No Content
----
[[delete-edit]]
=== Delete Change Edit
--
'DELETE /changes/link:#change-id[\{change-id\}]/edit'
--
Deletes change edit.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
----
As response "`204 No Content`" is returned.
.Response
----
HTTP/1.1 204 No Content
----
[[reviewer-endpoints]]
== Reviewer Endpoints
@ -3705,6 +3819,9 @@ The `EditInfo` entity contains information about a change edit.
|Field Name ||Description
|`commit` ||The commit of change edit as
link:#commit-info[CommitInfo] entity.
|`files` |optional|
The files of the change edit as a map that maps the file names to
link:#file-info[FileInfo] entities.
|===========================
[[restore-path-input]]

View File

@ -3,5 +3,8 @@ include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
srcs = ['ChangeEditIT.java'],
labels = ['edit'],
deps = ['//lib/joda:joda-time'],
deps = [
'//lib/commons:codec',
'//lib/joda:joda-time',
],
)

View File

@ -17,6 +17,7 @@ package com.google.gerrit.acceptance.edit;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_NO_CONTENT;
import static org.apache.http.HttpStatus.SC_OK;
import static org.junit.Assert.assertArrayEquals;
@ -27,6 +28,7 @@ import static org.junit.Assert.fail;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.PushOneCommit;
@ -39,8 +41,8 @@ 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.IdentifiedUser;
import com.google.gerrit.server.change.ChangeEdits.Put;
import com.google.gerrit.server.change.ChangeEdits.Post;
import com.google.gerrit.server.change.ChangeEdits.Put;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditModifier;
@ -50,6 +52,8 @@ import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.util.Providers;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@ -63,6 +67,7 @@ import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
public class ChangeEditIT extends AbstractDaemonTest {
@ -165,6 +170,45 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertFalse(editUtil.byChange(change).isPresent());
}
@Test
public void publishEditRest() throws Exception {
PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
oldCurrentPatchSet));
assertEquals(RefUpdate.Result.FORCED,
modifier.modifyFile(
editUtil.byChange(change).get(),
FILE_NAME,
CONTENT_NEW));
Optional<ChangeEdit> edit = editUtil.byChange(change);
RestResponse r = session.post(urlPublish());
assertEquals(SC_NO_CONTENT, r.getStatusCode());
edit = editUtil.byChange(change);
assertFalse(edit.isPresent());
PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId);
assertFalse(oldCurrentPatchSet.getId().equals(newCurrentPatchSet.getId()));
}
@Test
public void deleteEditRest() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
ps));
assertEquals(RefUpdate.Result.FORCED,
modifier.modifyFile(
editUtil.byChange(change).get(),
FILE_NAME,
CONTENT_NEW));
Optional<ChangeEdit> edit = editUtil.byChange(change);
RestResponse r = session.delete(urlEdit());
assertEquals(SC_NO_CONTENT, r.getStatusCode());
edit = editUtil.byChange(change);
assertFalse(edit.isPresent());
}
@Test
public void rebaseEdit() throws Exception {
assertEquals(RefUpdate.Result.NEW,
@ -195,6 +239,37 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertFalse(beforeRebase.equals(afterRebase));
}
@Test
public void rebaseEditRest() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
ps));
assertEquals(RefUpdate.Result.FORCED,
modifier.modifyFile(
editUtil.byChange(change).get(),
FILE_NAME,
CONTENT_NEW));
ChangeEdit edit = editUtil.byChange(change).get();
PatchSet current = getCurrentPatchSet(changeId);
assertEquals(current.getPatchSetId() - 1,
edit.getBasePatchSet().getPatchSetId());
Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
RestResponse r = session.post(urlRebase());
assertEquals(SC_NO_CONTENT, r.getStatusCode());
edit = editUtil.byChange(change).get();
assertArrayEquals(CONTENT_NEW,
toBytes(fileUtil.getContent(edit.getChange().getProject(),
edit.getRevision().get(), FILE_NAME)));
assertArrayEquals(CONTENT_NEW2,
toBytes(fileUtil.getContent(edit.getChange().getProject(),
edit.getRevision().get(), FILE_NAME2)));
assertEquals(current.getPatchSetId(),
edit.getBasePatchSet().getPatchSetId());
Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
assertFalse(beforeRebase.equals(afterRebase));
}
@Test
public void updateExistingFile() throws Exception {
assertEquals(RefUpdate.Result.NEW,
@ -231,7 +306,7 @@ public class ChangeEditIT extends AbstractDaemonTest {
FILE_NAME,
CONTENT_NEW));
edit = editUtil.byChange(change);
EditInfo info = toEditInfo();
EditInfo info = toEditInfo(false);
assertEquals(edit.get().getRevision().get(), info.commit.commit);
assertEquals(1, info.commit.parents.size());
@ -242,6 +317,26 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertEquals(SC_NO_CONTENT, r.getStatusCode());
}
@Test
public void retrieveFilesInEdit() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
ps));
Optional<ChangeEdit> edit = editUtil.byChange(change);
assertEquals(RefUpdate.Result.FORCED,
modifier.modifyFile(
edit.get(),
FILE_NAME,
CONTENT_NEW));
EditInfo info = toEditInfo(true);
assertEquals(2, info.files.size());
List<String> l = Lists.newArrayList(info.files.keySet());
assertEquals("/COMMIT_MSG", l.get(0));
assertEquals("foo", l.get(1));
}
@Test
public void deleteExistingFile() throws Exception {
assertEquals(RefUpdate.Result.NEW,
@ -262,6 +357,41 @@ public class ChangeEditIT extends AbstractDaemonTest {
}
}
@Test
public void createEditByDeletingExistingFileRest() throws Exception {
RestResponse r = session.delete(urlEditFile());
assertEquals(SC_NO_CONTENT, r.getStatusCode());
Optional<ChangeEdit> edit = editUtil.byChange(change);
try {
fileUtil.getContent(edit.get().getChange().getProject(),
edit.get().getRevision().get(), FILE_NAME);
fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) {
}
}
@Test
public void deletingNonExistingEditRest() throws Exception {
RestResponse r = session.delete(urlEdit());
assertEquals(SC_BAD_REQUEST, r.getStatusCode());
}
@Test
public void deleteExistingFileRest() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
ps));
assertEquals(SC_NO_CONTENT, session.delete(urlEditFile()).getStatusCode());
Optional<ChangeEdit> edit = editUtil.byChange(change);
try {
fileUtil.getContent(edit.get().getChange().getProject(),
edit.get().getRevision().get(), FILE_NAME);
fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) {
}
}
@Test
public void restoreDeletedFileInEdit() throws Exception {
assertEquals(RefUpdate.Result.NEW,
@ -412,6 +542,44 @@ public class ChangeEditIT extends AbstractDaemonTest {
edit.get().getRevision().get(), FILE_NAME)));
}
@Test
public void getFileContentRest() throws Exception {
Put.Input in = new Put.Input();
in.content = RestSession.newRawInput(CONTENT_NEW);
assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
in.content).getStatusCode());
Optional<ChangeEdit> edit = editUtil.byChange(change);
assertEquals(RefUpdate.Result.FORCED,
modifier.modifyFile(
edit.get(),
FILE_NAME,
CONTENT_NEW2));
edit = editUtil.byChange(change);
RestResponse r = session.get(urlEditFile());
assertEquals(SC_OK, r.getStatusCode());
String content = r.getEntityContent();
assertEquals(StringUtils.newStringUtf8(CONTENT_NEW2),
StringUtils.newStringUtf8(Base64.decodeBase64(content)));
}
@Test
public void getFileNotFoundRest() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
change,
ps));
assertEquals(SC_NO_CONTENT, session.delete(urlEditFile()).getStatusCode());
Optional<ChangeEdit> edit = editUtil.byChange(change);
try {
fileUtil.getContent(edit.get().getChange().getProject(),
edit.get().getRevision().get(), FILE_NAME);
fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) {
}
RestResponse r = session.get(urlEditFile());
assertEquals(SC_NO_CONTENT, r.getStatusCode());
}
@Test
public void addNewFile() throws Exception {
assertEquals(RefUpdate.Result.NEW,
@ -529,8 +697,25 @@ public class ChangeEditIT extends AbstractDaemonTest {
+ FILE_NAME;
}
private EditInfo toEditInfo() throws IOException {
RestResponse r = session.get(urlEdit());
private String urlGetFiles() {
return urlEdit()
+ "?list";
}
private String urlPublish() {
return "/changes/"
+ change.getChangeId()
+ "/publish_edit";
}
private String urlRebase() {
return "/changes/"
+ change.getChangeId()
+ "/rebase_edit";
}
private EditInfo toEditInfo(boolean files) throws IOException {
RestResponse r = session.get(files ? urlGetFiles() : urlEdit());
assertEquals(SC_OK, r.getStatusCode());
return newGson().fromJson(r.getReader(), EditInfo.class);
}

View File

@ -14,6 +14,9 @@
package com.google.gerrit.extensions.common;
import java.util.Map;
public class EditInfo {
public CommitInfo commit;
public Map<String, FileInfo> files;
}

View File

@ -208,6 +208,9 @@ public class ChangeInfo extends JavaScriptObject {
public final native String name() /*-{ return this.name; }-*/;
public final native CommitInfo commit() /*-{ return this.commit; }-*/;
public final native boolean has_files() /*-{ return this.hasOwnProperty('files') }-*/;
public final native NativeMap<FileInfo> files() /*-{ return this.files; }-*/;
protected EditInfo() {
}
}

View File

@ -21,6 +21,7 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AcceptsDelete;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@ -36,11 +37,13 @@ import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
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.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditJson;
import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@ -49,30 +52,36 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import org.kohsuke.args4j.Option;
import java.io.IOException;
@Singleton
public class ChangeEdits implements
ChildCollection<ChangeResource, ChangeEditResource>,
AcceptsCreate<ChangeResource>,
AcceptsPost<ChangeResource> {
AcceptsPost<ChangeResource>,
AcceptsDelete<ChangeResource> {
private final DynamicMap<RestView<ChangeEditResource>> views;
private final Create.Factory createFactory;
private final Detail detail;
private final DeleteEdit.Factory deleteEditFactory;
private final Provider<Detail> detail;
private final ChangeEditUtil editUtil;
private final Post post;
@Inject
ChangeEdits(DynamicMap<RestView<ChangeEditResource>> views,
Create.Factory createFactory,
Detail detail,
Provider<Detail> detail,
ChangeEditUtil editUtil,
Post post) {
Post post,
DeleteEdit.Factory deleteEditFactory) {
this.views = views;
this.createFactory = createFactory;
this.detail = detail;
this.editUtil = editUtil;
this.post = post;
this.deleteEditFactory = deleteEditFactory;
}
@Override
@ -82,7 +91,7 @@ public class ChangeEdits implements
@Override
public RestView<ChangeResource> list() {
return detail;
return detail.get();
}
@Override
@ -116,6 +125,14 @@ public class ChangeEdits implements
* change edit wasn't created yet. Change edit is created and PUT
* handler is called.
*/
@SuppressWarnings("unchecked")
@Override
public DeleteEdit delete(ChangeResource parent, IdString id)
throws RestApiException {
return deleteEditFactory.create(parent.getChange(),
id != null ? id.get() : null);
}
static class Create implements
RestModifyView<ChangeResource, Put.Input> {
@ -172,27 +189,108 @@ public class ChangeEdits implements
}
}
@Singleton
static class DeleteEdit implements
RestModifyView<ChangeResource, DeleteEdit.Input> {
public static class Input {
}
interface Factory {
DeleteEdit create(Change change, String path);
}
private final ChangeEditUtil editUtil;
private final ChangeEditModifier editModifier;
private final Provider<ReviewDb> db;
private final String path;
@Inject
DeleteEdit(ChangeEditUtil editUtil,
ChangeEditModifier editModifier,
Provider<ReviewDb> db,
@Assisted @Nullable String path) {
this.editUtil = editUtil;
this.editModifier = editModifier;
this.db = db;
this.path = path;
}
@Override
public Response<?> apply(ChangeResource rsrc, DeleteEdit.Input in)
throws IOException, AuthException, ResourceConflictException,
OrmException, InvalidChangeOperationException, BadRequestException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (edit.isPresent() && path == null) {
// Edit is wiped out
editUtil.delete(edit.get());
} else if (!edit.isPresent() && path != null) {
// Edit is created on top of current patch set by deleting path.
// Even if the latest patch set changed since the user triggered
// the operation, deleting the whole file is probably still what
// they intended.
editModifier.createEdit(rsrc.getChange(), db.get().patchSets().get(
rsrc.getChange().currentPatchSetId()));
edit = editUtil.byChange(rsrc.getChange());
editModifier.deleteFile(edit.get(), path);
} else {
// Bad request
throw new BadRequestException(
"change edit doesn't exist and no path was provided");
}
return Response.none();
}
}
static class Detail implements RestReadView<ChangeResource> {
private final ChangeEditUtil editUtil;
private final ChangeEditJson editJson;
private final FileInfoJson fileInfoJson;
private final Revisions revisions;
@Option(name = "--base", metaVar = "revision-id")
String base;
@Option(name = "--list", metaVar = "LIST")
boolean list;
@Inject
Detail(ChangeEditJson editJson,
ChangeEditUtil editUtil) {
Detail(ChangeEditUtil editUtil,
ChangeEditJson editJson,
FileInfoJson fileInfoJson,
Revisions revisions) {
this.editJson = editJson;
this.editUtil = editUtil;
this.fileInfoJson = fileInfoJson;
this.revisions = revisions;
}
@Override
public Response<EditInfo> apply(ChangeResource rsrc) throws AuthException,
IOException, NoSuchChangeException, InvalidChangeOperationException,
ResourceNotFoundException {
IOException, InvalidChangeOperationException,
ResourceNotFoundException, OrmException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (edit.isPresent()) {
return Response.ok(editJson.toEditInfo(edit.get()));
if (!edit.isPresent()) {
return Response.none();
}
return Response.none();
EditInfo editInfo = editJson.toEditInfo(edit.get());
if (list) {
PatchSet basePatchSet = null;
if (base != null) {
RevisionResource baseResource = revisions.parse(
rsrc, IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
}
try {
editInfo.files =
fileInfoJson.toFileInfoMap(
rsrc.getChange(),
edit.get().getRevision(),
basePatchSet);
} catch (PatchListNotAvailableException e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
return Response.ok(editInfo);
}
}
@ -280,4 +378,58 @@ public class ChangeEdits implements
return Response.none();
}
}
/**
* Handler to delete a file.
* <p>
* This deletes the file from the repository completely. This is not the same
* as reverting or restoring a file to its previous contents.
*/
@Singleton
static class DeleteContent implements
RestModifyView<ChangeEditResource, DeleteContent.Input> {
public static class Input {
}
private final ChangeEditModifier editModifier;
@Inject
DeleteContent(ChangeEditModifier editModifier) {
this.editModifier = editModifier;
}
@Override
public Response<?> apply(ChangeEditResource rsrc, DeleteContent.Input input)
throws AuthException, ResourceConflictException {
try {
editModifier.deleteFile(rsrc.getChangeEdit(), rsrc.getPath());
} catch(InvalidChangeOperationException | IOException e) {
throw new ResourceConflictException(e.getMessage());
}
return Response.none();
}
}
@Singleton
static class Get implements RestReadView<ChangeEditResource> {
private final FileContentUtil fileContentUtil;
@Inject
Get(FileContentUtil fileContentUtil) {
this.fileContentUtil = fileContentUtil;
}
@Override
public Response<?> apply(ChangeEditResource rsrc)
throws ResourceNotFoundException, IOException {
try {
return Response.ok(fileContentUtil.getContent(
rsrc.getChangeEdit().getChange().getProject(),
rsrc.getChangeEdit().getRevision().get(),
rsrc.getPath()));
} catch (ResourceNotFoundException rnfe) {
return Response.none();
}
}
}
}

View File

@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@ -44,15 +45,15 @@ public class FileInfoJson {
Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
return toFileInfoMap(change, patchSet, null);
return toFileInfoMap(change, patchSet.getRevision(), null);
}
Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet, @Nullable PatchSet base)
Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, @Nullable PatchSet base)
throws PatchListNotAvailableException {
ObjectId a = (base == null)
? null
: ObjectId.fromString(base.getRevision().get());
ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
ObjectId b = ObjectId.fromString(revision.get());
PatchList list = patchListCache.get(
new PatchListKey(change.getProject(), a, b, Whitespace.IGNORE_NONE));

View File

@ -141,7 +141,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
try {
Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
resource.getPatchSet(),
resource.getPatchSet().getRevision(),
basePatchSet));
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));

View File

@ -102,7 +102,11 @@ public class Module extends RestApiModule {
get(FILE_KIND, "diff").to(GetDiff.class);
child(CHANGE_KIND, "edit").to(ChangeEdits.class);
child(CHANGE_KIND, "publish_edit").to(PublishChangeEdit.class);
child(CHANGE_KIND, "rebase_edit").to(RebaseChangeEdit.class);
put(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Put.class);
delete(CHANGE_EDIT_KIND).to(ChangeEdits.DeleteContent.class);
get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
install(new FactoryModule() {
@Override
@ -113,6 +117,7 @@ public class Module extends RestApiModule {
factory(ChangeInserter.Factory.class);
factory(PatchSetInserter.Factory.class);
factory(ChangeEdits.Create.Factory.class);
factory(ChangeEdits.DeleteEdit.Factory.class);
}
});
}

View File

@ -0,0 +1,99 @@
// Copyright (C) 2014 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.common.base.Optional;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
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.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
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.Singleton;
import java.io.IOException;
@Singleton
public class PublishChangeEdit implements
ChildCollection<ChangeResource, ChangeEditResource>,
AcceptsPost<ChangeResource> {
private final Publish publish;
@Inject
PublishChangeEdit(Publish publish) {
this.publish = publish;
}
@Override
public DynamicMap<RestView<ChangeEditResource>> views() {
throw new IllegalStateException("not yet implemented");
}
@Override
public RestView<ChangeResource> list() {
throw new IllegalStateException("not yet implemented");
}
@Override
public ChangeEditResource parse(ChangeResource parent, IdString id)
throws ResourceNotFoundException, Exception {
throw new IllegalStateException("not yet implemented");
}
@SuppressWarnings("unchecked")
@Override
public Publish post(ChangeResource parent) throws RestApiException {
return publish;
}
@Singleton
public static class Publish implements RestModifyView<ChangeResource, Publish.Input> {
public static class Input {
}
private final ChangeEditUtil editUtil;
@Inject
Publish(ChangeEditUtil editUtil) {
this.editUtil = editUtil;
}
@Override
public Response<?> apply(ChangeResource rsrc, Publish.Input in)
throws AuthException, ResourceConflictException, NoSuchChangeException,
IOException, InvalidChangeOperationException, OrmException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (!edit.isPresent()) {
throw new ResourceConflictException(String.format(
"no edit exists for change %s",
rsrc.getChange().getChangeId()));
}
editUtil.publish(edit.get());
return Response.none();
}
}
}

View File

@ -0,0 +1,117 @@
// Copyright (C) 2014 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.common.base.Optional;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
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.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
public class RebaseChangeEdit implements
ChildCollection<ChangeResource, ChangeEditResource>,
AcceptsPost<ChangeResource> {
private final Rebase rebase;
@Inject
RebaseChangeEdit(Rebase rebase) {
this.rebase = rebase;
}
@Override
public DynamicMap<RestView<ChangeEditResource>> views() {
throw new IllegalStateException("not yet implemented");
}
@Override
public RestView<ChangeResource> list() {
throw new IllegalStateException("not yet implemented");
}
@Override
public ChangeEditResource parse(ChangeResource parent, IdString id)
throws ResourceNotFoundException, Exception {
throw new IllegalStateException("not yet implemented");
}
@SuppressWarnings("unchecked")
@Override
public Rebase post(ChangeResource parent) throws RestApiException {
return rebase;
}
@Singleton
public static class Rebase implements RestModifyView<ChangeResource, Publish.Input> {
public static class Input {
}
private final ChangeEditModifier editModifier;
private final ChangeEditUtil editUtil;
private final Provider<ReviewDb> db;
@Inject
Rebase(ChangeEditModifier editModifier,
ChangeEditUtil editUtil,
Provider<ReviewDb> db) {
this.editModifier = editModifier;
this.editUtil = editUtil;
this.db = db;
}
@Override
public Response<?> apply(ChangeResource rsrc, Publish.Input in)
throws AuthException, ResourceConflictException, IOException,
InvalidChangeOperationException, OrmException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (!edit.isPresent()) {
throw new ResourceConflictException(String.format(
"no edit exists for change %s",
rsrc.getChange().getChangeId()));
}
PatchSet current = db.get().patchSets().get(
rsrc.getChange().currentPatchSetId());
if (current.getId().equals(edit.get().getBasePatchSet().getId())) {
throw new ResourceConflictException(String.format(
"edit for change %s is already on latest patch set: %s",
rsrc.getChange().getChangeId(),
current.getId()));
}
editModifier.rebaseEdit(edit.get(), current);
return Response.none();
}
}
}