ChangeScreen2: Recompute changeMerge.test on display
Add a new /mergeable REST API to compute the mergeable flag
when displaying a change in ChangeScreen2. This allows the
UI to pick up the most recent information rather than relying
on the potentially stale value observed in the ChangeInfo.
Since the merge test requires the submit type these can be
combined together and reported as one unit when /mergeable
is requested by the client.
Change-Id: Ibc44c322fb4848a45f7f9ed3722ac6a6a6e73fea
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 8bcdf29..b288348 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1533,6 +1533,33 @@
RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
----
+[[get-mergeable]]
+Get Mergeable
+~~~~~~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/mergeable'
+
+Gets the method the server will use to submit (merge) the change and
+an indicator if the change is currently mergeable.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/mergeable HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ submit_type: "MERGE_IF_NECESSARY",
+ mergeable: true,
+ }
+----
+
[[get-submit-type]]
Get Submit Type
~~~~~~~~~~~~~~~
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index dcd29a7..dbb5ce2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -56,13 +56,14 @@
private String project;
private String subject;
private String message;
+ private boolean canSubmit;
Actions() {
initWidget(uiBinder.createAndBindUi(this));
getElement().setId("change_actions");
}
- void display(ChangeInfo info, String revision, boolean canSubmit) {
+ void display(ChangeInfo info, String revision) {
this.revision = revision;
boolean hasUser = Gerrit.isSignedIn();
@@ -74,7 +75,7 @@
message = commit.message();
initChangeActions(info, hasUser);
- initRevisionActions(info, revInfo, canSubmit, hasUser);
+ initRevisionActions(info, revInfo, hasUser);
}
private void initChangeActions(ChangeInfo info, boolean hasUser) {
@@ -95,10 +96,7 @@
}
private void initRevisionActions(ChangeInfo info, RevisionInfo revInfo,
- boolean canSubmit, boolean hasUser) {
- boolean hasConflict = Gerrit.getConfig().testChangeMerge()
- && !info.mergeable();
-
+ boolean hasUser) {
NativeMap<ActionInfo> actions = revInfo.has_actions()
? revInfo.actions()
: NativeMap.<ActionInfo> create();
@@ -106,9 +104,7 @@
cherrypick.setVisible(hasUser && actions.containsKey("cherrypick"));
rebase.setVisible(hasUser && actions.containsKey("rebase"));
- submit.setVisible(hasUser && !hasConflict
- && canSubmit
- && actions.containsKey("submit"));
+ canSubmit = hasUser && actions.containsKey("submit");
if (hasUser) {
for (String id : filterNonCore(actions)) {
@@ -129,6 +125,10 @@
return ids;
}
+ void setSubmitEnabled(boolean ok) {
+ submit.setVisible(ok && canSubmit);
+ }
+
boolean isSubmitEnabled() {
return submit.isVisible() && submit.isEnabled();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
index b596359..699fbcf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
@@ -86,7 +86,7 @@
<div><ui:msg>Restore</ui:msg></div>
</g:Button>
- <g:Button ui:field='submit' styleName='{style.submit}'>
+ <g:Button ui:field='submit' styleName='{style.submit}' visible='false'>
<div><ui:msg>Submit</ui:msg></div>
</g:Button>
</g:FlowPanel>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index 2e16be9..a6308cd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -22,6 +22,7 @@
import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ChangeInfo.MergeableInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.StarredChanges;
@@ -48,8 +49,6 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -290,10 +289,6 @@
}
}));
group.done();
-
- if (info.status().isOpen() && rev.name().equals(info.current_revision())) {
- loadSubmitAction(rev);
- }
}
private void loadDiff(final RevisionInfo rev, CallbackGroup group) {
@@ -343,33 +338,50 @@
}));
}
- private void loadSubmitAction(final RevisionInfo rev) {
- // Submit action is less important than other data.
- // Defer so browser can start other requests first.
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- ChangeApi.revision(changeId.get(), rev.name())
- .view("submit_type")
- .get(new AsyncCallback<NativeString>() {
- @Override
- public void onSuccess(NativeString result) {
- String action = result.asString();
- try {
- SubmitType type = Project.SubmitType.valueOf(action);
- submitActionText.setInnerText(
- com.google.gerrit.client.admin.Util.toLongString(type));
- } catch (IllegalArgumentException e) {
- submitActionText.setInnerText(action);
- }
+ private void loadMergeable(final boolean canSubmit) {
+ if (Gerrit.getConfig().testChangeMerge()) {
+ ChangeApi.revision(changeId.get(), revision)
+ .view("mergeable")
+ .get(new AsyncCallback<MergeableInfo>() {
+ @Override
+ public void onSuccess(MergeableInfo result) {
+ if (canSubmit) {
+ actions.setSubmitEnabled(result.mergeable());
+ statusText.setInnerText(result.mergeable()
+ ? Util.C.readyToSubmit()
+ : Util.C.mergeConflict());
}
+ setVisible(notMergeable, !result.mergeable());
+ renderSubmitType(result.submit_type());
+ }
- @Override
- public void onFailure(Throwable caught) {
- }
- });
- }
- });
+ @Override
+ public void onFailure(Throwable caught) {
+ loadSubmitType(canSubmit);
+ }
+ });
+ } else {
+ loadSubmitType(canSubmit);
+ }
+ }
+
+ private void loadSubmitType(final boolean canSubmit) {
+ if (canSubmit) {
+ actions.setSubmitEnabled(true);
+ statusText.setInnerText(Util.C.readyToSubmit());
+ }
+ ChangeApi.revision(changeId.get(), revision)
+ .view("submit_type")
+ .get(new AsyncCallback<NativeString>() {
+ @Override
+ public void onSuccess(NativeString result) {
+ renderSubmitType(result.asString());
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
}
private RevisionInfo resolveRevisionToDisplay(ChangeInfo info) {
@@ -390,14 +402,16 @@
private void renderChangeInfo(ChangeInfo info) {
statusText.setInnerText(Util.toLongString(info.status()));
- boolean canSubmit = labels.set(info);
+ boolean current = info.status().isOpen()
+ && revision.equals(info.current_revision());
+ boolean canSubmit = labels.set(info, current);
renderOwner(info);
renderReviewers(info);
renderActionTextDate(info);
renderRevisions(info);
renderHistory(info);
- actions.display(info, revision, canSubmit);
+ actions.display(info, revision);
star.setValue(info.starred());
permalink.setHref(ChangeLink.permalink(changeId));
@@ -411,9 +425,6 @@
commit.set(commentLinkProcessor, info, revision);
quickApprove.set(info, revision);
- boolean hasConflict = Gerrit.getConfig().testChangeMerge() && !info.mergeable();
- setVisible(notMergeable, info.status().isOpen() && hasConflict);
-
if (Gerrit.isSignedIn()) {
replyAction = new ReplyAction(info, revision, style, reply);
if (topic.canEdit()) {
@@ -425,13 +436,10 @@
});
}
}
- reply.setVisible(replyAction != null);
-
- if (canSubmit && !hasConflict && actions.isSubmitEnabled()) {
- statusText.setInnerText(Util.C.readyToSubmit());
- } else if (canSubmit && hasConflict) {
- statusText.setInnerText(Util.C.mergeConflict());
+ if (current) {
+ loadMergeable(canSubmit);
}
+ reply.setVisible(replyAction != null);
StringBuilder sb = new StringBuilder();
sb.append(Util.M.changeScreenTitleId(info.id_abbreviated()));
@@ -498,6 +506,16 @@
: Gerrit.getConfig().getAnonymousCowardName());
}
+ private void renderSubmitType(String action) {
+ try {
+ SubmitType type = Project.SubmitType.valueOf(action);
+ submitActionText.setInnerText(
+ com.google.gerrit.client.admin.Util.toLongString(type));
+ } catch (IllegalArgumentException e) {
+ submitActionText.setInnerText(action);
+ }
+ }
+
private void renderActionTextDate(ChangeInfo info) {
String action;
if (info.created().equals(info.updated())) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index e7d7554..8c9fa10 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -243,7 +243,10 @@
<th><ui:msg>Strategy</ui:msg></th>
<td>
<span ui:field='submitActionText'/>
- <div ui:field='notMergeable' class='{style.notMergeable}'>
+ <div ui:field='notMergeable'
+ class='{style.notMergeable}'
+ style='display: none'
+ aria-hidden='true'>
<ui:msg>Cannot Merge</ui:msg>
</div>
</td>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index 4df1efa..f3ce7fc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -47,7 +47,7 @@
this.statusText = statusText;
}
- boolean set(ChangeInfo info) {
+ boolean set(ChangeInfo info, boolean current) {
List<String> names = new ArrayList<String>(info.labels());
Collections.sort(names);
@@ -67,12 +67,16 @@
if (canSubmit && info.status() == Change.Status.NEW) {
switch (label.status()) {
case NEED:
- statusText.setInnerText("Needs " + name);
+ if (current) {
+ statusText.setInnerText("Needs " + name);
+ }
canSubmit = false;
break;
case REJECT:
case IMPOSSIBLE:
- statusText.setInnerText("Not " + name);
+ if (current) {
+ statusText.setInnerText("Not " + name);
+ }
canSubmit = false;
break;
default:
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 cf73d79..a2d5d64 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
@@ -260,4 +260,12 @@
protected MessageInfo() {
}
}
+
+ public static class MergeableInfo extends JavaScriptObject {
+ public final native String submit_type() /*-{ return this.submit_type }-*/;
+ public final native boolean mergeable() /*-{ return this.mergeable }-*/;
+
+ protected MergeableInfo() {
+ }
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 568971d..972345b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -18,6 +18,9 @@
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -27,15 +30,16 @@
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.Mergeable;
+import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -48,6 +52,8 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -61,6 +67,9 @@
/** Creates a {@link ChangeDetail} from a {@link Change}. */
public class ChangeDetailFactory extends Handler<ChangeDetail> {
+ private static final Logger log = LoggerFactory
+ .getLogger(ChangeDetailFactory.class);
+
public interface Factory {
ChangeDetailFactory create(Change.Id id);
}
@@ -78,7 +87,7 @@
private ChangeControl control;
private Map<PatchSet.Id, PatchSet> patchsetsById;
- private final MergeOp.Factory opFactory;
+ private final Mergeable mergeable;
private boolean testMerge;
private List<PatchSetAncestor> currentPatchSetAncestors;
@@ -92,7 +101,7 @@
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final AnonymousUser anonymousUser,
- final MergeOp.Factory opFactory,
+ final Mergeable mergeable,
@GerritServerConfig final Config cfg,
@Assisted final Change.Id id) {
this.patchSetDetail = patchSetDetail;
@@ -102,7 +111,7 @@
this.anonymousUser = anonymousUser;
this.aic = accountInfoCacheFactory.create();
- this.opFactory = opFactory;
+ this.mergeable = mergeable;
this.testMerge = cfg.getBoolean("changeMerge", "test", false);
this.changeId = id;
@@ -224,7 +233,21 @@
final Change.Status status = detail.getChange().getStatus();
if ((status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) &&
testMerge) {
- ChangeUtil.testMerge(opFactory, detail.getChange());
+ try {
+ detail.getChange().setMergeable(mergeable.apply(new RevisionResource(
+ new ChangeResource(control),
+ detail.getCurrentPatchSet())).mergeable);
+ } catch (RepositoryNotFoundException e) {
+ log.warn("Cannot check mergeable", e);
+ } catch (ResourceConflictException e) {
+ log.warn("Cannot check mergeable", e);
+ } catch (BadRequestException e) {
+ log.warn("Cannot check mergeable", e);
+ } catch (AuthException e) {
+ log.warn("Cannot check mergeable", e);
+ } catch (IOException e) {
+ log.warn("Cannot check mergeable", e);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index e988683..2dfd222 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.CommitMessageEditedSender;
@@ -40,7 +39,6 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.MagicBranch;
@@ -177,11 +175,6 @@
db.trackingIds().delete(toDelete);
}
- public static void testMerge(MergeOp.Factory opFactory, Change change)
- throws NoSuchProjectException {
- opFactory.create(change.getDest()).verifyMergeability(change);
- }
-
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
throws OrmException {
final int cnt = src.getParentCount();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
new file mode 100644
index 0000000..b220879
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -0,0 +1,196 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.SubmitStrategyFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+
+public class Mergeable implements RestReadView<RevisionResource> {
+ private static final Logger log = LoggerFactory.getLogger(Mergeable.class);
+
+ public static class MergeableInfo {
+ public String submitType;
+ public boolean mergeable;
+ }
+
+ private final TestSubmitType.Get submitType;
+ private final GitRepositoryManager gitManager;
+ private final SubmitStrategyFactory submitStrategyFactory;
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ Mergeable(TestSubmitType.Get submitType,
+ GitRepositoryManager gitManager,
+ SubmitStrategyFactory submitStrategyFactory,
+ Provider<ReviewDb> db) {
+ this.submitType = submitType;
+ this.gitManager = gitManager;
+ this.submitStrategyFactory = submitStrategyFactory;
+ this.db = db;
+ }
+
+ @Override
+ public MergeableInfo apply(RevisionResource resource)
+ throws ResourceConflictException, BadRequestException, AuthException,
+ OrmException, RepositoryNotFoundException, IOException {
+ Change change = resource.getChange();
+ PatchSet ps = resource.getPatchSet();
+ MergeableInfo result = new MergeableInfo();
+
+ if (!change.getStatus().isOpen()) {
+ throw new ResourceConflictException("change is " + Submit.status(change));
+ } else if (!ps.getId().equals(change.currentPatchSetId())) {
+ // Only the current revision is mergeable. Others always fail.
+ return result;
+ }
+
+ result.submitType = submitType.apply(resource);
+ result.mergeable = change.isMergeable();
+
+ Repository git = gitManager.openRepository(change.getProject());
+ try {
+ Ref ref = git.getRef(change.getDest().get());
+ if (isStale(change, ref)) {
+ result.mergeable = refresh(change, ps, result.submitType, git, ref);
+ }
+ } finally {
+ git.close();
+ }
+ return result;
+ }
+
+ private static boolean isStale(Change change, Ref ref) {
+ return change.getLastSha1MergeTested() == null
+ || !toRevId(ref).equals(change.getLastSha1MergeTested());
+ }
+
+ private static RevId toRevId(Ref ref) {
+ return new RevId(ref != null && ref.getObjectId() != null
+ ? ref.getObjectId().name()
+ : "");
+ }
+
+ private boolean refresh(Change change,
+ PatchSet ps,
+ String submitType,
+ Repository git,
+ Ref ref) throws IOException, OrmException {
+ Project.SubmitType type;
+ try {
+ type = Project.SubmitType.valueOf(submitType);
+ } catch (IllegalArgumentException unsupported) {
+ log.warn(String.format(
+ "Change %d uses unsupported submit_type \"%s\"",
+ change.getId().get(),
+ submitType));
+ return false;
+ }
+
+ RevWalk rw = new RevWalk(git) {
+ @Override
+ protected CodeReviewCommit createCommit(AnyObjectId id) {
+ return new CodeReviewCommit(id);
+ }
+ };
+ try {
+ ObjectId id;
+ try {
+ id = ObjectId.fromString(ps.getRevision().get());
+ } catch (IllegalArgumentException e) {
+ log.error(String.format(
+ "Invalid revision on patch set %d of %d",
+ ps.getId().get(),
+ change.getId().get()));
+ return false;
+ }
+
+ RevFlag canMerge = rw.newFlag("CAN_MERGE");
+ CodeReviewCommit rev = parse(rw, id);
+ rev.add(canMerge);
+
+ boolean mergeable;
+ if (ref == null || ref.getObjectId() == null) {
+ mergeable = true; // Assume yes on new branch.
+ } else {
+ CodeReviewCommit tip = parse(rw, ref.getObjectId());
+ mergeable = submitStrategyFactory.create(
+ type,
+ db.get(),
+ git,
+ rw,
+ null /*inserter*/,
+ canMerge,
+ Collections.<RevCommit> emptySet(),
+ change.getDest()).dryRun(tip, rev);
+ }
+
+ Change c = db.get().changes().get(change.getId());
+ if (c != null) {
+ c.setMergeable(mergeable);
+ c.setLastSha1MergeTested(toRevId(ref));
+ db.get().changes().update(Collections.singleton(c));
+ }
+ return mergeable;
+ } catch (MergeException e) {
+ return false;
+ } catch (IOException e) {
+ log.error(String.format(
+ "Cannot merge test change %d", change.getId().get()), e);
+ return false;
+ } catch (NoSuchProjectException e) {
+ log.error(String.format(
+ "Cannot merge test change %d", change.getId().get()), e);
+ return false;
+ } finally {
+ rw.release();
+ }
+ }
+
+ private static CodeReviewCommit parse(RevWalk rw, ObjectId id)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ return (CodeReviewCommit) rw.parseCommit(id);
+ }
+}
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 ed296f0..a9e3604 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
@@ -63,6 +63,7 @@
child(CHANGE_KIND, "revisions").to(Revisions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
+ get(REVISION_KIND, "mergeable").to(Mergeable.class);
get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);
post(REVISION_KIND, "submit").to(Submit.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 0db5208..cc88efb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -311,7 +311,7 @@
});
}
- private static String status(Change change) {
+ static String status(Change change) {
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
index 86d79e0..311856d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -24,7 +24,7 @@
import java.util.List;
/** Extended commit entity with code review specific metadata. */
-class CodeReviewCommit extends RevCommit {
+public class CodeReviewCommit extends RevCommit {
static CodeReviewCommit error(final CommitMergeStatus s) {
final CodeReviewCommit r = new CodeReviewCommit(ObjectId.zeroId());
r.statusCode = s;
@@ -59,7 +59,7 @@
/** Commits which are missing ancestors of this commit. */
List<CodeReviewCommit> missing;
- CodeReviewCommit(final AnyObjectId id) {
+ public CodeReviewCommit(final AnyObjectId id) {
super(id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 2c8aeb6..2b9234a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
-
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -87,7 +86,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
/**
@@ -199,61 +197,6 @@
commits = new HashMap<Change.Id, CodeReviewCommit>();
}
- public void verifyMergeability(Change change) throws NoSuchProjectException {
- try {
- setDestProject();
- openRepository();
- final Ref destBranchRef = repo.getRef(destBranch.get());
-
- // Test mergeability of the change if the last merged sha1
- // in the branch is different from the last sha1
- // the change was tested against.
- if ((destBranchRef == null && change.getLastSha1MergeTested() == null)
- || change.getLastSha1MergeTested() == null
- || (destBranchRef != null && !destBranchRef.getObjectId().getName()
- .equals(change.getLastSha1MergeTested().get()))) {
- openSchema();
- openBranch();
- validateChangeList(Collections.singletonList(change));
- if (!toMerge.isEmpty()) {
- final Entry<SubmitType, CodeReviewCommit> e =
- toMerge.entries().iterator().next();
- final boolean isMergeable =
- createStrategy(e.getKey()).dryRun(branchTip, e.getValue());
-
- // update sha1 tested merge.
- if (destBranchRef != null) {
- change.setLastSha1MergeTested(new RevId(destBranchRef
- .getObjectId().getName()));
- } else {
- change.setLastSha1MergeTested(new RevId(""));
- }
- change.setMergeable(isMergeable);
- db.changes().update(Collections.singleton(change));
- } else {
- log.error("Test merge attempt for change: " + change.getId()
- + " failed");
- }
- }
- } catch (MergeException e) {
- log.error("Test merge attempt for change: " + change.getId()
- + " failed", e);
- } catch (OrmException e) {
- log.error("Test merge attempt for change: " + change.getId()
- + " failed: Not able to query the database", e);
- } catch (IOException e) {
- log.error("Test merge attempt for change: " + change.getId()
- + " failed", e);
- } finally {
- if (repo != null) {
- repo.close();
- }
- if (db != null) {
- db.close();
- }
- }
- }
-
private void setDestProject() throws MergeException {
destProject = projectCache.get(destBranch.getParentKey());
if (destProject == null) {