Merge "InlineEdit: Fix diff against revision feature"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 01d92c6..ca567d1 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2070,7 +2070,7 @@
type = HTTP
httpHeader = TRUSTED_USER
-[http]
+[httpd]
filterClass = org.anyorg.MySecureFilter
----
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index c1f398f..72d7ddf 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -221,6 +221,7 @@
should be before the instance members.
* Annotations should go before language keywords (`final`, `private`, etc) +
Example: `@Assisted @Nullable final type varName`
+ * The `@Inject`-ed constructor arguments should be listed one per line.
* Imports should be mostly alphabetical (uppercase sorts before
all lowercase, which means classes come before packages at the
same level).
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index c2f2af4..1aeab2c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -415,6 +415,13 @@
its own custom event class derived from
`com.google.gerrit.server.events.Event`.
+Plugins which define new Events should register them via the
+`com.google.gerrit.server.events.EventTypes.registerClass()`
+method. This will make the EventType known to the system.
+Deserialzing events with the
+`com.google.gerrit.server.events.EventDeserializer` class requires
+that the event be registered in EventTypes.
+
[[validation]]
== Validation Listeners
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index a88b42b..3d3104c 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -145,6 +145,12 @@
git tag -a v2.5
----
+Tag the plugins:
+
+----
+ git submodule foreach git tag -a v2.5
+----
+
[[build-gerrit]]
=== Build Gerrit
@@ -313,20 +319,18 @@
[[push-tag]]
==== Push the Release Tag
-* Push the new Release Tag
-+
-For an `RC`:
-+
-----
- git push gerrit-review tag v2.5-rc0
-----
-+
-For a final `stable` release:
-+
+Push the new Release Tag:
+
----
git push gerrit-review tag v2.5
----
+Push the new Release Tag on the plugins:
+
+----
+ git submodule foreach git push gerrit-review tag v2.5
+----
+
[[upload-documentation]]
==== Upload the Documentation
@@ -400,9 +404,8 @@
* Update the new discussion group announcement to be sticky
** Go to: http://groups.google.com/group/repo-discuss/topics
** Click on the announcement thread
-** Near the top right, click on options
-** Under options, click the "Display this top first" checkbox
-** and Save
+** Near the top right, click on actions
+** Under actions, click the "Display this top first" checkbox
* Update the previous discussion group announcement to no longer be sticky
** See above (unclick checkbox)
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 576cec6..bd72353 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1445,6 +1445,10 @@
RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
----
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
[[get-edit-meta-data]]
=== Retrieve meta data of a file from Change Edit
--
@@ -1512,6 +1516,18 @@
M2JhNjcxZTk0OTBmNzUxNDU5ZGUzCg==
----
+Alternatively, if the only value of the Accept request header is
+`application/json` the commit message is returned as JSON string:
+
+.Response
+----
+ HTTP/1.1 200 OK
+
+)]}'
+"Subject of the commit message\n\nThis is the body of the commit message.\n\nChange-Id: Iaf1ba916bf843c175673d675bf7f52862f452db9\n"
+----
+
+
[[publish-edit]]
=== Publish Change Edit
--
@@ -2861,6 +2877,10 @@
Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
----
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
[[get-diff]]
=== Get Diff
--
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 584186c..bf6f928 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
@@ -16,9 +16,11 @@
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
+import com.google.common.net.HttpHeaders;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.server.OutputFormat;
+import org.apache.http.Header;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
@@ -41,7 +43,20 @@
@Override
public RestResponse get(String endPoint) throws IOException {
+ return getWithHeader(endPoint, null);
+ }
+
+ public RestResponse getJsonAccept(String endPoint) throws IOException {
+ return getWithHeader(endPoint,
+ new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
+ }
+
+ private RestResponse getWithHeader(String endPoint, Header header)
+ throws IOException {
HttpGet get = new HttpGet(url + "/a" + endPoint);
+ if (header != null) {
+ get.addHeader(header);
+ }
return new RestResponse(getClient().execute(get));
}
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 a177d00..9cc8f9c 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
@@ -32,6 +32,7 @@
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+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.client.SubmitType;
@@ -181,6 +182,66 @@
}
@Test
+ public void cherryPickToSameBranch() throws Exception {
+ PushOneCommit.Result r = createChange();
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "master";
+ in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
+ ChangeInfo cherryInfo = gApi.changes()
+ .id("p~master~" + r.getChangeId())
+ .revision(r.getCommit().name())
+ .cherryPick(in)
+ .get();
+ assertThat((Iterable<?>)cherryInfo.messages).hasSize(2);
+ Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
+ }
+
+ @Test
+ public void cherryPickToSameBranchWithRebase() throws Exception {
+ // Push a new change, then merge it
+ PushOneCommit.Result baseChange = createChange();
+ RevisionApi baseRevision =
+ gApi.changes().id("p~master~" + baseChange.getChangeId()).current();
+ baseRevision.review(ReviewInput.approve());
+ baseRevision.submit();
+
+ // Push a new change (change 1)
+ PushOneCommit.Result r1 = createChange();
+
+ // Push another new change (change 2)
+ String subject = "Test change\n\n" +
+ "Change-Id: Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), subject,
+ "another_file.txt", "another content");
+ PushOneCommit.Result r2 = push.to(git, "refs/for/master");
+
+ // Change 2's parent should be change 1
+ assertThat(r2.getCommit().getParents()[0].name())
+ .isEqualTo(r1.getCommit().name());
+
+ // Cherry pick change 2 onto the same branch
+ ChangeApi orig = gApi.changes().id("p~master~" + r2.getChangeId());
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "master";
+ in.message = subject;
+ ChangeApi cherry = orig.revision(r2.getCommit().name()).cherryPick(in);
+ ChangeInfo cherryInfo = cherry.get();
+ assertThat((Iterable<?>)cherryInfo.messages).hasSize(2);
+ Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
+
+ // Parent of change 2 should now be the change that was merged, i.e.
+ // change 2 is rebased onto the head of the master branch.
+ String newParent = cherryInfo.revisions.get(cherryInfo.currentRevision)
+ .commit.parents.get(0).commit;
+ assertThat(newParent).isEqualTo(baseChange.getCommit().name());
+ }
+
+ @Test
public void cherryPickIdenticalTree() throws Exception {
PushOneCommit.Result r = createChange();
CherryPickInput in = new CherryPickInput();
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 2640645..371a472 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
@@ -53,10 +53,10 @@
import com.google.gerrit.server.edit.UnchangedCommitMessageException;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gson.stream.JsonReader;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
-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.ObjectId;
@@ -82,7 +82,7 @@
private final static String FILE_NAME3 = "foo3";
private final static byte[] CONTENT_OLD = "bar".getBytes(UTF_8);
private final static byte[] CONTENT_NEW = "baz".getBytes(UTF_8);
- private final static byte[] CONTENT_NEW2 = "qux".getBytes(UTF_8);
+ private final static byte[] CONTENT_NEW2 = "quxÄÜÖßµ".getBytes(UTF_8);
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
@@ -301,16 +301,14 @@
assertThat(adminSession.get(urlEditMessage()).getStatusCode())
.isEqualTo(SC_NOT_FOUND);
EditMessage.Input in = new EditMessage.Input();
- in.message = String.format("New commit message\n\nChange-Id: %s",
+ in.message = String.format("New commit message\n\n" +
+ CONTENT_NEW2 + "\n\nChange-Id: %s",
change.getKey());
assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
.isEqualTo(SC_NO_CONTENT);
- RestResponse r = adminSession.get(urlEditMessage());
- assertThat(adminSession.get(urlEditMessage()).getStatusCode())
- .isEqualTo(SC_OK);
- String content = r.getEntityContent();
- assertThat(StringUtils.newStringUtf8(Base64.decodeBase64(content)))
- .isEqualTo(in.message);
+ RestResponse r = adminSession.getJsonAccept(urlEditMessage());
+ assertThat(r.getStatusCode()).isEqualTo(SC_OK);
+ assertThat(readContentFromJson(r)).isEqualTo(in.message);
Optional<ChangeEdit> edit = editUtil.byChange(change);
assertThat(edit.get().getEditCommit().getFullMessage())
.isEqualTo(in.message);
@@ -538,11 +536,10 @@
assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
.isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change);
- RestResponse r = adminSession.get(urlEditFile());
+ RestResponse r = adminSession.getJsonAccept(urlEditFile());
assertThat(r.getStatusCode()).isEqualTo(SC_OK);
- String content = r.getEntityContent();
- assertThat(StringUtils.newStringUtf8(Base64.decodeBase64(content)))
- .isEqualTo(StringUtils.newStringUtf8(CONTENT_NEW2));
+ assertThat(readContentFromJson(r)).isEqualTo(
+ StringUtils.newStringUtf8(CONTENT_NEW2));
}
@Test
@@ -718,4 +715,10 @@
assertThat(r.getStatusCode()).isEqualTo(SC_OK);
return newGson().fromJson(r.getReader(), EditInfo.class);
}
+
+ private String readContentFromJson(RestResponse r) throws IOException {
+ JsonReader jsonReader = new JsonReader(r.getReader());
+ jsonReader.setLenient(true);
+ return newGson().fromJson(jsonReader, String.class);
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java
index e87be82..c8856e7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java
@@ -15,4 +15,5 @@
package com.google.gerrit.extensions.api.changes;
public class FixInput {
+ public boolean deletePatchSetIfCommitMissing;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java
index 369bcda..a117d07 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java
@@ -22,4 +22,15 @@
public String message;
public Status status;
public String outcome;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+ .append('[').append(message);
+ if (status != null || outcome != null) {
+ sb.append(" (").append(status).append(": ").append(outcome)
+ .append(')');
+ }
+ return sb.append(']').toString();
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 1e62573..cbf906e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -22,21 +22,14 @@
String accountDashboard();
String accountInfoBlock();
String accountLinkPanel();
- String accountName();
String accountPassword();
String accountUsername();
String activeRow();
String addBranch();
String addMemberTextBox();
- String addReviewer();
String addSshKeyPanel();
String addWatchPanel();
- String approvalTable();
- String approvalhint();
- String approvalrole();
- String approvalscore();
String avatarInfoPanel();
- String blockHeader();
String bottomheader();
String cAPPROVAL();
String cLastUpdate();
@@ -45,44 +38,32 @@
String cSUBJECT();
String cSTATUS();
String cellsNextToFileComment();
- String changeComments();
- String changeInfoBlock();
- String changeInfoTopicPanel();
- String changeScreen();
String changeScreenDescription();
String changeScreenStarIcon();
String changeSize();
String changeTable();
String changeTablePrevNextLinks();
String changeTypeCell();
- String changeid();
- String closedstate();
String commentCell();
String commentEditorPanel();
String commentHolder();
String commentHolderLeftmost();
String commentPanel();
String commentPanelAuthorCell();
- String commentPanelBorder();
String commentPanelButtons();
String commentPanelContent();
String commentPanelDateCell();
String commentPanelHeader();
String commentPanelLast();
- String commentPanelMenuBar();
String commentPanelMessage();
String commentPanelSummary();
String commentPanelSummaryCell();
String commentedActionDialog();
String commentedActionMessage();
- String complexHeader();
- String content();
String contributorAgreementAlreadySubmitted();
String contributorAgreementButton();
String contributorAgreementLegal();
String contributorAgreementShortDescription();
- String coverMessage();
- String createGroupLink();
String createProjectPanel();
String dataCell();
String dataCellHidden();
@@ -93,7 +74,6 @@
String diffTextCONTEXT();
String diffTextDELETE();
String diffTextFileHeader();
- String diffTextForBinaryInSideBySide();
String diffTextHunkHeader();
String diffTextINSERT();
String diffTextNoLF();
@@ -108,7 +88,6 @@
String downloadLinkHeader();
String downloadLinkHeaderGap();
String downloadLinkList();
- String downloadLinkListCell();
String downloadLink_Active();
String drafts();
String editHeadButton();
@@ -117,23 +96,18 @@
String errorDialogButtons();
String errorDialogErrorType();
String errorDialogGlass();
- String errorDialogText();
String errorDialogTitle();
String loadingPluginsDialog();
String fileColumnHeader();
String fileCommentBorder();
String fileLine();
- String fileLineCONTEXT();
String fileLineDELETE();
String fileLineINSERT();
- String fileLineMode();
- String fileLineNone();
String filePathCell();
String gerritBody();
String gerritTopMenu();
String greenCheckClass();
String groupDescriptionPanel();
- String groupExternalNameFilterTextBox();
String groupIncludesTable();
String groupMembersTable();
String groupName();
@@ -142,21 +116,16 @@
String groupOptionsPanel();
String groupOwnerPanel();
String groupOwnerTextBox();
- String groupTypeSelectListBox();
String groupUUIDPanel();
String header();
- String hyperlink();
String iconCell();
String iconCellOfFileCommentRow();
String iconHeader();
String identityUntrustedExternalId();
String infoBlock();
- String infoTable();
String inputFieldTypeHint();
- String labelList();
String labelNotApplicable();
String leftMostCell();
- String lineHeader();
String lineNumber();
String link();
String linkMenuBar();
@@ -169,60 +138,38 @@
String menuBarUserNamePanel();
String menuItem();
String menuScreenMenuBar();
- String missingApproval();
- String missingApprovalList();
- String monospace();
String needsReview();
String negscore();
- String noLineLineNumber();
String noborder();
- String notVotable();
- String outdated();
- String parentsTable();
String patchBrowserPopup();
String patchBrowserPopupBody();
String patchCellReverseDiff();
- String patchComments();
String patchContentTable();
String patchHistoryTable();
String patchHistoryTablePatchSetHeader();
String patchNoDifference();
- String patchScreenDisplayControls();
String patchSetActions();
- String patchSetInfoBlock();
- String patchSetLink();
- String patchSetRevision();
- String patchSetUserIdentity();
String patchSizeCell();
String pluginProjectConfigInheritedValue();
String pluginsTable();
String posscore();
String projectActions();
- String projectAdminLabelRangeLine();
- String projectAdminLabelValue();
String projectFilterLabel();
String projectFilterPanel();
String projectNameColumn();
- String publishCommentsScreen();
String registerScreenExplain();
String registerScreenNextLinks();
String registerScreenSection();
- String removeReviewer();
- String removeReviewerCell();
String reviewedPanelBottom();
String rightBorder();
- String rightmost();
String rpcStatus();
String screen();
String screenHeader();
- String screenNoHeader();
String searchPanel();
String suggestBoxPopup();
String sectionHeader();
- String selectPatchSetOldVersion();
String sideBySideScreenLinkTable();
String singleLine();
- String skipLine();
String smallHeading();
String sourceFilePath();
String specialBranchDataCell();
@@ -244,7 +191,6 @@
String unifiedTable();
String unifiedTableHeader();
String userInfoPopup();
- String useridentity();
String usernameField();
String watchedProjectFilter();
}
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 f5fa6dd..71231e4 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
@@ -150,10 +150,6 @@
submit.setVisible(canSubmit);
}
- boolean isSubmitEnabled() {
- return submit.isVisible() && submit.isEnabled();
- }
-
@UiHandler("followUp")
void onFollowUp(@SuppressWarnings("unused") ClickEvent e) {
if (followUpAction == null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
index 072b78e..faba10d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
@@ -169,7 +169,7 @@
.openElement("button")
.setAttribute("title", "Remove hashtag")
.setAttribute("onclick", REMOVE + "(event)")
- .append("☒")
+ .append("×")
.closeElement("button")
.closeSpan();
if (itr.hasNext()) {
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 849512b..a416894 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
@@ -273,7 +273,7 @@
html.openElement("button")
.setAttribute("title", Util.M.removeReviewer(name))
.setAttribute("onclick", REMOVE + "(event)")
- .append("☒")
+ .append("×")
.closeElement("button");
}
html.closeSpan();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index 53729e7..2803db3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -25,6 +25,10 @@
width: 20px;
}
+.table th {
+ vertical-align: top;
+ text-align: left;
+}
.table tr {
vertical-align: top;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index a9082e2..38b2f69 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -84,10 +84,6 @@
color: black;
}
-.hyperlink {
- text-decoration: underline;
-}
-
.accountLinkPanel {
display: inline;
}
@@ -105,10 +101,6 @@
top: -1px;
}
-.accountName {
- white-space: nowrap;
-}
-
.inputFieldTypeHint {
color: grey;
}
@@ -118,13 +110,6 @@
font-weight: bold;
}
-.blockHeader {
- font-size: small;
- font-weight: bold;
- background: trimColor;
- padding: 0.2em 0.2em 0.2em 0.5em;
-}
-
.link {
cursor: pointer;
}
@@ -156,47 +141,6 @@
}
/** CommentPanel **/
-.commentPanelBorder {
- border-top: 1px solid lightgray;
- border-left: 1px solid lightgray;
- border-right: 1px solid lightgray;
- border-top-left-radius: 8px;
- border-top-right-radius: 8px;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
-}
-.commentPanelBorder.commentPanelLast {
- border-bottom: 1px solid lightgray;
- border-bottom-left-radius: 8px;
- border-bottom-right-radius: 8px;
-}
-
-@if user.agent safari {
- .commentPanelBorder {
- \-webkit-border-top-left-radius: 8px;
- \-webkit-border-top-right-radius: 8px;
- \-webkit-border-bottom-left-radius: 0px;
- \-webkit-border-bottom-right-radius: 0px;
- }
- .commentPanelBorder.commentPanelLast {
- \-webkit-border-bottom-left-radius: 8px;
- \-webkit-border-bottom-right-radius: 8px;
- }
-}
-
-@if user.agent gecko1_8 {
- .commentPanelBorder {
- \-moz-border-radius-topleft: 8px;
- \-moz-border-radius-topright: 8px;
- \-moz-border-radius-bottomleft: 0;
- \-moz-border-radius-bottomright: 0;
- }
- .commentPanelBorder.commentPanelLast {
- \-moz-border-radius-bottomleft: 8px;
- \-moz-border-radius-bottomright: 8px;
- }
-}
-
.commentPanelHeader {
cursor: pointer;
width: 100%;
@@ -224,9 +168,6 @@
padding-left: 0.5em;
padding-right: 0.5em;
}
-.commentPanelMenuBar {
- float: right;
-}
.commentPanelMessage p {
margin-top: 0px;
margin-bottom: 0px;
@@ -428,10 +369,6 @@
width: 100%;
margin-top: 15px;
}
-.errorDialogText {
- font-size: 15px;
- font-family: verdana;
-}
.errorDialog a,
.errorDialog a:visited,
.errorDialog a:hover {
@@ -459,10 +396,6 @@
overflow: hidden;
}
-.screenNoHeader {
- margin-top: 5px;
-}
-
/** ChangeTable **/
.changeTable {
border-collapse: separate;
@@ -477,10 +410,6 @@
background: tableOddRowColor;
}
-.changeTable .outdated {
- background: changeTableOutdatedColor !important;
-}
-
.changeTable .iconCell {
width: 1px;
padding: 0px;
@@ -675,10 +604,6 @@
/** PatchScreen **/
-.patchScreenDisplayControls .gwt-CheckBox {
- margin-right: 1em;
-}
-
.reviewedPanelBottom {
float: right;
font-size: small;
@@ -716,10 +641,6 @@
border-left: thin solid #b0bdcc;
}
-.patchContentTable .diffTextForBinaryInSideBySide {
- width: 50%;
-}
-
.patchContentTable .diffTextFileHeader {
color: grey;
font-weight: bold;
@@ -787,9 +708,6 @@
background: white;
border-bottom: 1px solid white;
}
-.lineNumber.rightmost {
- border-left: thin solid #b0bdcc;
-}
.lineNumber.rightBorder {
border-right: thin solid #b0bdcc;
}
@@ -809,14 +727,6 @@
.lineNumber.fileColumnHeader {
border-bottom: 1px solid trimColor;
}
-.noLineLineNumber {
- font-family: mono-font;
- width: 3.5em;
- padding-left: 0.2em;
- padding-right: 0.2em;
- background: white;
- border-bottom: 1px solid white;
-}
.fileLine {
padding-left: 0;
@@ -824,22 +734,11 @@
white-space: pre;
border-left: thin solid #b0bdcc;
}
-.fileLineNone {
- background: #eeeeee;
- border-bottom: 1px solid #eeeeee;
-}
-.fileLineMode {
- font-weight: bold;
-}
.fileLineDELETE,
.fileLineDELETE .wdc {
background: #ffeeee;
border-bottom: 1px solid #ffeeee;
}
-.fileLineCONTEXT {
- background: white;
- border-bottom: 1px solid white;
-}
.fileLineINSERT,
.fileLineINSERT .wdc {
background: #ddffdd;
@@ -852,27 +751,6 @@
border-bottom: 1px solid #9F9;
}
-.patchContentTable .skipLine .iconCell,
-.patchContentTable .skipLine {
- font-family: norm-font;
- text-align: center;
- font-style: italic;
- background: #def;
- color: grey;
-}
-.patchContentTable .skipLine div {
- display: inline;
-}
-.patchContentTable a.skipLine {
- color: grey;
- text-decoration: none;
-}
-.patchContentTable a:hover.skipLine {
- background: white;
- color: #00A;
- text-decoration: underline;
-}
-
.patchContentTable td.cellsNextToFileComment {
background: trimColor;
border-top: trimColor;
@@ -908,36 +786,6 @@
margin-right: 5px;
}
-.changeScreen .gwt-DisclosurePanel .header td {
- font-weight: bold;
- white-space: nowrap;
-}
-
-.changeScreen .gwt-DisclosurePanel .complexHeader {
- white-space: nowrap;
-}
-.changeScreen .gwt-DisclosurePanel .complexHeader span {
- white-space: nowrap;
-}
-
-.patchSetRevision {
- padding-left: 20px;
- font-size: 8pt;
-}
-
-.patchSetLink {
- padding-left: 0.5em;
- font-size: 8pt;
-}
-
-.changeScreen .gwt-DisclosurePanel .content {
- margin-bottom: 10px;
-}
-
-.gwt-DisclosurePanel .content {
- margin-left: 10px;
-}
-
.changeScreenDescription,
.changeScreenDescription textarea {
white-space: pre;
@@ -949,74 +797,6 @@
padding-top: 0.5em;
}
-.changeComments {
- padding-top: 1em;
- width: 60em;
-}
-
-.infoTable {
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-.infoTable td {
- border-left: 1px solid trimColor;
- border-bottom: 1px solid trimColor;
- padding: 2px 6px 1px;
-}
-
-.infoTable td.header {
- background-color: trimColor;
- font-weight: normal;
- padding: 2px 4px 0 6px;
- font-style: italic;
- text-align: left;
- vertical-align: top;
- white-space: nowrap;
-}
-
-.rightmost {
- border-right: 1px solid trimColor;
-}
-
-.infoTable td.approvalrole {
- width: 5em;
- border-left: none;
- font-style: italic;
- white-space: nowrap;
-}
-
-.infoTable td.approvalscore {
- text-align: center;
-}
-.infoTable td.notVotable {
- background: #F5F5F5;
-}
-.infoTable td.negscore {
- color: red;
-}
-.infoTable td.posscore {
- color: #08a400;
-}
-
-.infoTable td.approvalhint {
- white-space: nowrap;
- text-align: left;
- color: #444444;
-}
-
-.changeInfoBlock {
- margin-right: 15px;
-}
-
-.changeInfoTopicPanel img {
- float: right;
-}
-
-.changeInfoTopicPanel a {
- float: left;
-}
-
.avatarInfoPanel {
margin-right: 10px;
}
@@ -1060,27 +840,6 @@
border-bottom: 1px solid trimColor;
}
-.infoBlock td.closedstate {
- font-weight: bold;
-}
-
-.infoBlock td.useridentity {
- white-space: nowrap;
-}
-.changeInfoBlock td.changeid {
- font-size: x-small;
-}
-
-
-.patchSetInfoBlock {
- margin-bottom: 10px;
-}
-.patchSetUserIdentity {
- white-space: nowrap;
-}
-.patchSetUserIdentity .gwt-InlineLabel {
- margin-left: 0.2em;
-}
.patchSetActions {
margin-bottom: 10px;
@@ -1090,42 +849,10 @@
font-size: 8pt;
}
-.selectPatchSetOldVersion {
- font-weight: bold;
- margin-right: 30px;
- margin-top: 5px;
-}
-
-.approvalTable {
- margin-top: 1em;
- margin-bottom: 1em;
-}
-.missingApprovalList {
- margin-top: 5px;
- margin-left: 1em;
- padding-left: 1em;
- margin-bottom: 0px;
-}
-.missingApproval {
- font-size: small;
- white-space: nowrap;
-}
-.addReviewer {
- margin-left: 1em;
- margin-top: 5px;
- white-space: nowrap;
-}
-.removeReviewer {
- padding: 0px;
-}
-td.removeReviewerCell {
- padding-left: 4em;
- border-left: none;
-}
-
.downloadBox {
min-width: 580px;
margin: 5px;
+ margin-right: 15px;
}
.downloadBoxTable {
border-spacing: 0;
@@ -1167,9 +894,6 @@
.downloadBoxCopyLabel div {
float: right;
}
-td.downloadLinkListCell {
- padding: 0px;
-}
.downloadLinkHeader {
background: trimColor;
white-space: nowrap;
@@ -1208,27 +932,6 @@
width: 30em;
}
-.parentsTable {
- border-style: none;
- outline: 0px;
- padding: 0px;
- border-spacing: 0px;
- text-align: left;
- font-family: mono-font;
- font-size: 10px;
-}
-
-.parentsTable td.noborder {
- border: none;
-}
-
-.parentsTable td.monospace {
- font-family: mono-font;
- font-size: 10px;
- margin: 0px;
- padding-left: 0px;
-}
-
/** UnifiedScreen **/
.unifiedTable {
width: 100%;
@@ -1347,10 +1050,6 @@
width: 100%;
}
-.createGroupLink {
- margin-bottom: 10px;
-}
-
.createProjectPanel {
margin-bottom: 10px;
background-color: trimColor;
@@ -1436,66 +1135,10 @@
width: 45em;
}
-.projectAdminLabelRangeLine {
- white-space: nowrap;
-}
-.projectAdminLabelValue {
- font-family: mono-font;
- font-size: small;
-}
.projectActions {
margin-bottom: 10px;
}
-/** PublishCommentsScreen **/
-.publishCommentsScreen .smallHeading {
- font-size: small;
- font-weight: bold;
- white-space: nowrap;
-}
-.publishCommentsScreen .labelList {
- margin-bottom: 10px;
- margin-left: 10px;
- background: trimColor;
- width: 25em;
- white-space: nowrap;
- padding-top: 2px;
- padding-bottom: 2px;
-}
-.publishCommentsScreen .coverMessage {
- margin-left: 10px;
- padding: 5px 5px 5px 5px;
-}
-.publishCommentsScreen .coverMessage textarea {
- font-size: small;
-}
-.publishCommentsScreen .labelList .gwt-RadioButton {
- font-size: smaller;
-}
-.publishCommentsScreen .patchComments {
- margin-left: 1em;
- margin-right: 0.5em;
- margin-bottom: 0.5em;
-}
-.publishCommentsScreen .gwt-Hyperlink {
- white-space: nowrap;
- font-size: small;
-}
-.publishCommentsScreen .lineHeader {
- white-space: nowrap;
- font-family: mono-font;
- font-size: small;
- font-style: italic;
- padding-left: 3px;
-}
-.publishCommentsScreen .commentPanel {
- border: none;
- width: 35em;
-}
-.publishCommentsScreen .commentPanelDateCell {
- display: none;
-}
-
/** CommentedActionDialog **/
.commentedActionDialog .gwt-DisclosurePanel .header td {
@@ -1538,10 +1181,6 @@
.groupDescriptionPanel {
margin-bottom: 3px;
}
-.groupExternalNameFilterTextBox {
- margin-right: 2px;
- margin-bottom: 2px;
-}
.groupNamePanel {
margin-bottom: 3px;
}
@@ -1557,9 +1196,6 @@
.groupOwnerTextBox {
margin-bottom: 2px;
}
-.groupTypeSelectListBox {
- margin-bottom: 2px;
-}
/** AccountGroupMembersScreen **/
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
index 218a6c3..178a583 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
@@ -261,8 +261,9 @@
private static boolean isUnifiedPatchLink(final Patch patch) {
return (patch.getPatchType().equals(PatchType.BINARY)
- || Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
- .equals(DiffView.UNIFIED_DIFF));
+ || (Gerrit.isSignedIn()
+ && Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
+ .equals(DiffView.UNIFIED_DIFF)));
}
private static String getFileNameOnly(Patch patch) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
index eb62809..4cc6a16 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
@@ -14,7 +14,6 @@
package com.google.gerrit.client.ui;
-import com.google.gerrit.client.Gerrit;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
@@ -56,7 +55,6 @@
{
setElement((Element)(DOM.createTD()));
getElement().setInnerHTML(" ");
- addStyleName(Gerrit.RESOURCES.css().complexHeader());
}
@Override
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 4c0bb69..fe2cd96 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -598,4 +598,14 @@
public void setTopic(String topic) {
this.topic = topic;
}
+
+ @Override
+ public String toString() {
+ return new StringBuilder(getClass().getSimpleName())
+ .append('{').append(changeId)
+ .append(" (").append(changeKey).append("), ")
+ .append("dest=").append(dest).append(", ")
+ .append("status=").append(status).append('}')
+ .toString();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
index 6f5618b..ef28ed8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server;
+import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.CapabilityControl;
@@ -39,8 +40,9 @@
InternalUser create();
}
+ @VisibleForTesting
@Inject
- protected InternalUser(CapabilityControl.Factory capabilityControlFactory) {
+ public InternalUser(CapabilityControl.Factory capabilityControlFactory) {
super(capabilityControlFactory);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 7197269..52d2032 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -17,6 +17,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Ordering;
@@ -28,7 +29,12 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
@@ -39,7 +45,9 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
+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.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -49,6 +57,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -82,6 +91,9 @@
private final Provider<ReviewDb> db;
private final GitRepositoryManager repoManager;
+ private final Provider<CurrentUser> user;
+ private final Provider<PersonIdent> serverIdent;
+ private final PatchSetInfoFactory patchSetInfoFactory;
private FixInput fix;
private Change change;
@@ -95,9 +107,15 @@
@Inject
ConsistencyChecker(Provider<ReviewDb> db,
- GitRepositoryManager repoManager) {
+ GitRepositoryManager repoManager,
+ Provider<CurrentUser> user,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent,
+ PatchSetInfoFactory patchSetInfoFactory) {
this.db = db;
this.repoManager = repoManager;
+ this.user = user;
+ this.serverIdent = serverIdent;
+ this.patchSetInfoFactory = patchSetInfoFactory;
reset();
}
@@ -192,26 +210,36 @@
}
}
+ private static final Function<PatchSet, Integer> TO_PS_ID =
+ new Function<PatchSet, Integer>() {
+ @Override
+ public Integer apply(PatchSet in) {
+ return in.getId().get();
+ }
+ };
+
+ private static final Ordering<PatchSet> PS_ID_ORDER = Ordering.natural()
+ .onResultOf(TO_PS_ID);
+
private boolean checkPatchSets() {
List<PatchSet> all;
try {
- all = db.get().patchSets().byChange(change.getId()).toList();
+ all = Lists.newArrayList(db.get().patchSets().byChange(change.getId()));
} catch (OrmException e) {
return error("Failed to look up patch sets", e);
}
- Function<PatchSet, Integer> toPsId = new Function<PatchSet, Integer>() {
- @Override
- public Integer apply(PatchSet in) {
- return in.getId().get();
- }
- };
+ // Iterate in descending order so deletePatchSet can assume the latest patch
+ // set exists.
+ Collections.sort(all, PS_ID_ORDER.reverse());
Multimap<ObjectId, PatchSet> bySha = MultimapBuilder.hashKeys(all.size())
- .treeSetValues(Ordering.natural().onResultOf(toPsId))
+ .treeSetValues(PS_ID_ORDER)
.build();
for (PatchSet ps : all) {
+ // Check revision format.
ObjectId objId;
String rev = ps.getRevision().get();
int psNum = ps.getId().get();
+ String refName = ps.getId().toRefName();
try {
objId = ObjectId.fromString(rev);
} catch (IllegalArgumentException e) {
@@ -221,22 +249,48 @@
}
bySha.put(objId, ps);
+ // Check ref existence.
+ ProblemInfo refProblem = null;
+ try {
+ Ref ref = repo.getRef(refName);
+ if (ref == null) {
+ refProblem = problem("Ref missing: " + refName);
+ } else if (!objId.equals(ref.getObjectId())) {
+ String actual = ref.getObjectId() != null
+ ? ref.getObjectId().name()
+ : "null";
+ refProblem = problem(String.format(
+ "Expected %s to point to %s, found %s",
+ ref.getName(), objId.name(), actual));
+ }
+ } catch (IOException e) {
+ error("Error reading ref: " + refName, e);
+ refProblem = lastProblem();
+ }
+
+ // Check object existence.
RevCommit psCommit = parseCommit(
objId, String.format("patch set %d", psNum));
if (psCommit == null) {
+ if (fix != null && fix.deletePatchSetIfCommitMissing) {
+ deletePatchSet(lastProblem(), ps.getId());
+ }
continue;
+ } else if (refProblem != null && fix != null) {
+ fixPatchSetRef(refProblem, ps);
}
if (ps.getId().equals(change.currentPatchSetId())) {
currPsCommit = psCommit;
}
}
+ // Check for duplicates.
for (Map.Entry<ObjectId, Collection<PatchSet>> e
: bySha.asMap().entrySet()) {
if (e.getValue().size() > 1) {
problem(String.format("Multiple patch sets pointing to %s: %s",
e.getKey().name(),
- Collections2.transform(e.getValue(), toPsId)));
+ Collections2.transform(e.getValue(), TO_PS_ID)));
}
}
@@ -305,6 +359,104 @@
}
}
+ private void fixPatchSetRef(ProblemInfo p, PatchSet ps) {
+ try {
+ RefUpdate ru = repo.updateRef(ps.getId().toRefName());
+ ru.setForceUpdate(true);
+ ru.setNewObjectId(ObjectId.fromString(ps.getRevision().get()));
+ ru.setRefLogIdent(newRefLogIdent());
+ ru.setRefLogMessage("Repair patch set ref", true);
+ RefUpdate.Result result = ru.update();
+ switch (result) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ case NO_CHANGE:
+ p.status = Status.FIXED;
+ p.outcome = "Repaired patch set ref";
+ return;
+ default:
+ p.status = Status.FIX_FAILED;
+ p.outcome = "Failed to update patch set ref: " + result;
+ return;
+ }
+ } catch (IOException e) {
+ String msg = "Error fixing patch set ref";
+ log.warn(msg + ' ' + ps.getId().toRefName(), e);
+ p.status = Status.FIX_FAILED;
+ p.outcome = msg;
+ }
+ }
+
+ private void deletePatchSet(ProblemInfo p, PatchSet.Id psId) {
+ ReviewDb db = this.db.get();
+ Change.Id cid = psId.getParentKey();
+ try {
+ db.changes().beginTransaction(cid);
+ try {
+ Change c = db.changes().get(cid);
+ if (c == null) {
+ throw new OrmException("Change missing: " + cid);
+ }
+
+ if (psId.equals(c.currentPatchSetId())) {
+ List<PatchSet> all = Lists.newArrayList(db.patchSets().byChange(cid));
+ if (all.size() == 1 && all.get(0).getId().equals(psId)) {
+ p.status = Status.FIX_FAILED;
+ p.outcome = "Cannot delete patch set; no patch sets would remain";
+ return;
+ }
+ // If there were multiple missing patch sets, assumes deletePatchSet
+ // has been called in decreasing order, so the max remaining PatchSet
+ // is the effective current patch set.
+ Collections.sort(all, PS_ID_ORDER.reverse());
+ PatchSet.Id latest = null;
+ for (PatchSet ps : all) {
+ latest = ps.getId();
+ if (!ps.getId().equals(psId)) {
+ break;
+ }
+ }
+ c.setCurrentPatchSet(patchSetInfoFactory.get(db, latest));
+ db.changes().update(Collections.singleton(c));
+ }
+
+ // Delete dangling primary key references. Don't delete ChangeMessages,
+ // which don't use patch sets as a primary key, and may provide useful
+ // historical information.
+ db.accountPatchReviews().delete(
+ db.accountPatchReviews().byPatchSet(psId));
+ db.patchSetAncestors().delete(
+ db.patchSetAncestors().byPatchSet(psId));
+ db.patchSetApprovals().delete(
+ db.patchSetApprovals().byPatchSet(psId));
+ db.patchComments().delete(
+ db.patchComments().byPatchSet(psId));
+ db.patchSets().deleteKeys(Collections.singleton(psId));
+ db.commit();
+
+ p.status = Status.FIXED;
+ p.outcome = "Deleted patch set";
+ } finally {
+ db.rollback();
+ }
+ } catch (PatchSetInfoNotAvailableException | OrmException e) {
+ String msg = "Error deleting patch set";
+ log.warn(msg + ' ' + psId, e);
+ p.status = Status.FIX_FAILED;
+ p.outcome = msg;
+ }
+ }
+
+ private PersonIdent newRefLogIdent() {
+ CurrentUser u = user.get();
+ if (u.isIdentifiedUser()) {
+ return ((IdentifiedUser) u).newRefLogIdent();
+ } else {
+ return serverIdent.get();
+ }
+ }
+
private RevCommit parseCommit(ObjectId objId, String desc) {
try {
return rw.parseCommit(objId);
@@ -325,6 +477,10 @@
return p;
}
+ private ProblemInfo lastProblem() {
+ return problems.get(problems.size() - 1);
+ }
+
private boolean error(String msg, Throwable t) {
problem(msg);
// TODO(dborowitz): Expose stack trace to administrators.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
index 29dcb17..8843dbb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
@@ -21,15 +21,19 @@
import org.eclipse.jgit.transport.ReceiveCommand;
public class CommitReceivedEvent extends RefEvent {
- public final ReceiveCommand command;
- public final Project project;
- public final String refName;
- public final RevCommit commit;
- public final IdentifiedUser user;
+ public ReceiveCommand command;
+ public Project project;
+ public String refName;
+ public RevCommit commit;
+ public IdentifiedUser user;
+
+ public CommitReceivedEvent() {
+ super("commit-received");
+ }
public CommitReceivedEvent(ReceiveCommand command, Project project,
String refName, RevCommit commit, IdentifiedUser user) {
- super("commit-received");
+ this();
this.command = command;
this.project = project;
this.refName = refName;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java
new file mode 100644
index 0000000..3508acf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java
@@ -0,0 +1,49 @@
+// 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.events;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+
+/**
+ * JSON deserializer for {@link Event}s.
+ * <p>
+ * Deserialized objects are of an appropriate subclass based on the value of the
+ * top-level "type" element.
+ */
+public class EventDeserializer implements JsonDeserializer<Event> {
+ @Override
+ public Event deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ if (!json.isJsonObject()) {
+ throw new JsonParseException("Not an object");
+ }
+ JsonElement typeJson = json.getAsJsonObject().get("type");
+ if (typeJson == null || !typeJson.isJsonPrimitive()
+ || !typeJson.getAsJsonPrimitive().isString()) {
+ throw new JsonParseException("Type is not a string: " + typeJson);
+ }
+ String type = typeJson.getAsJsonPrimitive().getAsString();
+ Class<?> cls = EventTypes.getClass(type);
+ if (cls == null) {
+ throw new JsonParseException("Unknown event type: " + type);
+ }
+ return context.deserialize(json, cls);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
new file mode 100644
index 0000000..4d26e13
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
@@ -0,0 +1,64 @@
+// 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.events;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Class for registering event types */
+public class EventTypes {
+ private static final Map<String, Class<?>> typesByString = new HashMap<>();
+
+ static {
+ registerClass(new ChangeAbandonedEvent());
+ registerClass(new ChangeMergedEvent());
+ registerClass(new ChangeRestoredEvent());
+ registerClass(new CommentAddedEvent());
+ registerClass(new CommitReceivedEvent());
+ registerClass(new DraftPublishedEvent());
+ registerClass(new HashtagsChangedEvent());
+ registerClass(new MergeFailedEvent());
+ registerClass(new RefUpdatedEvent());
+ registerClass(new RefReceivedEvent());
+ registerClass(new ReviewerAddedEvent());
+ registerClass(new PatchSetCreatedEvent());
+ registerClass(new TopicChangedEvent());
+ }
+
+ /** Register an event.
+ *
+ * @param event The event to register.
+ * @throws IllegalArgumentException if the event's type is already
+ * registered.
+ **/
+ public static void registerClass(Event event) {
+ String type = event.getType();
+ if (typesByString.containsKey(type)) {
+ throw new IllegalArgumentException(
+ "Event type already registered: " + type);
+ }
+ typesByString.put(type, event.getClass());
+ }
+
+ /** Get the class for an event type.
+ *
+ * @param type The type.
+ * @return The event class, or null if no class is registered with the
+ * given type
+ **/
+ public static Class<?> getClass(String type) {
+ return typesByString.get(type);
+ }
+}
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 8acbcd3..1f0f6648 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
@@ -494,6 +494,7 @@
private ListMultimap<SubmitType, Change> validateChangeList(
List<ChangeData> submitted) throws MergeException {
+ logDebug("Validating {} changes", submitted.size());
ListMultimap<SubmitType, Change> toSubmit = ArrayListMultimap.create();
Map<String, Ref> allRefs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index 7c35014..53f39f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -142,9 +142,10 @@
new URLClassLoader(urls.toArray(new URL[urls.size()]),
PluginLoader.parentFor(type));
+ JarScanner jarScanner = createJarScanner(srcJar);
ServerPlugin plugin =
new ServerPlugin(name, description.canonicalUrl, description.user,
- srcJar, snapshot, new JarScanner(srcJar), description.dataDir,
+ srcJar, snapshot, jarScanner, description.dataDir,
pluginLoader);
plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile));
keep = true;
@@ -155,4 +156,13 @@
}
}
}
+
+ private JarScanner createJarScanner(File srcJar)
+ throws InvalidPluginException {
+ try {
+ return new JarScanner(srcJar);
+ } catch (IOException e) {
+ throw new InvalidPluginException("Cannot scan plugin file " + srcJar, e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index d7d0efd..6a65272 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -43,9 +43,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
@@ -67,12 +69,8 @@
private final JarFile jarFile;
- public JarScanner(File srcFile) throws InvalidPluginException {
- try {
- this.jarFile = new JarFile(srcFile);
- } catch (IOException e) {
- throw new InvalidPluginException("Cannot scan plugin file " + srcFile, e);
- }
+ public JarScanner(File srcFile) throws IOException {
+ this.jarFile = new JarFile(srcFile);
}
@Override
@@ -136,6 +134,36 @@
return result.build();
}
+ public List<String> findImplementationsOf(Class<?> requestedInterface)
+ throws IOException {
+ List<String> result = Lists.newArrayList();
+ String name = requestedInterface.getName().replace('.', '/');
+
+ Enumeration<JarEntry> e = jarFile.entries();
+ while (e.hasMoreElements()) {
+ JarEntry entry = e.nextElement();
+ if (skip(entry)) {
+ continue;
+ }
+
+ ClassData def = new ClassData(Collections.<String>emptySet());
+ try {
+ new ClassReader(read(jarFile, entry)).accept(def, SKIP_ALL);
+ } catch (RuntimeException err) {
+ PluginLoader.log.warn(String.format("Jar %s has invalid class file %s",
+ jarFile.getName(), entry.getName()), err);
+ continue;
+ }
+
+ if (def.isConcrete() && def.interfaces != null
+ && Iterables.contains(Arrays.asList(def.interfaces), name)) {
+ result.add(def.className);
+ }
+ }
+
+ return result;
+ }
+
private static boolean skip(JarEntry entry) {
if (!entry.getName().endsWith(".class")) {
return true; // Avoid non-class resources.
@@ -166,6 +194,7 @@
String className;
String annotationName;
String annotationValue;
+ String[] interfaces;
Iterable<String> exports;
private ClassData(Iterable<String> exports) {
@@ -183,6 +212,7 @@
String superName, String[] interfaces) {
this.className = Type.getObjectType(name).getClassName();
this.access = access;
+ this.interfaces = interfaces;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index d9dad78..62e1ba9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -656,7 +656,13 @@
@Override
public String toString() {
- return MoreObjects.toStringHelper(this).addValue(getId()).toString();
+ MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
+ if (change != null) {
+ h.addValue(change);
+ } else {
+ h.addValue(legacyId);
+ }
+ return h.toString();
}
public static class ChangedLines {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java
index 51aa283..358620f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
import static com.google.gerrit.testutil.TestChanges.newChange;
import static com.google.gerrit.testutil.TestChanges.newPatchSet;
import static java.util.Collections.singleton;
@@ -29,15 +29,20 @@
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.CurrentUser;
+import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.testutil.FakeAccountByEmailCache;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
import com.google.inject.util.Providers;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.After;
@@ -59,14 +64,21 @@
@Before
public void setUp() throws Exception {
+ FakeAccountByEmailCache accountCache = new FakeAccountByEmailCache();
schemaFactory = InMemoryDatabase.newDatabase();
schemaFactory.create();
db = schemaFactory.open();
repoManager = new InMemoryRepositoryManager();
- checker = new ConsistencyChecker(Providers.<ReviewDb> of(db), repoManager);
+ checker = new ConsistencyChecker(
+ Providers.<ReviewDb> of(db),
+ repoManager,
+ Providers.<CurrentUser> of(new InternalUser(null)),
+ Providers.of(new PersonIdent("server", "noreply@example.com")),
+ new PatchSetInfoFactory(repoManager, accountCache));
project = new Project.NameKey("repo");
repo = new TestRepository<>(repoManager.createRepository(project));
userId = new Account.Id(1);
+ accountCache.putAny(userId);
db.accounts().insert(singleton(new Account(userId, TimeUtil.nowTs())));
tip = repo.branch("master").commit().create();
}
@@ -83,31 +95,19 @@
@Test
public void validNewChange() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
- RevCommit commit1 = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps1 = newPatchSet(c.currentPatchSetId(), commit1, userId);
- db.patchSets().insert(singleton(ps1));
-
+ Change c = insertChange();
+ insertPatchSet(c);
incrementPatchSet(c);
- RevCommit commit2 = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit2, userId);
- db.patchSets().insert(singleton(ps2));
-
+ insertPatchSet(c);
assertProblems(c);
}
@Test
public void validMergedChange() throws Exception {
- Change c = newChange(project, userId);
+ Change c = insertChange();
c.setStatus(Change.Status.MERGED);
- db.changes().insert(singleton(c));
- RevCommit commit1 = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps1 = newPatchSet(c.currentPatchSetId(), commit1, userId);
- db.patchSets().insert(singleton(ps1));
+ insertPatchSet(c);
+ incrementPatchSet(c);
incrementPatchSet(c);
RevCommit commit2 = repo.branch(c.currentPatchSetId().toRefName()).commit()
@@ -135,68 +135,202 @@
public void missingRepo() throws Exception {
Change c = newChange(new Project.NameKey("otherproject"), userId);
db.changes().insert(singleton(c));
- PatchSet ps = newPatchSet(c.currentPatchSetId(),
- ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId);
- db.patchSets().insert(singleton(ps));
+ insertMissingPatchSet(c, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
assertProblems(c, "Destination repository not found: otherproject");
}
@Test
public void invalidRevision() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ Change c = insertChange();
- PatchSet ps = new PatchSet(c.currentPatchSetId());
- ps.setRevision(new RevId("fooooooooooooooooooooooooooooooooooooooo"));
- ps.setUploader(userId);
- ps.setCreatedOn(TimeUtil.nowTs());
- db.patchSets().insert(singleton(ps));
-
+ db.patchSets().insert(singleton(newPatchSet(c.currentPatchSetId(),
+ "fooooooooooooooooooooooooooooooooooooooo", userId)));
incrementPatchSet(c);
- RevCommit commit2 = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit2, userId);
- db.patchSets().insert(singleton(ps2));
+ insertPatchSet(c);
assertProblems(c,
"Invalid revision on patch set 1:"
+ " fooooooooooooooooooooooooooooooooooooooo");
}
+ // No test for ref existing but object missing; InMemoryRepository won't let
+ // us do such a thing.
+
@Test
- public void patchSetObjectMissing() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ public void patchSetObjectAndRefMissing() throws Exception {
+ Change c = insertChange();
PatchSet ps = newPatchSet(c.currentPatchSetId(),
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId);
db.patchSets().insert(singleton(ps));
assertProblems(c,
+ "Ref missing: " + ps.getId().toRefName(),
"Object missing: patch set 1: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
}
@Test
+ public void patchSetObjectAndRefMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId);
+ db.patchSets().insert(singleton(ps));
+
+ String refName = ps.getId().toRefName();
+ List<ProblemInfo> problems = checker.check(c, new FixInput()).problems();
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + refName);
+ assertThat(p.status).isNull();
+ }
+
+ @Test
+ public void patchSetRefMissing() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = insertPatchSet(c);
+ String refName = ps.getId().toRefName();
+ repo.update("refs/other/foo", ObjectId.fromString(ps.getRevision().get()));
+ deleteRef(refName);
+
+ assertProblems(c, "Ref missing: " + refName);
+ }
+
+ @Test
+ public void patchSetRefMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = insertPatchSet(c);
+ String refName = ps.getId().toRefName();
+ repo.update("refs/other/foo", ObjectId.fromString(ps.getRevision().get()));
+ deleteRef(refName);
+
+ List<ProblemInfo> problems = checker.check(c, new FixInput()).problems();
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + refName);
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Repaired patch set ref");
+
+ assertThat(repo.getRepository().getRef(refName).getObjectId().name())
+ .isEqualTo(ps.getRevision().get());
+ }
+
+ @Test
+ public void patchSetObjectAndRefMissingWithDeletingPatchSet()
+ throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+ incrementPatchSet(c);
+ PatchSet ps2 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(2);
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps2.getId().toRefName());
+ assertThat(p.status).isNull();
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps2.getId())).isNull();
+ }
+
+ @Test
+ public void patchSetMultipleObjectsMissingWithDeletingPatchSets()
+ throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+
+ incrementPatchSet(c);
+ PatchSet ps2 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ incrementPatchSet(c);
+ PatchSet ps3 = insertPatchSet(c);
+
+ incrementPatchSet(c);
+ PatchSet ps4 = insertMissingPatchSet(c,
+ "c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(4);
+
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps4.getId().toRefName());
+ assertThat(p.status).isNull();
+
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 4: c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ p = problems.get(2);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps2.getId().toRefName());
+ assertThat(p.status).isNull();
+
+ p = problems.get(3);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(3);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps2.getId())).isNull();
+ assertThat(db.patchSets().get(ps3.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps4.getId())).isNull();
+ }
+
+ @Test
+ public void onlyPatchSetObjectMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(2);
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps1.getId().toRefName());
+ assertThat(p.status).isNull();
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 1: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIX_FAILED);
+ assertThat(p.outcome)
+ .isEqualTo("Cannot delete patch set; no patch sets would remain");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ }
+
+ @Test
public void currentPatchSetMissing() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ Change c = insertChange();
assertProblems(c, "Current patch set 1 not found");
}
@Test
public void duplicatePatchSetRevisions() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
- RevCommit commit1 = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps1 = newPatchSet(c.currentPatchSetId(), commit1, userId);
- db.patchSets().insert(singleton(ps1));
-
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+ String rev = ps1.getRevision().get();
incrementPatchSet(c);
- PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit1, userId);
- db.patchSets().insert(singleton(ps2));
+ PatchSet ps2 = insertMissingPatchSet(c, rev);
+ updatePatchSetRef(ps2);
assertProblems(c,
- "Multiple patch sets pointing to " + commit1.name() + ": [1, 2]");
+ "Multiple patch sets pointing to " + rev + ": [1, 2]");
}
@Test
@@ -204,10 +338,10 @@
RefUpdate ru = repo.getRepository().updateRef("refs/heads/master");
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ Change c = insertChange();
RevCommit commit = repo.commit().create();
PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ updatePatchSetRef(ps);
db.patchSets().insert(singleton(ps));
assertProblems(c, "Destination ref not found (may be new branch): master");
@@ -215,23 +349,19 @@
@Test
public void mergedChangeIsNotMerged() throws Exception {
- Change c = newChange(project, userId);
+ Change c = insertChange();
c.setStatus(Change.Status.MERGED);
- db.changes().insert(singleton(c));
- RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
- .parent(tip).create();
- PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
- db.patchSets().insert(singleton(ps));
+ PatchSet ps = insertPatchSet(c);
+ String rev = ps.getRevision().get();
assertProblems(c,
- "Patch set 1 (" + commit.name() + ") is not merged into destination ref"
+ "Patch set 1 (" + rev + ") is not merged into destination ref"
+ " master (" + tip.name() + "), but change status is MERGED");
}
@Test
public void newChangeIsMerged() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ Change c = insertChange();
RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
.parent(tip).create();
PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
@@ -245,8 +375,7 @@
@Test
public void newChangeIsMergedWithFix() throws Exception {
- Change c = newChange(project, userId);
- db.changes().insert(singleton(c));
+ Change c = insertChange();
RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
.parent(tip).create();
PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
@@ -267,11 +396,52 @@
assertProblems(c);
}
+ private Change insertChange() throws Exception {
+ Change c = newChange(project, userId);
+ db.changes().insert(singleton(c));
+ return c;
+ }
+
+ private void incrementPatchSet(Change c) throws Exception {
+ TestChanges.incrementPatchSet(c);
+ db.changes().upsert(singleton(c));
+ }
+
+ private PatchSet insertPatchSet(Change c) throws Exception {
+ db.changes().upsert(singleton(c));
+ RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ updatePatchSetRef(ps);
+ db.patchSets().insert(singleton(ps));
+ return ps;
+ }
+
+ private PatchSet insertMissingPatchSet(Change c, String id) throws Exception {
+ PatchSet ps = newPatchSet(c.currentPatchSetId(),
+ ObjectId.fromString(id), userId);
+ db.patchSets().insert(singleton(ps));
+ return ps;
+ }
+
+ private void updatePatchSetRef(PatchSet ps) throws Exception {
+ repo.update(ps.getId().toRefName(),
+ ObjectId.fromString(ps.getRevision().get()));
+ }
+
+ private void deleteRef(String refName) throws Exception {
+ RefUpdate ru = repo.getRepository().updateRef(refName, true);
+ ru.setForceUpdate(true);
+ assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
+ }
+
private void assertProblems(Change c, String... expected) {
assertThat(Lists.transform(checker.check(c).problems(),
new Function<ProblemInfo, String>() {
@Override
public String apply(ProblemInfo in) {
+ checkArgument(in.status == null,
+ "Status is not null: " + in.message);
return in.message;
}
})).containsExactly((Object[]) expected);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
new file mode 100644
index 0000000..c22c7fb
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2015 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.events;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventTypes;
+
+import org.junit.Test;
+
+public class EventTypesTest {
+ public static class TestEvent extends Event {
+ public TestEvent() {
+ super("test-event");
+ }
+ }
+
+ public static class TestEvent2 extends Event {
+ public TestEvent2() {
+ super("test-event"); // Intentionally same as in TestEvent
+ }
+ }
+
+ public static class AnotherTestEvent extends Event {
+ public AnotherTestEvent() {
+ super("another-test-event");
+ }
+ }
+
+ @Test
+ public void testEventTypeRegistration() {
+ EventTypes.registerClass(new TestEvent());
+ EventTypes.registerClass(new AnotherTestEvent());
+ assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
+ assertThat(EventTypes.getClass("another-test-event"))
+ .isEqualTo(AnotherTestEvent.class);
+
+ try {
+ EventTypes.registerClass(new TestEvent());
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
+ }
+
+ try {
+ EventTypes.registerClass(new TestEvent2());
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
+ }
+ }
+
+ @Test
+ public void testGetClassForNonExistingType() {
+ Class<?> clazz = EventTypes.getClass("does-not-exist-event");
+ assertThat(clazz).isNull();
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java
new file mode 100644
index 0000000..c3bfe1e
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 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.testutil;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountByEmailCache;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Fake implementation of {@link AccountByEmailCache} for testing. */
+public class FakeAccountByEmailCache implements AccountByEmailCache {
+ private final SetMultimap<String, Account.Id> byEmail;
+ private final Set<Account.Id> anyEmail;
+
+ public FakeAccountByEmailCache() {
+ byEmail = HashMultimap.create();
+ anyEmail = new HashSet<>();
+ }
+
+ @Override
+ public synchronized Set<Account.Id> get(String email) {
+ return Collections.unmodifiableSet(
+ Sets.union(byEmail.get(email), anyEmail));
+ }
+
+ @Override
+ public synchronized void evict(String email) {
+ // Do nothing.
+ }
+
+ public synchronized void put(String email, Account.Id id) {
+ byEmail.put(email, id);
+ }
+
+ public synchronized void putAny(Account.Id id) {
+ anyEmail.add(id);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
index 11ba665..675634e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
@@ -69,8 +69,13 @@
public static PatchSet newPatchSet(PatchSet.Id id, ObjectId revision,
Account.Id userId) {
+ return newPatchSet(id, revision.name(), userId);
+ }
+
+ public static PatchSet newPatchSet(PatchSet.Id id, String revision,
+ Account.Id userId) {
PatchSet ps = new PatchSet(id);
- ps.setRevision(new RevId(revision.name()));
+ ps.setRevision(new RevId(revision));
ps.setUploader(userId);
ps.setCreatedOn(TimeUtil.nowTs());
return ps;