Merge "Add PatchLineComments to RebuildNotedb"
diff --git a/.buckconfig b/.buckconfig
index 43bfa5e..211e878 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -12,6 +12,7 @@
release = //:release
safari = //:safari
withdocs = //:withdocs
+ codeserver = //lib/gwt:codeserver
[buildfile]
includes = //tools/default.defs
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 384bb74..d8db555 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -67,6 +67,53 @@
* Close the Debug Configurations dialog and save the changes when prompted.
+=== Running Super Dev Mode
+
+Install dependencies:
+
+----
+ buck build codeserver
+----
+
+Due to codeserver not correctly identifying user agent problem, that was
+already fixed upstream but not released yet, the used user agent must be
+explicitly set in `GerritGwtUI.gwt.xml` for SDM to work:
+
+[source,xml]
+----
+ <set-property name="user.agent" value="geko1_8" />
+----
+
+or
+
+[source,xml]
+----
+ <set-property name="user.agent" value="safari" />
+----
+
+* Select in Eclipse Run -> Debug Configurations `gerrit_gwt_codeserver.launch`
+* Select in Eclipse Run -> Debug Configurations `gerrit_daemon.launch`
+* Only once: add bookmarks for `Dev Mode On/Off` from codeserver URL:
+`http://localhost:9876/` to your bookmark bar
+* Make sure to activate source maps feature in your browser
+* Load Gerrit page `http://localhost:8080`
+* Open developer tools, source tab
+* Click on `Dev Mode On` bookmark
+* Select `gerrit_ui` module to compile (the `Compile` button can also be used
+as a bookmarklet).
+* Navigate on the left to: sourcemaps/gerrit_ui folder (`Ctrl+O` key shortcut
+can be used)
+* Select a file, for example com.google.gerrit.client.change.ChangeScreen2
+and put a breakpoint
+* Navigate in application in change screen and confirm hitting the breakpoint
+* Select `Dev Mode Off` when the debugging session is finished
+
+After changing the client side code:
+* click `Dev Mode On` then `Compile` to reflect your changes in debug session
+* Hitting `F5` in the browser will just load the last compile output, without
+recompiling
+
+
[[known-problems]]
== Known problems
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 7a01b99..61221a0 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1139,6 +1139,227 @@
HTTP/1.1 204 No Content
----
+[[edit-endpoints]]
+== Change Edit Endpoints
+
+These endpoints are considered to be unstable and can be changed in
+backwards incompatible way any time without notice.
+
+[[get-edit-detail]]
+=== Get Change Edit Details
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Retrieves a change edit details.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+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
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "commit":{
+ "parents":[
+ {
+ "commit":"1eee2c9d8f352483781e772f35dc586a69ff5646",
+ }
+ ],
+ "author":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "committer":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "subject":"Use an EventBus to manage star icons",
+ "message":"Use an EventBus to manage star icons\n\nImage widgets that need to ..."
+ },
+ }
+----
+
+[[put-edit-file]]
+=== Change file content in Change Edit
+--
+'PUT /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
+--
+
+Put content of a file to a change edit.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+When change edit doesn't exist for this change yet it is created. When file
+content isn't provided, it is wiped out for that file. As response
+"`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[post-edit]]
+=== Restore file content in Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Creates empty change edit or restores file content in change edit. The
+request body needs to include a link:#change-edit-input[ChangeEditInput]
+entity when a file within change edit should be restored.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "path": "foo",
+ "restore": true
+ }
+----
+
+When change edit doesn't exist for this change yet it is created. When path
+and restore flag are provided in request body, this file is restored. As
+response "`204 No Content`" is returned.
+
+.Response
+----
+ 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
@@ -3589,6 +3810,31 @@
|`url` |The link URL.
|======================
+[[edit-info]]
+=== EditInfo
+The `EditInfo` entity contains information about a change edit.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|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]]
+=== RestorePathInput
+The `RestorePathInput` entity contains information for restoring a
+path within change edit.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`restore_path`|optional|Path to file to restore.
+|===========================
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 45befd0..bf4918c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -31,6 +31,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
public class RestSession extends HttpSession {
@@ -91,12 +92,14 @@
}
- public static RawInput newRawInput(final String content) throws IOException {
- Preconditions.checkNotNull(content);
- Preconditions.checkArgument(!content.isEmpty());
- return new RawInput() {
- byte bytes[] = content.getBytes("UTF-8");
+ public static RawInput newRawInput(String content) throws IOException {
+ return newRawInput(content.getBytes(StandardCharsets.UTF_8));
+ }
+ public static RawInput newRawInput(final byte[] bytes) throws IOException {
+ Preconditions.checkNotNull(bytes);
+ Preconditions.checkArgument(bytes.length > 0);
+ return new RawInput() {
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index bbb91b2..6a58933 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -26,7 +26,6 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
-import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -72,7 +71,7 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
}
@@ -81,10 +80,10 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.restore();
}
@@ -93,15 +92,15 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.review(ReviewInput.approve());
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.submit();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revert();
}
@@ -111,12 +110,14 @@
IOException, RestApiException {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.rebase();
}
- private static Set<Account.Id> getReviewers(ChangeInfo ci) {
+ private Set<Account.Id> getReviewers(String changeId)
+ throws RestApiException {
+ ChangeInfo ci = gApi.changes().id(changeId).get();
Set<Account.Id> result = Sets.newHashSet();
for (LabelInfo li : ci.labels.values()) {
for (ApprovalInfo ai : li.all) {
@@ -132,30 +133,34 @@
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.addReviewer(in);
- assertEquals(ImmutableSet.of(user.id), getReviewers(cApi.get()));
+ gApi.changes()
+ .id(r.getChangeId())
+ .addReviewer(in);
+ assertEquals(ImmutableSet.of(user.id), getReviewers(r.getChangeId()));
}
@Test
public void addReviewerToClosedChange() throws GitAPIException,
IOException, RestApiException {
PushOneCommit.Result r = createChange();
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.review(ReviewInput.approve());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.submit();
- assertEquals(ImmutableSet.of(admin.getId()), getReviewers(cApi.get()));
+ assertEquals(ImmutableSet.of(admin.getId()), getReviewers(r.getChangeId()));
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.addReviewer(in);
assertEquals(ImmutableSet.of(admin.getId(), user.id),
- getReviewers(cApi.get()));
+ getReviewers(r.getChangeId()));
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index e18ac17..54e8799 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -27,7 +27,6 @@
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -264,12 +263,6 @@
.isEmpty());
}
- protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
- return gApi.changes()
- .id(r.getChangeId())
- .current();
- }
-
private void merge(PushOneCommit.Result r) throws Exception {
revision(r).review(ReviewInput.approve());
revision(r).submit();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
index 610631f..be6fcdc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
@@ -3,5 +3,8 @@
acceptance_tests(
srcs = ['ChangeEditIT.java'],
labels = ['edit'],
- deps = ['//lib/joda:joda-time'],
+ deps = [
+ '//lib/commons:codec',
+ '//lib/joda:joda-time',
+ ],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 45dcdc3..b4631190 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -17,6 +17,9 @@
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -25,17 +28,21 @@
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.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.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;
@@ -45,6 +52,8 @@
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;
@@ -56,10 +65,11 @@
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;
-@NoHttpd
public class ChangeEditIT extends AbstractDaemonTest {
private final static String FILE_NAME = "foo";
@@ -161,6 +171,45 @@
}
@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,
modifier.createEdit(
@@ -191,6 +240,37 @@
}
@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,
modifier.createEdit(
@@ -212,6 +292,52 @@
}
@Test
+ public void retrieveEdit() throws Exception {
+ RestResponse r = session.get(urlEdit());
+ assertEquals(SC_NO_CONTENT, r.getStatusCode());
+ 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));
+ edit = editUtil.byChange(change);
+ EditInfo info = toEditInfo(false);
+ assertEquals(edit.get().getRevision().get(), info.commit.commit);
+ assertEquals(1, info.commit.parents.size());
+
+ edit = editUtil.byChange(change);
+ editUtil.delete(edit.get());
+
+ r = session.get(urlEdit());
+ 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,
modifier.createEdit(
@@ -232,6 +358,41 @@
}
@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,
modifier.createEdit(
@@ -286,6 +447,18 @@
}
@Test
+ public void restoreDeletedFileInPatchSetRest() throws Exception {
+ Post.Input in = new Post.Input();
+ in.restorePath = FILE_NAME;
+ assertEquals(SC_NO_CONTENT, session.post(urlEdit2(),
+ in).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change2);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
public void amendExistingFile() throws Exception {
assertEquals(RefUpdate.Result.NEW,
modifier.createEdit(
@@ -313,6 +486,101 @@
}
@Test
+ public void createAndChangeEditInOneRequestRest() 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);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ in.content = RestSession.newRawInput(CONTENT_NEW2);
+ assertEquals(SC_NO_CONTENT, session.putRaw(urlEditFile(),
+ in.content).getStatusCode());
+ edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_NEW2,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void changeEditRest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ 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);
+ assertArrayEquals(CONTENT_NEW,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void emptyPutRequest() throws Exception {
+ assertEquals(RefUpdate.Result.NEW,
+ modifier.createEdit(
+ change,
+ ps));
+ assertEquals(SC_NO_CONTENT, session.put(urlEditFile()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals("".getBytes(),
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ edit.get().getRevision().get(), FILE_NAME)));
+ }
+
+ @Test
+ public void createEmptyEditRest() throws Exception {
+ assertEquals(SC_NO_CONTENT, session.post(urlEdit()).getStatusCode());
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertArrayEquals(CONTENT_OLD,
+ toBytes(fileUtil.getContent(edit.get().getChange().getProject(),
+ 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,
modifier.createEdit(
@@ -410,4 +678,45 @@
content.writeTo(os);
return os.toByteArray();
}
+
+ private String urlEdit() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit";
+ }
+
+ private String urlEdit2() {
+ return "/changes/"
+ + change2.getChangeId()
+ + "/edit/";
+ }
+
+ private String urlEditFile() {
+ return urlEdit()
+ + "/"
+ + FILE_NAME;
+ }
+
+ 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);
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index f636f11..983198a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -25,7 +25,6 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.common.InheritableBoolean;
import com.google.gerrit.extensions.common.ProjectInfo;
@@ -74,8 +73,7 @@
@Test
public void testCreateProjectApi() throws RestApiException, IOException {
final String newProjectName = "newProject";
- ProjectApi projectApi = gApi.projects().name(newProjectName).create();
- ProjectInfo p = projectApi.get();
+ ProjectInfo p = gApi.projects().name(newProjectName).create().get();
assertEquals(newProjectName, p.name);
ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
assertNotNull(projectState);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index 749b12a..71a93d3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -18,7 +18,27 @@
import com.google.gerrit.extensions.restapi.RestApiException;
public interface Accounts {
+ /**
+ * Look up an account by ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the account. Methods that
+ * mutate the account do not necessarily re-read the account. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code AccountApi} instances.
+ *
+ * @param id any identifier supported by the REST API, including numeric ID,
+ * email, or username.
+ * @return API for accessing the account.
+ * @throws RestApiException if an error occurred.
+ */
AccountApi id(String id) throws RestApiException;
+
+ /**
+ * Look up the account of the current in-scope user.
+ *
+ * @see #id(String)
+ */
AccountApi self() throws RestApiException;
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index c3ade5f..4d509e9 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -24,8 +24,32 @@
public interface ChangeApi {
String id();
+ /**
+ * Look up the current revision for the change.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the revision. Methods that
+ * mutate the revision do not necessarily re-read the revision. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code RevisionApi} instances.
+ *
+ * @return API for accessing the revision.
+ * @throws RestApiException if an error occurred.
+ */
RevisionApi current() throws RestApiException;
+
+ /**
+ * Look up a revision of a change by number.
+ *
+ * @see #current()
+ */
RevisionApi revision(int id) throws RestApiException;
+
+ /**
+ * Look up a revision of a change by commit SHA-1.
+ *
+ * @see #current()
+ */
RevisionApi revision(String id) throws RestApiException;
void abandon() throws RestApiException;
@@ -34,7 +58,18 @@
void restore() throws RestApiException;
void restore(RestoreInput in) throws RestApiException;
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert() throws RestApiException;
+
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert(RevertInput in) throws RestApiException;
String topic() throws RestApiException;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
index 201a0bd..4084946 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -24,10 +24,40 @@
import java.util.List;
public interface Changes {
+ /**
+ * Look up a change by numeric ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the change. Methods that
+ * mutate the change do not necessarily re-read the change. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code ChangeApi} instances.
+ *
+ * @param id change number.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
ChangeApi id(int id) throws RestApiException;
- ChangeApi id(String triplet) throws RestApiException;
+
+ /**
+ * Look up a change by string ID.
+ *
+ * @see #id(int)
+ * @param id any identifier supported by the REST API, including change
+ * number, Change-Id, or project~branch~Change-Id triplet.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
+ ChangeApi id(String id) throws RestApiException;
+
+ /**
+ * Look up a change by project, branch, and change ID.
+ *
+ * @see #id(int)
+ */
ChangeApi id(String project, String branch, String id)
throws RestApiException;
+
ChangeApi create(ChangeInfo in) throws RestApiException;
QueryRequest query();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index d013c5d..07a48a1 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -22,6 +22,19 @@
ProjectApi create() throws RestApiException;
ProjectApi create(ProjectInput in) throws RestApiException;
ProjectInfo get();
+
+ /**
+ * Look up a branch by refname.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the branch. Methods that
+ * mutate the branch do not necessarily re-read the branch. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code BranchApi} instances.
+ *
+ * @param ref branch name, with or without "refs/heads/" prefix.
+ * @return API for accessing the branch.
+ */
BranchApi branch(String ref);
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index 9c0cfd8..736d375 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -21,6 +21,19 @@
import java.util.List;
public interface Projects {
+ /**
+ * Look up a project by name.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the project. Methods that
+ * mutate the project do not necessarily re-read the project. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code ProjectApi} instances.
+ *
+ * @param name project name.
+ * @return API for accessing the project.
+ * @throws RestApiException if an error occurred.
+ */
ProjectApi name(String name) throws RestApiException;
ListRequest list();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
new file mode 100644
index 0000000..4946cb9
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
@@ -0,0 +1,22 @@
+// 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.extensions.common;
+
+import java.util.Map;
+
+public class EditInfo {
+ public CommitInfo commit;
+ public Map<String, FileInfo> files;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 4ca7ca2..c79c45e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -204,6 +204,17 @@
}
}
+ public static class EditInfo 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() {
+ }
+ }
+
public static class RevisionInfo extends JavaScriptObject {
public final native int _number() /*-{ return this._number; }-*/;
public final native String name() /*-{ return this.name; }-*/;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
index 606dad8..c57f5a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
@@ -23,16 +23,27 @@
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.TypeLiteral;
+/**
+ * Represents change edit resource, that is actualy two kinds of resources:
+ * <ul>
+ * <li>the change edit itself</li>
+ * <li>a path within the edit</li>
+ * </ul>
+ * distinguished by whether path is null or not.
+ */
public class ChangeEditResource implements RestResource {
public static final TypeLiteral<RestView<ChangeEditResource>> CHANGE_EDIT_KIND =
new TypeLiteral<RestView<ChangeEditResource>>() {};
private final ChangeResource change;
private final ChangeEdit edit;
+ private final String path;
- public ChangeEditResource(ChangeResource change, ChangeEdit edit) {
+ public ChangeEditResource(ChangeResource change, ChangeEdit edit,
+ String path) {
this.change = change;
this.edit = edit;
+ this.path = path;
}
// TODO(davido): Make this cacheable.
@@ -57,6 +68,10 @@
return edit;
}
+ public String getPath() {
+ return path;
+ }
+
Account.Id getAccountId() {
return getUser().getAccountId();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index 55834da..7e2844f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -14,21 +14,74 @@
package com.google.gerrit.server.change;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.io.ByteStreams;
+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;
import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RawInput;
+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.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;
import com.google.inject.Inject;
+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
-class ChangeEdits implements
- ChildCollection<ChangeResource, ChangeEditResource> {
+public class ChangeEdits implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsCreate<ChangeResource>,
+ AcceptsPost<ChangeResource>,
+ AcceptsDelete<ChangeResource> {
private final DynamicMap<RestView<ChangeEditResource>> views;
+ private final Create.Factory createFactory;
+ private final DeleteEdit.Factory deleteEditFactory;
+ private final Provider<Detail> detail;
+ private final ChangeEditUtil editUtil;
+ private final Post post;
@Inject
- ChangeEdits(DynamicMap<RestView<ChangeEditResource>> views) {
+ ChangeEdits(DynamicMap<RestView<ChangeEditResource>> views,
+ Create.Factory createFactory,
+ Provider<Detail> detail,
+ ChangeEditUtil editUtil,
+ Post post,
+ DeleteEdit.Factory deleteEditFactory) {
this.views = views;
+ this.createFactory = createFactory;
+ this.detail = detail;
+ this.editUtil = editUtil;
+ this.post = post;
+ this.deleteEditFactory = deleteEditFactory;
}
@Override
@@ -38,11 +91,345 @@
@Override
public RestView<ChangeResource> list() {
- throw new IllegalStateException("not yet implemented");
+ return detail.get();
}
@Override
- public ChangeEditResource parse(ChangeResource change, IdString id) {
- throw new IllegalStateException("not yet implemented");
+ public ChangeEditResource parse(ChangeResource rsrc, IdString id)
+ throws ResourceNotFoundException, AuthException, IOException,
+ InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new ChangeEditResource(rsrc, edit.get(), id.get());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Create create(ChangeResource parent, IdString id)
+ throws RestApiException {
+ return createFactory.create(parent.getChange(), id.get());
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Post post(ChangeResource parent) throws RestApiException {
+ return post;
+ }
+
+ /**
+ * Create handler that is activated when collection element is accessed
+ * but doesn't exist, e. g. PUT request with a path was called but
+ * 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> {
+
+ interface Factory {
+ Create create(Change change, String path);
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+ private final Put putEdit;
+ private final Change change;
+ private final String path;
+
+ @Inject
+ Create(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier,
+ Put putEdit,
+ @Assisted Change change,
+ @Assisted @Nullable String path) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ this.putEdit = putEdit;
+ this.change = change;
+ this.path = path;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Put.Input input)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ if (edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "edit already exists for the change %s",
+ resource.getChange().getChangeId()));
+ }
+ edit = createEdit();
+ if (!Strings.isNullOrEmpty(path)) {
+ putEdit.apply(new ChangeEditResource(resource, edit.get(), path),
+ input);
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit() throws AuthException,
+ IOException, ResourceConflictException, OrmException,
+ InvalidChangeOperationException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ 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(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, InvalidChangeOperationException,
+ ResourceNotFoundException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ 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);
+ }
+ }
+
+ /**
+ * Post to edit collection resource. Two different operations are
+ * supported:
+ * <ul>
+ * <li>Create non existing change edit</li>
+ * <li>Restore path in existing change edit</li>
+ * </ul>
+ * The combination of two operations in one request is supported.
+ */
+ @Singleton
+ public static class Post implements
+ RestModifyView<ChangeResource, Post.Input> {
+ public static class Input {
+ public String restorePath;
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Post(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Post.Input input)
+ throws AuthException, InvalidChangeOperationException, IOException,
+ ResourceConflictException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(resource.getChange());
+ if (!edit.isPresent()) {
+ edit = createEdit(resource.getChange());
+ }
+
+ if (input != null && !Strings.isNullOrEmpty(input.restorePath)) {
+ editModifier.restoreFile(edit.get(), input.restorePath);
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit(Change change)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ /**
+ * Put handler that is activated when PUT request is called on
+ * collection element.
+ */
+ @Singleton
+ public static class Put implements
+ RestModifyView<ChangeEditResource, Put.Input> {
+ public static class Input {
+ @DefaultInput
+ public RawInput content;
+ }
+
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Put(ChangeEditModifier editModifier) {
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc, Input input)
+ throws AuthException, ResourceConflictException, IOException {
+ try {
+ editModifier.modifyFile(rsrc.getChangeEdit(), rsrc.getPath(),
+ ByteStreams.toByteArray(input.content.getInputStream()));
+ } catch(InvalidChangeOperationException | IOException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ 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();
+ }
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
index 6bb7236..6ae87a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -21,6 +21,7 @@
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 @@
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));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 74659b8..3035ce1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -141,7 +141,7 @@
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));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 4b8e33b..a916430 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND;
import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
import static com.google.gerrit.server.change.CommentResource.COMMENT_KIND;
import static com.google.gerrit.server.change.DraftResource.DRAFT_KIND;
import static com.google.gerrit.server.change.FileResource.FILE_KIND;
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
-import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -101,6 +101,13 @@
get(FILE_KIND, "content").to(GetContent.class);
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
protected void configure() {
@@ -109,6 +116,8 @@
factory(EmailReviewComments.Factory.class);
factory(ChangeInserter.Factory.class);
factory(PatchSetInserter.Factory.class);
+ factory(ChangeEdits.Create.Factory.class);
+ factory(ChangeEdits.DeleteEdit.Factory.class);
}
});
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
new file mode 100644
index 0000000..b511c20
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -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();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
new file mode 100644
index 0000000..8fccfb8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
@@ -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();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
new file mode 100644
index 0000000..be88004
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -0,0 +1,51 @@
+// 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.edit;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.server.CommonConverters;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+
+@Singleton
+public class ChangeEditJson {
+ public EditInfo toEditInfo(ChangeEdit edit) throws IOException {
+ EditInfo out = new EditInfo();
+ out.commit = fillCommit(edit.getEditCommit());
+ return out;
+ }
+
+ private static CommitInfo fillCommit(RevCommit editCommit) throws IOException {
+ CommitInfo commit = new CommitInfo();
+ commit.commit = editCommit.toObjectId().getName();
+ commit.parents = Lists.newArrayListWithCapacity(1);
+ commit.author = CommonConverters.toGitPerson(editCommit.getAuthorIdent());
+ commit.committer = CommonConverters.toGitPerson(
+ editCommit.getCommitterIdent());
+ commit.subject = editCommit.getShortMessage();
+ commit.message = editCommit.getFullMessage();
+
+ CommitInfo i = new CommitInfo();
+ i.commit = editCommit.getParent(0).toObjectId().getName();
+ commit.parents.add(i);
+
+ return commit;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 3c6fd71..2ae09f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
@@ -525,7 +526,8 @@
try {
Repository repo = openRepository();
try {
- return isMergedIntoVisibleRef(repo, db, rw, commit, repo.getAllRefs());
+ return isMergedIntoVisibleRef(repo, db, rw, commit,
+ repo.getAllRefs().values());
} finally {
repo.close();
}
@@ -539,10 +541,14 @@
}
boolean isMergedIntoVisibleRef(Repository repo, ReviewDb db, RevWalk rw,
- RevCommit commit, Map<String, Ref> unfilteredRefs) throws IOException {
+ RevCommit commit, Collection<Ref> unfilteredRefs) throws IOException {
VisibleRefFilter filter =
new VisibleRefFilter(tagCache, changeCache, repo, this, db, true);
- Map<String, Ref> refs = filter.filter(unfilteredRefs, true);
+ Map<String, Ref> m = Maps.newHashMapWithExpectedSize(unfilteredRefs.size());
+ for (Ref r : unfilteredRefs) {
+ m.put(r.getName(), r);
+ }
+ Map<String, Ref> refs = filter.filter(m, true);
return !refs.isEmpty()
&& IncludedInResolver.includedInOne(repo, rw, commit, refs.values());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index f13b411..9848faa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -324,9 +324,9 @@
try {
Repository repo = projectControl.openRepository();
try {
- Map<String, Ref> refs =
- repo.getRefDatabase().getRefs(Constants.R_HEADS);
- refs.putAll(repo.getRefDatabase().getRefs(Constants.R_TAGS));
+ List<Ref> refs = new ArrayList<>(
+ repo.getRefDatabase().getRefs(Constants.R_HEADS).values());
+ refs.addAll(repo.getRefDatabase().getRefs(Constants.R_TAGS).values());
return projectControl.isMergedIntoVisibleRef(
repo, db, rw, commit, refs);
} finally {
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 8d2b718..3bf514d 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -18,12 +18,33 @@
deps = [
':javax-validation',
':javax-validation_src',
+ ':json',
],
attach_source = False,
exclude = ['org/eclipse/jetty/*'],
)
maven_jar(
+ name = 'codeserver',
+ id = 'com.google.gwt:gwt-codeserver:' + VERSION,
+ sha1 = '940edc715cc31b1957e18f617f75a068f251346a',
+ license = 'Apache2.0',
+ deps = [
+ ':dev',
+ ':legacy-jetty-servlet-aggregate',
+ ],
+ attach_source = False,
+)
+
+maven_jar(
+ name = 'json',
+ id = 'org.json:json:20140107',
+ sha1 = 'd1ffca6e2482b002702c6a576166fd685e3370e3',
+ license = 'DO_NOT_DISTRIBUTE',
+ attach_source = False,
+)
+
+maven_jar(
name = 'javax-validation',
id = 'javax.validation:validation-api:1.0.0.GA',
bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
@@ -52,3 +73,21 @@
visibility = [],
)
+maven_jar(
+ name = 'legacy-jetty-servlet-aggregate',
+ id = 'org.eclipse.jetty.aggregate:jetty-servlet:8.1.12.v20130726',
+ sha1 = '4d0f0cb6e5a54de01be46717a7ab48d0b45dcadd',
+ license = 'Apache2.0',
+ attach_source = False,
+ deps = [':legacy-jetty-servlets'],
+ visibility = [],
+)
+
+maven_jar(
+ name = 'legacy-jetty-servlets',
+ id = 'org.eclipse.jetty:jetty-servlets:8.1.12.v20130726',
+ sha1 = '4ebc6894b899fee0c3597697d11f255ce9214bbf',
+ license = 'Apache2.0',
+ attach_source = False,
+ visibility = [],
+)
diff --git a/tools/eclipse/gerrit_gwt_codeserver.launch b/tools/eclipse/gerrit_gwt_codeserver.launch
new file mode 100644
index 0000000..e7f5206
--- /dev/null
+++ b/tools/eclipse/gerrit_gwt_codeserver.launch
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" javaProject="gerrit" path="1" type="4"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/legacy-jetty-servlet-aggregate/jetty-servlet-8.1.12.v20130726.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/codeserver/gwt-codeserver-2.6.1.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gerrit/buck-out/gen/lib/gwt/legacy-jetty-servlets/jetty-servlets-8.1.12.v20130726.jar" path="3" type="2"/> "/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="gerrit"/> </runtimeClasspathEntry> "/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.codeserver.CodeServer"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -src ${resource_loc:/gerrit} -workDir ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui com.google.gerrit.GerritGwtUI"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024M -XX:MaxPermSize=256M"/>
+</launchConfiguration>