diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index 09c7e115e0..359d379780 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt @@ -710,6 +710,95 @@ the error message is contained in the response body. change is new ---- +[[rebase-change]] +Rebase Change +~~~~~~~~~~~~~ +[verse] +'POST /changes/link:#change-id[\{change-id\}]/rebase' + +Rebases a change. + +.Request +---- + POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/rebase HTTP/1.0 +---- + +As response a link:#change-info[ChangeInfo] entity is returned that +describes the rebased change. Information about the current patch set +is included. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "kind": "gerritcodereview#change", + "id": "myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2", + "project": "myProject", + "branch": "master", + "change_id": "I3ea943139cb62e86071996f2480e58bf3eeb9dd2", + "subject": "Implement Feature X", + "status": "NEW", + "created": "2013-02-01 09:59:32.126000000", + "updated": "2013-02-21 11:16:36.775000000", + "mergeable": false, + "_sortkey": "0024cf9a000012bf", + "_number": 4799, + "owner": { + "name": "John Doe" + }, + "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822", + "revisions": { + "27cc4558b5a3d3387dd11ee2df7a117e7e581822": { + "_number": 2, + "fetch": { + "http": { + "url": "http://gerrit:8080/myProject", + "ref": "refs/changes/99/4799/2" + } + }, + "commit": { + "parents": [ + { + "commit": "b4003890dadd406d80222bf1ad8aca09a4876b70", + "subject": "Implement Feature A" + } + ], + "author": { + "name": "John Doe", + "email": "john.doe@example.com", + "date": "2013-05-07 15:21:27.000000000", + "tz": 120 + }, + "committer": { + "name": "Gerrit Code Review", + "email": "gerrit-server@example.com", + "date": "2013-05-07 15:35:43.000000000", + "tz": 120 + }, + "subject": "Implement Feature X", + "message": "Implement Feature X\n\nAdded feature X." + } + } + } +---- + +If the change cannot be rebased, e.g. due to conflicts, the response is +"`409 Conflict`" and the error message is contained in the response +body. + +.Response +---- + HTTP/1.1 409 Conflict + Content-Disposition: attachment + Content-Type: text/plain;charset=UTF-8 + + The change could not be rebased due to a path conflict during merge. +---- + [[revert-change]] Revert Change ~~~~~~~~~~~~~ @@ -1218,6 +1307,95 @@ describes the applied labels. } ---- +[[rebase-revision]] +Rebase Revision +~~~~~~~~~~~~~~~ +[verse] +'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/rebase' + +Rebases a revision. + +.Request +---- + POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/rebase HTTP/1.0 +---- + +As response a link:#change-info[ChangeInfo] entity is returned that +describes the rebased change. Information about the current patch set +is included. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "kind": "gerritcodereview#change", + "id": "myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2", + "project": "myProject", + "branch": "master", + "change_id": "I3ea943139cb62e86071996f2480e58bf3eeb9dd2", + "subject": "Implement Feature X", + "status": "NEW", + "created": "2013-02-01 09:59:32.126000000", + "updated": "2013-02-21 11:16:36.775000000", + "mergeable": false, + "_sortkey": "0024cf9a000012bf", + "_number": 4799, + "owner": { + "name": "John Doe" + }, + "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822", + "revisions": { + "27cc4558b5a3d3387dd11ee2df7a117e7e581822": { + "_number": 2, + "fetch": { + "http": { + "url": "http://gerrit:8080/myProject", + "ref": "refs/changes/99/4799/2" + } + }, + "commit": { + "parents": [ + { + "commit": "b4003890dadd406d80222bf1ad8aca09a4876b70", + "subject": "Implement Feature A" + } + ], + "author": { + "name": "John Doe", + "email": "john.doe@example.com", + "date": "2013-05-07 15:21:27.000000000", + "tz": 120 + }, + "committer": { + "name": "Gerrit Code Review", + "email": "gerrit-server@example.com", + "date": "2013-05-07 15:35:43.000000000", + "tz": 120 + }, + "subject": "Implement Feature X", + "message": "Implement Feature X\n\nAdded feature X." + } + } + } +---- + +If the revision cannot be rebased, e.g. due to conflicts, the response is +"`409 Conflict`" and the error message is contained in the response +body. + +.Response +---- + HTTP/1.1 409 Conflict + Content-Disposition: attachment + Content-Type: text/plain;charset=UTF-8 + + The change could not be rebased due to a path conflict during merge. +---- + [[submit-revision]] Submit Revision ~~~~~~~~~~~~~~~ diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java index b9acfa9c6c..8558fe8e7d 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java @@ -62,7 +62,7 @@ class RebaseChangeHandler extends Handler { EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException, MissingObjectException, IncorrectObjectTypeException, IOException, InvalidChangeOperationException, NoSuchProjectException { - rebaseChange.rebase(patchSetId, currentUser.getAccountId()); + rebaseChange.rebase(patchSetId, currentUser); return changeDetailFactory.create(patchSetId.getParentKey()).call(); } } 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 690faf3147..04ce0d1f2f 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 @@ -53,6 +53,7 @@ public class Module extends RestApiModule { post(CHANGE_KIND, "restore").to(Restore.class); post(CHANGE_KIND, "revert").to(Revert.class); post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class); + post(CHANGE_KIND, "rebase").to(Rebase.CurrentRevision.class); post(CHANGE_KIND, "reviewers").to(PostReviewers.class); child(CHANGE_KIND, "reviewers").to(Reviewers.class); @@ -63,6 +64,7 @@ public class Module extends RestApiModule { get(REVISION_KIND, "review").to(GetReview.class); post(REVISION_KIND, "review").to(PostReview.class); post(REVISION_KIND, "submit").to(Submit.class); + post(REVISION_KIND, "rebase").to(Rebase.class); get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class); post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class); post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java new file mode 100644 index 0000000000..ec6b122fc2 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java @@ -0,0 +1,105 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.gerrit.common.changes.ListChangesOption; +import com.google.gerrit.common.errors.EmailException; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestModifyView; +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.change.ChangeJson.ChangeInfo; +import com.google.gerrit.server.change.Rebase.Input; +import com.google.gerrit.server.changedetail.RebaseChange; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.project.InvalidChangeOperationException; +import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import java.io.IOException; + +public class Rebase implements RestModifyView { + public static class Input { + } + + private final RebaseChange rebaseChange; + private final ChangeJson json; + + @Inject + public Rebase(RebaseChange rebaseChange, ChangeJson json) { + this.rebaseChange = rebaseChange; + this.json = json; + } + + @Override + public ChangeInfo apply(RevisionResource rsrc, Input input) + throws AuthException, ResourceNotFoundException, + ResourceConflictException, EmailException, OrmException { + ChangeControl control = rsrc.getControl(); + Change change = rsrc.getChange(); + if (!control.canRebase()) { + throw new AuthException("rebase not permitted"); + } else if (!change.getStatus().isOpen()) { + throw new ResourceConflictException("change is " + + change.getStatus().name().toLowerCase()); + } + + try { + rebaseChange.rebase(rsrc.getPatchSet().getId(), rsrc.getUser()); + } catch (InvalidChangeOperationException e) { + throw new ResourceConflictException(e.getMessage()); + } catch (IOException e) { + throw new ResourceConflictException(e.getMessage()); + } catch (NoSuchChangeException e) { + throw new ResourceNotFoundException(change.getId().toString()); + } + + json.addOption(ListChangesOption.CURRENT_REVISION) + .addOption(ListChangesOption.CURRENT_COMMIT); + return json.format(change.getId()); + } + + public static class CurrentRevision implements + RestModifyView { + private final Provider dbProvider; + private final Rebase rebase; + + @Inject + CurrentRevision(Provider dbProvider, Rebase rebase) { + this.dbProvider = dbProvider; + this.rebase = rebase; + } + + @Override + public ChangeInfo apply(ChangeResource rsrc, Input input) + throws AuthException, ResourceNotFoundException, + ResourceConflictException, EmailException, OrmException { + PatchSet ps = + dbProvider.get().patchSets() + .get(rsrc.getChange().currentPatchSetId()); + if (ps == null) { + throw new ResourceConflictException("current revision is missing"); + } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) { + throw new AuthException("current revision not accessible"); + } + return rebase.apply(new RevisionResource(rsrc, ps), input); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java index cdd9e0f2e7..f9a5c07de6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java @@ -48,6 +48,10 @@ public class RevisionResource implements RestResource { } Account.Id getAccountId() { - return ((IdentifiedUser) getControl().getCurrentUser()).getAccountId(); + return getUser().getAccountId(); + } + + IdentifiedUser getUser() { + return (IdentifiedUser) getControl().getCurrentUser(); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java index c9f7dce7c6..cb4c2c379c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java @@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MergeUtil; @@ -64,7 +65,7 @@ import java.util.List; import java.util.Set; public class RebaseChange { - private final ChangeControl.Factory changeControlFactory; + private final ChangeControl.GenericFactory changeControlFactory; private final PatchSetInfoFactory patchSetInfoFactory; private final ReviewDb db; private final GitRepositoryManager gitManager; @@ -76,7 +77,7 @@ public class RebaseChange { private final ProjectCache projectCache; @Inject - RebaseChange(final ChangeControl.Factory changeControlFactory, + RebaseChange(final ChangeControl.GenericFactory changeControlFactory, final PatchSetInfoFactory patchSetInfoFactory, final ReviewDb db, @GerritPersonIdent final PersonIdent myIdent, final GitRepositoryManager gitManager, @@ -123,12 +124,12 @@ public class RebaseChange { * @throws IOException thrown if rebase is not possible or not needed * @throws InvalidChangeOperationException thrown if rebase is not allowed */ - public void rebase(final PatchSet.Id patchSetId, final Account.Id uploader) + public void rebase(final PatchSet.Id patchSetId, final IdentifiedUser uploader) throws NoSuchChangeException, EmailException, OrmException, IOException, InvalidChangeOperationException { final Change.Id changeId = patchSetId.getParentKey(); final ChangeControl changeControl = - changeControlFactory.validateFor(changeId); + changeControlFactory.validateFor(changeId, uploader); if (!changeControl.canRebase()) { throw new InvalidChangeOperationException( "Cannot rebase: New patch sets are not allowed to be added to change: " @@ -152,7 +153,7 @@ public class RebaseChange { rw.parseCommit(ObjectId.fromString(baseRev)); final PatchSet newPatchSet = - rebase(git, rw, inserter, patchSetId, change, uploader, baseCommit, + rebase(git, rw, inserter, patchSetId, change, uploader.getAccountId(), baseCommit, mergeUtilFactory.create( changeControl.getProjectControl().getProjectState(), true)); @@ -167,7 +168,7 @@ public class RebaseChange { } final ReplacePatchSetSender cm = rebasedPatchSetSenderFactory.create(change); - cm.setFrom(uploader); + cm.setFrom(uploader.getAccountId()); cm.setPatchSet(newPatchSet); cm.addReviewers(oldReviewers); cm.addExtraCC(oldCC); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java index 6397b96714..f8b99759d0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java @@ -56,10 +56,12 @@ public class ChangeControl { public static class GenericFactory { private final ProjectControl.GenericFactory projectControl; + private final Provider db; @Inject - GenericFactory(ProjectControl.GenericFactory p) { + GenericFactory(ProjectControl.GenericFactory p, Provider d) { projectControl = p; + db = d; } public ChangeControl controlFor(Change change, CurrentUser user) @@ -71,6 +73,29 @@ public class ChangeControl { throw new NoSuchChangeException(change.getId(), e); } } + + public ChangeControl controlFor(Change.Id id, CurrentUser user) + throws NoSuchChangeException { + final Change change; + try { + change = db.get().changes().get(id); + if (change == null) { + throw new NoSuchChangeException(id); + } + } catch (OrmException e) { + throw new NoSuchChangeException(id, e); + } + return controlFor(change, user); + } + + public ChangeControl validateFor(Change.Id id, CurrentUser user) + throws NoSuchChangeException, OrmException { + ChangeControl c = controlFor(id, user); + if (!c.isVisible(db.get())) { + throw new NoSuchChangeException(c.getChange().getId()); + } + return c; + } } public static class Factory {