Merge "Add rebase button to the change screen"
This commit is contained in:
@@ -29,6 +29,7 @@ public class ChangeDetail {
|
||||
protected boolean allowsAnonymous;
|
||||
protected boolean canAbandon;
|
||||
protected boolean canPublish;
|
||||
protected boolean canRebase;
|
||||
protected boolean canRestore;
|
||||
protected boolean canRevert;
|
||||
protected boolean canDeleteDraft;
|
||||
@@ -80,6 +81,14 @@ public class ChangeDetail {
|
||||
canPublish = a;
|
||||
}
|
||||
|
||||
public boolean canRebase() {
|
||||
return canRebase;
|
||||
}
|
||||
|
||||
public void setCanRebase(final boolean a) {
|
||||
canRebase = a;
|
||||
}
|
||||
|
||||
public boolean canRestore() {
|
||||
return canRestore;
|
||||
}
|
||||
|
@@ -44,4 +44,7 @@ public interface ChangeManageService extends RemoteJsonService {
|
||||
|
||||
@SignInRequired
|
||||
void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback<VoidResult> callback);
|
||||
|
||||
@SignInRequired
|
||||
void rebaseChange(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
||||
}
|
||||
|
@@ -111,6 +111,8 @@ public interface ChangeConstants extends Constants {
|
||||
String patchSetInfoParents();
|
||||
String initialCommit();
|
||||
|
||||
String buttonRebaseChange();
|
||||
|
||||
String buttonRevertChangeBegin();
|
||||
String buttonRevertChangeSend();
|
||||
String headingRevertMessage();
|
||||
|
@@ -96,6 +96,8 @@ oldVersionHistory = Old Version History:
|
||||
baseDiffItem = Base
|
||||
autoMerge = Auto Merge
|
||||
|
||||
buttonRebaseChange = Rebase Change
|
||||
|
||||
buttonRevertChangeBegin = Revert Change
|
||||
buttonRevertChangeSend = Revert Change
|
||||
headingRevertMessage = Revert Commit Message:
|
||||
|
@@ -19,6 +19,7 @@ import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.FocusWidget;
|
||||
|
||||
public class ChangeDetailCache extends ListenableValue<ChangeDetail> {
|
||||
public static class GerritCallback extends
|
||||
@@ -29,6 +30,27 @@ public class ChangeDetailCache extends ListenableValue<ChangeDetail> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* GerritCallback which will re-enable a FocusWidget
|
||||
* {@link com.google.gwt.user.client.ui.FocusWidget} if we are returning
|
||||
* with a failed result.
|
||||
*
|
||||
* It is up to the caller to handle the original disabling of the Widget.
|
||||
*/
|
||||
public static class GerritWidgetCallback extends GerritCallback {
|
||||
private FocusWidget widget;
|
||||
|
||||
public GerritWidgetCallback(FocusWidget widget) {
|
||||
this.widget = widget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
widget.setEnabled(true);
|
||||
super.onFailure(caught);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IgnoreErrorCallback implements AsyncCallback<ChangeDetail> {
|
||||
@Override
|
||||
public void onSuccess(ChangeDetail detail) {
|
||||
|
@@ -321,6 +321,8 @@ public class ChangeScreen extends Screen
|
||||
}
|
||||
|
||||
dependenciesPanel.setOpen(depsOpen);
|
||||
|
||||
dependenciesPanel.getHeader().clear();
|
||||
if (outdated > 0) {
|
||||
dependenciesPanel.getHeader().add(new InlineLabel(
|
||||
Util.M.outdatedHeader(outdated)));
|
||||
|
@@ -229,7 +229,10 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
if (! c.isLatest()) {
|
||||
s += " [OUTDATED]";
|
||||
table.getRowFormatter().addStyleName(row, Gerrit.RESOURCES.css().outdated());
|
||||
} else {
|
||||
table.getRowFormatter().removeStyleName(row, Gerrit.RESOURCES.css().outdated());
|
||||
}
|
||||
|
||||
table.setWidget(row, C_SUBJECT, new TableChangeLink(s, c));
|
||||
table.setWidget(row, C_OWNER, link(c.getOwner()));
|
||||
table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c
|
||||
|
@@ -551,6 +551,19 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel
|
||||
});
|
||||
actionsPanel.add(b);
|
||||
}
|
||||
|
||||
if (changeDetail.canRebase()) {
|
||||
final Button b = new Button(Util.C.buttonRebaseChange());
|
||||
b.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(final ClickEvent event) {
|
||||
b.setEnabled(false);
|
||||
Util.MANAGE_SVC.rebaseChange(patchSet.getId(),
|
||||
new ChangeDetailCache.GerritWidgetCallback(b));
|
||||
}
|
||||
});
|
||||
actionsPanel.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateDiffAllActions(final PatchSetDetail detail) {
|
||||
|
@@ -129,6 +129,8 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
|
||||
|
||||
detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet());
|
||||
|
||||
detail.setCanRebase(detail.getChange().getStatus().isOpen() && control.canRebase());
|
||||
|
||||
detail.setCanEdit(control.getRefControl().canWrite());
|
||||
|
||||
if (detail.getChange().getStatus().isOpen()) {
|
||||
|
@@ -24,6 +24,7 @@ import com.google.inject.Inject;
|
||||
class ChangeManageServiceImpl implements ChangeManageService {
|
||||
private final SubmitAction.Factory submitAction;
|
||||
private final AbandonChangeHandler.Factory abandonChangeHandlerFactory;
|
||||
private final RebaseChange.Factory rebaseChangeFactory;
|
||||
private final RestoreChangeHandler.Factory restoreChangeHandlerFactory;
|
||||
private final RevertChange.Factory revertChangeFactory;
|
||||
private final PublishAction.Factory publishAction;
|
||||
@@ -32,12 +33,14 @@ class ChangeManageServiceImpl implements ChangeManageService {
|
||||
@Inject
|
||||
ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
|
||||
final AbandonChangeHandler.Factory abandonChangeHandlerFactory,
|
||||
final RebaseChange.Factory rebaseChangeFactory,
|
||||
final RestoreChangeHandler.Factory restoreChangeHandlerFactory,
|
||||
final RevertChange.Factory revertChangeFactory,
|
||||
final PublishAction.Factory publishAction,
|
||||
final DeleteDraftChange.Factory deleteDraftChangeFactory) {
|
||||
this.submitAction = patchSetAction;
|
||||
this.abandonChangeHandlerFactory = abandonChangeHandlerFactory;
|
||||
this.rebaseChangeFactory = rebaseChangeFactory;
|
||||
this.restoreChangeHandlerFactory = restoreChangeHandlerFactory;
|
||||
this.revertChangeFactory = revertChangeFactory;
|
||||
this.publishAction = publishAction;
|
||||
@@ -54,6 +57,11 @@ class ChangeManageServiceImpl implements ChangeManageService {
|
||||
abandonChangeHandlerFactory.create(patchSetId, message).to(callback);
|
||||
}
|
||||
|
||||
public void rebaseChange(final PatchSet.Id patchSetId,
|
||||
final AsyncCallback<ChangeDetail> callback) {
|
||||
rebaseChangeFactory.create(patchSetId).to(callback);
|
||||
}
|
||||
|
||||
public void revertChange(final PatchSet.Id patchSetId, final String message,
|
||||
final AsyncCallback<ChangeDetail> callback) {
|
||||
revertChangeFactory.create(patchSetId, message).to(callback);
|
||||
|
@@ -31,6 +31,7 @@ public class ChangeModule extends RpcServletModule {
|
||||
factory(AbandonChangeHandler.Factory.class);
|
||||
factory(RestoreChangeHandler.Factory.class);
|
||||
factory(RevertChange.Factory.class);
|
||||
factory(RebaseChange.Factory.class);
|
||||
factory(ChangeDetailFactory.Factory.class);
|
||||
factory(IncludedInDetailFactory.Factory.class);
|
||||
factory(PatchSetDetailFactory.Factory.class);
|
||||
|
@@ -0,0 +1,110 @@
|
||||
// Copyright (C) 2012 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd.rpc.changedetail;
|
||||
|
||||
import com.google.gerrit.common.ChangeHookRunner;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.mail.EmailException;
|
||||
import com.google.gerrit.server.mail.RebasedPatchSetSender;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
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.assistedinject.Assisted;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RebaseChange extends Handler<ChangeDetail> {
|
||||
interface Factory {
|
||||
RebaseChange create(PatchSet.Id patchSetId);
|
||||
}
|
||||
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final ReviewDb db;
|
||||
private final IdentifiedUser currentUser;
|
||||
private final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory;
|
||||
|
||||
private final ChangeDetailFactory.Factory changeDetailFactory;
|
||||
private final ReplicationQueue replication;
|
||||
|
||||
private final PatchSet.Id patchSetId;
|
||||
|
||||
private final ChangeHookRunner hooks;
|
||||
|
||||
private final GitRepositoryManager gitManager;
|
||||
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||
|
||||
private final PersonIdent myIdent;
|
||||
|
||||
private final ApprovalTypes approvalTypes;
|
||||
|
||||
@Inject
|
||||
RebaseChange(final ChangeControl.Factory changeControlFactory,
|
||||
final ReviewDb db, final IdentifiedUser currentUser,
|
||||
final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
|
||||
final ChangeDetailFactory.Factory changeDetailFactory,
|
||||
@Assisted final PatchSet.Id patchSetId, final ChangeHookRunner hooks,
|
||||
final GitRepositoryManager gitManager,
|
||||
final PatchSetInfoFactory patchSetInfoFactory,
|
||||
final ReplicationQueue replication,
|
||||
@GerritPersonIdent final PersonIdent myIdent,
|
||||
final ApprovalTypes approvalTypes) {
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.db = db;
|
||||
this.currentUser = currentUser;
|
||||
this.rebasedPatchSetSenderFactory = rebasedPatchSetSenderFactory;
|
||||
this.changeDetailFactory = changeDetailFactory;
|
||||
|
||||
this.patchSetId = patchSetId;
|
||||
this.hooks = hooks;
|
||||
this.gitManager = gitManager;
|
||||
|
||||
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||
this.replication = replication;
|
||||
this.myIdent = myIdent;
|
||||
|
||||
this.approvalTypes = approvalTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeDetail call() throws NoSuchChangeException, OrmException,
|
||||
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
|
||||
MissingObjectException, IncorrectObjectTypeException, IOException,
|
||||
InvalidChangeOperationException {
|
||||
|
||||
ChangeUtil.rebaseChange(patchSetId, currentUser, db,
|
||||
rebasedPatchSetSenderFactory, hooks, gitManager, patchSetInfoFactory,
|
||||
replication, myIdent, changeControlFactory, approvalTypes);
|
||||
|
||||
return changeDetailFactory.create(patchSetId.getParentKey()).call();
|
||||
}
|
||||
}
|
@@ -432,6 +432,10 @@ public final class Change {
|
||||
lastUpdatedOn = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public int getNumberOfPatchSets() {
|
||||
return nbrPatchSets;
|
||||
}
|
||||
|
||||
public String getSortKey() {
|
||||
return sortKey;
|
||||
}
|
||||
|
@@ -14,11 +14,17 @@
|
||||
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.common.data.ApprovalType;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.reviewdb.client.ApprovalCategory;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ApprovalsUtil {
|
||||
@@ -33,4 +39,36 @@ public class ApprovalsUtil {
|
||||
}
|
||||
db.patchSetApprovals().update(approvals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the PatchSetApprovals to the last PatchSet on the change while
|
||||
* keeping the vetos.
|
||||
*
|
||||
* @param db The review database
|
||||
* @param change Change to update
|
||||
* @param approvalTypes The approval types
|
||||
* @throws OrmException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void copyVetosToLatestPatchSet(final ReviewDb db, Change change,
|
||||
ApprovalTypes approvalTypes) throws OrmException, IOException {
|
||||
PatchSet.Id source;
|
||||
if (change.getNumberOfPatchSets() > 1) {
|
||||
source = new PatchSet.Id(change.getId(), change.getNumberOfPatchSets() - 1);
|
||||
} else {
|
||||
throw new IOException("Previous patch set could not be found");
|
||||
}
|
||||
|
||||
PatchSet.Id dest = change.currPatchSetId();
|
||||
for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(source)) {
|
||||
// ApprovalCategory.SUBMIT is still in db but not relevant in git-store
|
||||
if (!ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
|
||||
final ApprovalType type = approvalTypes.byId(a.getCategoryId());
|
||||
if (type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
|
||||
db.patchSetApprovals().insert(
|
||||
Collections.singleton(new PatchSetApproval(dest, a)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,10 +14,16 @@
|
||||
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.common.ChangeHookRunner;
|
||||
import com.google.gerrit.common.ChangeHooks;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Change.Status;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.reviewdb.client.TrackingId;
|
||||
@@ -28,12 +34,16 @@ import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.mail.EmailException;
|
||||
import com.google.gerrit.server.mail.RebasedPatchSetSender;
|
||||
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
||||
import com.google.gerrit.server.mail.ReplyToChangeSender;
|
||||
import com.google.gerrit.server.mail.RevertedSender;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.AtomicUpdate;
|
||||
import com.google.gwtorm.server.OrmConcurrencyException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
@@ -44,8 +54,11 @@ import org.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.merge.MergeStrategy;
|
||||
import org.eclipse.jgit.merge.ThreeWayMerger;
|
||||
import org.eclipse.jgit.revwalk.FooterLine;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
@@ -53,6 +66,8 @@ import org.eclipse.jgit.util.Base64;
|
||||
import org.eclipse.jgit.util.NB;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -159,6 +174,252 @@ public class ChangeUtil {
|
||||
opFactory.create(change.getDest()).verifyMergeability(change);
|
||||
}
|
||||
|
||||
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
|
||||
throws OrmException {
|
||||
final int cnt = src.getParentCount();
|
||||
List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
|
||||
for (int p = 0; p < cnt; p++) {
|
||||
PatchSetAncestor a =
|
||||
new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
|
||||
a.setAncestorRevision(new RevId(src.getParent(p).getId().getName()));
|
||||
toInsert.add(a);
|
||||
}
|
||||
db.patchSetAncestors().insert(toInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebases a commit
|
||||
*
|
||||
* @param git Repository to find commits in
|
||||
* @param original The commit to rebase
|
||||
* @param base Base to rebase against
|
||||
* @return CommitBuilder the newly rebased commit
|
||||
* @throws IOException Merged failed
|
||||
*/
|
||||
public static CommitBuilder rebaseCommit(Repository git, RevCommit original,
|
||||
RevCommit base, PersonIdent committerIdent) throws IOException {
|
||||
|
||||
if (original.getParentCount() == 0) {
|
||||
throw new IOException(
|
||||
"Commits with no parents cannot be rebased (is this the initial commit?).");
|
||||
}
|
||||
|
||||
if (original.getParentCount() > 1) {
|
||||
throw new IOException(
|
||||
"Patch sets with multiple parents cannot be rebased (merge commits)."
|
||||
+ " Parents: " + Arrays.toString(original.getParents()));
|
||||
}
|
||||
|
||||
final RevCommit parentCommit = original.getParent(0);
|
||||
|
||||
if (base.equals(parentCommit)) {
|
||||
throw new IOException("Change is already up to date.");
|
||||
}
|
||||
|
||||
final ThreeWayMerger merger = MergeStrategy.RESOLVE.newMerger(git, true);
|
||||
merger.setBase(parentCommit);
|
||||
merger.merge(original, base);
|
||||
|
||||
if (merger.getResultTreeId() == null) {
|
||||
throw new IOException(
|
||||
"The rebase failed since conflicts occured during the merge.");
|
||||
}
|
||||
|
||||
final CommitBuilder rebasedCommitBuilder = new CommitBuilder();
|
||||
|
||||
rebasedCommitBuilder.setTreeId(merger.getResultTreeId());
|
||||
rebasedCommitBuilder.setParentId(base);
|
||||
rebasedCommitBuilder.setAuthor(original.getAuthorIdent());
|
||||
rebasedCommitBuilder.setMessage(original.getFullMessage());
|
||||
rebasedCommitBuilder.setCommitter(committerIdent);
|
||||
|
||||
return rebasedCommitBuilder;
|
||||
}
|
||||
|
||||
public static void rebaseChange(final PatchSet.Id patchSetId,
|
||||
final IdentifiedUser user, final ReviewDb db,
|
||||
RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
|
||||
final ChangeHookRunner hooks, GitRepositoryManager gitManager,
|
||||
final PatchSetInfoFactory patchSetInfoFactory,
|
||||
final ReplicationQueue replication, PersonIdent myIdent,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
final ApprovalTypes approvalTypes) throws NoSuchChangeException,
|
||||
EmailException, OrmException, MissingObjectException,
|
||||
IncorrectObjectTypeException, IOException,
|
||||
PatchSetInfoNotAvailableException, InvalidChangeOperationException {
|
||||
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final ChangeControl changeControl =
|
||||
changeControlFactory.validateFor(changeId);
|
||||
|
||||
if (!changeControl.canRebase()) {
|
||||
throw new InvalidChangeOperationException(
|
||||
"Cannot rebase: New patch sets are not allowed to be added to change: "
|
||||
+ changeId.toString());
|
||||
}
|
||||
|
||||
Change change = changeControl.getChange();
|
||||
final Repository git = gitManager.openRepository(change.getProject());
|
||||
try {
|
||||
final RevWalk revWalk = new RevWalk(git);
|
||||
try {
|
||||
final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
|
||||
RevCommit branchTipCommit = null;
|
||||
|
||||
List<PatchSetAncestor> patchSetAncestors =
|
||||
db.patchSetAncestors().ancestorsOf(patchSetId).toList();
|
||||
if (patchSetAncestors.size() > 1) {
|
||||
throw new IOException(
|
||||
"The patch set you are trying to rebase is dependent on several other patch sets: "
|
||||
+ patchSetAncestors.toString());
|
||||
}
|
||||
if (patchSetAncestors.size() == 1) {
|
||||
List<PatchSet> depPatchSetList = db.patchSets()
|
||||
.byRevision(patchSetAncestors.get(0).getAncestorRevision())
|
||||
.toList();
|
||||
if (!depPatchSetList.isEmpty()) {
|
||||
PatchSet depPatchSet = depPatchSetList.get(0);
|
||||
|
||||
Change.Id depChangeId = depPatchSet.getId().getParentKey();
|
||||
Change depChange = db.changes().get(depChangeId);
|
||||
|
||||
if (depChange.getStatus() == Status.ABANDONED) {
|
||||
throw new IOException("Cannot rebase against an abandoned change: "
|
||||
+ depChange.getKey().toString());
|
||||
}
|
||||
if (depChange.getStatus().isOpen()) {
|
||||
PatchSet latestDepPatchSet =
|
||||
db.patchSets().get(depChange.currentPatchSetId());
|
||||
if (!depPatchSet.getId().equals(depChange.currentPatchSetId())) {
|
||||
branchTipCommit =
|
||||
revWalk.parseCommit(ObjectId
|
||||
.fromString(latestDepPatchSet.getRevision().get()));
|
||||
} else {
|
||||
throw new IOException(
|
||||
"Change is already based on the latest patch set of the dependent change.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (branchTipCommit == null) {
|
||||
// We are dependent on a merged PatchSet or have no PatchSet
|
||||
// dependencies at all.
|
||||
Ref destRef = git.getRef(change.getDest().get());
|
||||
if (destRef == null) {
|
||||
throw new IOException(
|
||||
"The destination branch does not exist: "
|
||||
+ change.getDest().get());
|
||||
}
|
||||
branchTipCommit = revWalk.parseCommit(destRef.getObjectId());
|
||||
}
|
||||
|
||||
final RevCommit originalCommit =
|
||||
revWalk.parseCommit(ObjectId.fromString(originalPatchSet
|
||||
.getRevision().get()));
|
||||
|
||||
CommitBuilder rebasedCommitBuilder =
|
||||
rebaseCommit(git, originalCommit, branchTipCommit, myIdent);
|
||||
|
||||
final ObjectInserter oi = git.newObjectInserter();
|
||||
final ObjectId rebasedCommitId;
|
||||
try {
|
||||
rebasedCommitId = oi.insert(rebasedCommitBuilder);
|
||||
oi.flush();
|
||||
} finally {
|
||||
oi.release();
|
||||
}
|
||||
|
||||
Change updatedChange =
|
||||
db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus().isOpen()) {
|
||||
change.nextPatchSetId();
|
||||
return change;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (updatedChange == null) {
|
||||
throw new InvalidChangeOperationException("Change is closed: "
|
||||
+ change.toString());
|
||||
} else {
|
||||
change = updatedChange;
|
||||
}
|
||||
|
||||
final PatchSet rebasedPatchSet = new PatchSet(change.currPatchSetId());
|
||||
rebasedPatchSet.setCreatedOn(change.getCreatedOn());
|
||||
rebasedPatchSet.setUploader(user.getAccountId());
|
||||
rebasedPatchSet.setRevision(new RevId(rebasedCommitId.getName()));
|
||||
|
||||
insertAncestors(db, rebasedPatchSet.getId(),
|
||||
revWalk.parseCommit(rebasedCommitId));
|
||||
|
||||
db.patchSets().insert(Collections.singleton(rebasedPatchSet));
|
||||
final PatchSetInfo info =
|
||||
patchSetInfoFactory.get(db, rebasedPatchSet.getId());
|
||||
|
||||
change =
|
||||
db.changes().atomicUpdate(change.getId(),
|
||||
new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
change.setCurrentPatchSet(info);
|
||||
ChangeUtil.updated(change);
|
||||
return change;
|
||||
}
|
||||
});
|
||||
|
||||
final RefUpdate ru = git.updateRef(rebasedPatchSet.getRefName());
|
||||
ru.setNewObjectId(rebasedCommitId);
|
||||
ru.disableRefLog();
|
||||
if (ru.update(revWalk) != RefUpdate.Result.NEW) {
|
||||
throw new IOException("Failed to create ref "
|
||||
+ rebasedPatchSet.getRefName() + " in " + git.getDirectory()
|
||||
+ ": " + ru.getResult());
|
||||
}
|
||||
|
||||
replication.scheduleUpdate(change.getProject(), ru.getName());
|
||||
|
||||
ApprovalsUtil.copyVetosToLatestPatchSet(db, change, approvalTypes);
|
||||
|
||||
final ChangeMessage cmsg =
|
||||
new ChangeMessage(new ChangeMessage.Key(changeId,
|
||||
ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
|
||||
cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
|
||||
db.changeMessages().insert(Collections.singleton(cmsg));
|
||||
|
||||
final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
|
||||
final Set<Account.Id> oldCC = new HashSet<Account.Id>();
|
||||
|
||||
for (PatchSetApproval a : db.patchSetApprovals().byChange(change.getId())) {
|
||||
if (a.getValue() != 0) {
|
||||
oldReviewers.add(a.getAccountId());
|
||||
} else {
|
||||
oldCC.add(a.getAccountId());
|
||||
}
|
||||
}
|
||||
|
||||
final ReplacePatchSetSender cm =
|
||||
rebasedPatchSetSenderFactory.create(change);
|
||||
cm.setFrom(user.getAccountId());
|
||||
cm.setPatchSet(rebasedPatchSet);
|
||||
cm.addReviewers(oldReviewers);
|
||||
cm.addExtraCC(oldCC);
|
||||
cm.send();
|
||||
|
||||
hooks.doPatchsetCreatedHook(change, rebasedPatchSet, db);
|
||||
} finally {
|
||||
revWalk.release();
|
||||
}
|
||||
} finally {
|
||||
git.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static Change.Id revert(final PatchSet.Id patchSetId,
|
||||
final IdentifiedUser user, final String message, final ReviewDb db,
|
||||
final RevertedSender.Factory revertedSenderFactory,
|
||||
|
@@ -44,6 +44,7 @@ import com.google.gerrit.server.mail.CommentSender;
|
||||
import com.google.gerrit.server.mail.CreateChangeSender;
|
||||
import com.google.gerrit.server.mail.MergeFailSender;
|
||||
import com.google.gerrit.server.mail.MergedSender;
|
||||
import com.google.gerrit.server.mail.RebasedPatchSetSender;
|
||||
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
||||
import com.google.gerrit.server.mail.RestoredSender;
|
||||
import com.google.gerrit.server.mail.RevertedSender;
|
||||
@@ -95,6 +96,7 @@ public class GerritRequestModule extends FactoryModule {
|
||||
factory(PublishComments.Factory.class);
|
||||
factory(PublishDraft.Factory.class);
|
||||
factory(ReplacePatchSetSender.Factory.class);
|
||||
factory(RebasedPatchSetSender.Factory.class);
|
||||
factory(AbandonedSender.Factory.class);
|
||||
factory(RemoveReviewer.Factory.class);
|
||||
factory(RestoreChange.Factory.class);
|
||||
|
@@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2012 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
/** Send notice to reviewers that a change has been rebased. */
|
||||
public class RebasedPatchSetSender extends ReplacePatchSetSender {
|
||||
public static interface Factory {
|
||||
RebasedPatchSetSender create(Change change);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public RebasedPatchSetSender(EmailArguments ea,
|
||||
@AnonymousCowardName String anonymousCowardName, SshInfo si,
|
||||
@Assisted Change c) {
|
||||
super(ea, anonymousCowardName, si, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void formatChange() throws EmailException {
|
||||
appendText(velocifyFile("RebasedPatchSet.vm"));
|
||||
}
|
||||
}
|
@@ -199,6 +199,11 @@ public class ChangeControl {
|
||||
return isOwner() && isVisible(db);
|
||||
}
|
||||
|
||||
/** Can this user rebase this change? */
|
||||
public boolean canRebase() {
|
||||
return canAddPatchSet();
|
||||
}
|
||||
|
||||
/** Can this user restore this change? */
|
||||
public boolean canRestore() {
|
||||
return canAbandon(); // Anyone who can abandon the change can restore it back
|
||||
|
@@ -0,0 +1,54 @@
|
||||
## Copyright (C) 2012 The Android Open Source Project
|
||||
##
|
||||
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||
## you may not use this file except in compliance with the License.
|
||||
## You may obtain a copy of the License at
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
##
|
||||
##
|
||||
## Template Type:
|
||||
## -------------
|
||||
## This is a velocity mail template, see: http://velocity.apache.org and the
|
||||
## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
|
||||
##
|
||||
## Template File Names and extensions:
|
||||
## ----------------------------------
|
||||
## Gerrit will use templates ending in ".vm" but will ignore templates ending
|
||||
## in ".vm.example". If a .vm template does not exist, the default internal
|
||||
## gerrit template which is the same as the .vm.example will be used. If you
|
||||
## want to override the default template, copy the .vm.example file to a .vm
|
||||
## file and edit it appropriately.
|
||||
##
|
||||
## This Template:
|
||||
## --------------
|
||||
## The RebasedPatchSet.vm template will determine the contents of the email
|
||||
## related to a user rebasing a patchset for a change through the Gerrit UI.
|
||||
## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
|
||||
##
|
||||
#if($email.reviewerNames)
|
||||
Hello $email.joinStrings($email.reviewerNames, ', '),
|
||||
|
||||
I'd like you to reexamine a rebased change.#if($email.changeUrl) Please visit
|
||||
|
||||
$email.changeUrl
|
||||
|
||||
to look at the new rebased patch set (#$patchSet.patchSetId).
|
||||
#end
|
||||
#else
|
||||
$fromName has created a new patch set by issuing a rebase in Gerrit (#$patchSet.patchSetId).
|
||||
#end
|
||||
|
||||
Change subject: $change.subject
|
||||
......................................................................
|
||||
|
||||
$email.changeDetail
|
||||
#if($email.sshHost)
|
||||
git pull ssh://$email.sshHost/$projectName $patchSet.refName
|
||||
#end
|
Reference in New Issue
Block a user