Merge "Prefer using Splitter to String.split"
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 16a3a93..185feb4 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -39,6 +39,14 @@
The core plugins are developed and maintained by the Gerrit maintainers
and the Gerrit community.
+[[codemirror-editor]]
+=== codemirror-editor
+
+CodeMirror plugin for polygerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codemirror-editor[
+Project] |
+
[[commit-message-length-validator]]
=== commit-message-length-validator
@@ -53,16 +61,6 @@
link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
-[[cookbook-plugin]]
-=== cookbook-plugin
-
-Sample plugin to demonstrate features of Gerrit's plugin API.
-
-link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/cookbook-plugin[
-Project] |
-link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
-
[[download-commands]]
=== download-commands
@@ -347,6 +345,16 @@
link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
+[[its-phabricator]]
+==== its-phabricator
+
+Plugin to integrate with Phabricator.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-phabricator[
+Project] |
+link:https://gerrit.googlesource.com/plugins/its-phabricator/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
[[its-rtc]]
==== its-rtc
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
index 5a49f38..454de68 100644
--- a/Documentation/rest-api-access.txt
+++ b/Documentation/rest-api-access.txt
@@ -402,7 +402,7 @@
|`config_visible` |not set if `false`|
Whether the calling user can see the `refs/meta/config` branch of the
project.
-|`groups` |A map of group UUID to
+|`groups` ||A map of group UUID to
link:rest-api-groups.html#group-info[GroupInfo] objects, describing
the group UUIDs used in the `local` map. Groups that are not visible
are omitted from the `groups` map.
diff --git a/antlr3/BUILD b/antlr3/BUILD
index 08acf38..6d7102a 100644
--- a/antlr3/BUILD
+++ b/antlr3/BUILD
@@ -7,11 +7,11 @@
cmd = " && ".join([
"$(location //lib/antlr:antlr-tool) -o $$TMP $<",
"cd $$TMP",
- "zip -q $$ROOT/$@ $$(find . -type f )",
+ "find . -exec touch -t 198001010000 '{}' ';'",
+ "zip -q $$ROOT/$@ $$(find . -type f)",
]),
tools = [
"//lib/antlr:antlr-tool",
- "@bazel_tools//tools/zip:zipper",
],
visibility = ["//visibility:public"],
)
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index b21d3df..0a9f0ec 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -290,6 +290,7 @@
if (source.get(ChangeField.REVIEWER.getName()) != null) {
cd.setReviewers(
ChangeField.parseReviewerFieldValues(
+ cd.getId(),
FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.REVIEWER.getName())) {
@@ -300,6 +301,7 @@
if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
cd.setReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
FluentIterable.from(
source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
@@ -311,6 +313,7 @@
if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
cd.setPendingReviewers(
ChangeField.parseReviewerFieldValues(
+ cd.getId(),
FluentIterable.from(
source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
@@ -322,6 +325,7 @@
if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
cd.setPendingReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
FluentIterable.from(
source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index b30e66c..468aa67 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -586,12 +586,14 @@
private void decodeReviewers(ListMultimap<String, IndexableField> doc, ChangeData cd) {
cd.setReviewers(
ChangeField.parseReviewerFieldValues(
+ cd.getId(),
FluentIterable.from(doc.get(REVIEWER_FIELD)).transform(IndexableField::stringValue)));
}
private void decodeReviewersByEmail(ListMultimap<String, IndexableField> doc, ChangeData cd) {
cd.setReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
FluentIterable.from(doc.get(REVIEWER_BY_EMAIL_FIELD))
.transform(IndexableField::stringValue)));
}
@@ -599,6 +601,7 @@
private void decodePendingReviewers(ListMultimap<String, IndexableField> doc, ChangeData cd) {
cd.setPendingReviewers(
ChangeField.parseReviewerFieldValues(
+ cd.getId(),
FluentIterable.from(doc.get(PENDING_REVIEWER_FIELD))
.transform(IndexableField::stringValue)));
}
@@ -607,6 +610,7 @@
ListMultimap<String, IndexableField> doc, ChangeData cd) {
cd.setPendingReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
FluentIterable.from(doc.get(PENDING_REVIEWER_BY_EMAIL_FIELD))
.transform(IndexableField::stringValue)));
}
diff --git a/java/com/google/gerrit/server/account/AccountResolver.java b/java/com/google/gerrit/server/account/AccountResolver.java
index ee049c07..017bcaa 100644
--- a/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/java/com/google/gerrit/server/account/AccountResolver.java
@@ -116,7 +116,7 @@
}
}
- if (nameOrEmail.matches(ExternalId.USER_NAME_PATTERN_REGEX)) {
+ if (ExternalId.isValidUsername(nameOrEmail)) {
Optional<AccountState> who = byId.getByUsername(nameOrEmail);
if (who.isPresent()) {
return ImmutableSet.of(who.map(a -> a.getAccount().getId()).get());
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 3b6ebd1..ffd413a 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -51,7 +51,7 @@
private static final String USER_NAME_PATTERN_LAST_REGEX = "[a-zA-Z0-9]";
/** Regular expression that a username must match. */
- public static final String USER_NAME_PATTERN_REGEX =
+ private static final String USER_NAME_PATTERN_REGEX =
"^"
+ //
"("
diff --git a/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index 01ebb1f..c7d3f73 100644
--- a/java/com/google/gerrit/server/args4j/AccountIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -91,7 +91,7 @@
}
private Account.Id createAccountByLdap(String user) throws CmdLineException, IOException {
- if (!user.matches(ExternalId.USER_NAME_PATTERN_REGEX)) {
+ if (!ExternalId.isValidUsername(user)) {
throw new CmdLineException(owner, "user \"" + user + "\" not found");
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 00aa5a4..c30edc1 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -256,39 +256,44 @@
return state.toString() + ',' + adr;
}
- public static ReviewerSet parseReviewerFieldValues(Iterable<String> values) {
+ public static ReviewerSet parseReviewerFieldValues(Change.Id changeId, Iterable<String> values) {
ImmutableTable.Builder<ReviewerStateInternal, Account.Id, Timestamp> b =
ImmutableTable.builder();
for (String v : values) {
int i = v.indexOf(',');
if (i < 0) {
- log.error("Invalid value for reviewer field: {}", v);
+ log.warn("Invalid value for reviewer field from change {}: {}", changeId.get(), v);
continue;
}
int i2 = v.lastIndexOf(',');
if (i2 == i) {
- log.error("Invalid value for reviewer field: {}", v);
+ log.warn("Invalid value for reviewer field from change {}: {}", changeId.get(), v);
continue;
}
com.google.common.base.Optional<ReviewerStateInternal> reviewerState =
Enums.getIfPresent(ReviewerStateInternal.class, v.substring(0, i));
if (!reviewerState.isPresent()) {
- log.error("Failed to parse reviewer state from reviewer field: {}", v);
+ log.warn(
+ "Failed to parse reviewer state of reviewer field from change {}: {}",
+ changeId.get(),
+ v);
continue;
}
Optional<Account.Id> accountId = Account.Id.tryParse(v.substring(i + 1, i2));
if (!accountId.isPresent()) {
- log.error("Failed to parse account ID from reviewer field: {}", v);
+ log.warn(
+ "Failed to parse account ID of reviewer field from change {}: {}", changeId.get(), v);
continue;
}
Long l = Longs.tryParse(v.substring(i2 + 1, v.length()));
if (l == null) {
- log.error("Failed to parse timestamp from reviewer field: {}", v);
+ log.warn(
+ "Failed to parse timestamp of reviewer field from change {}: {}", changeId.get(), v);
continue;
}
Timestamp timestamp = new Timestamp(l);
@@ -298,37 +303,47 @@
return ReviewerSet.fromTable(b.build());
}
- public static ReviewerByEmailSet parseReviewerByEmailFieldValues(Iterable<String> values) {
+ public static ReviewerByEmailSet parseReviewerByEmailFieldValues(
+ Change.Id changeId, Iterable<String> values) {
ImmutableTable.Builder<ReviewerStateInternal, Address, Timestamp> b = ImmutableTable.builder();
for (String v : values) {
int i = v.indexOf(',');
if (i < 0) {
- log.error("Invalid value for reviewer by email field: {}", v);
+ log.warn("Invalid value for reviewer by email field from change {}: {}", changeId.get(), v);
continue;
}
int i2 = v.lastIndexOf(',');
if (i2 == i) {
- log.error("Invalid value for reviewer by email field: {}", v);
+ log.warn("Invalid value for reviewer by email field from change {}: {}", changeId.get(), v);
continue;
}
com.google.common.base.Optional<ReviewerStateInternal> reviewerState =
Enums.getIfPresent(ReviewerStateInternal.class, v.substring(0, i));
if (!reviewerState.isPresent()) {
- log.error("Failed to parse reviewer state from reviewer by email field: {}", v);
+ log.warn(
+ "Failed to parse reviewer state of reviewer by email field from change {}: {}",
+ changeId.get(),
+ v);
continue;
}
Address address = Address.tryParse(v.substring(i + 1, i2));
if (address == null) {
- log.error("Failed to parse address from reviewer by email field: {}", v);
+ log.warn(
+ "Failed to parse address of reviewer by email field from change {}: {}",
+ changeId.get(),
+ v);
continue;
}
Long l = Longs.tryParse(v.substring(i2 + 1, v.length()));
if (l == null) {
- log.error("Failed to parse timestamp from reviewer by email field: {}", v);
+ log.warn(
+ "Failed to parse timestamp of reviewer by email field from change {}: {}",
+ changeId.get(),
+ v);
continue;
}
Timestamp timestamp = new Timestamp(l);
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index 29c331f..31bf93f 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -119,7 +119,7 @@
throw new BadRequestException("username must match URL");
}
- if (!username.matches(ExternalId.USER_NAME_PATTERN_REGEX)) {
+ if (!ExternalId.isValidUsername(username)) {
throw new BadRequestException(
"Username '" + username + "' must contain only letters, numbers, _, - or .");
}
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index 224a014..f65b29f 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -190,7 +190,7 @@
}
private Optional<Account> createAccountByLdap(String user) throws IOException {
- if (!user.matches(ExternalId.USER_NAME_PATTERN_REGEX)) {
+ if (!ExternalId.isValidUsername(user)) {
return Optional.empty();
}
diff --git a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
index 5ecafd0..031284d 100644
--- a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
@@ -24,6 +24,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.testing.GerritBaseTests;
@@ -60,7 +61,7 @@
.containsExactly(
"REVIEWER,1", "REVIEWER,1," + t1.getTime(), "CC,2", "CC,2," + t2.getTime());
- assertThat(ChangeField.parseReviewerFieldValues(values)).isEqualTo(reviewers);
+ assertThat(ChangeField.parseReviewerFieldValues(new Change.Id(1), values)).isEqualTo(reviewers);
}
@Test
diff --git a/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java b/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java
index b9548bd..d88e09f 100644
--- a/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java
+++ b/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java
@@ -83,6 +83,32 @@
}
@Test
+ public void simpleInlineCommentsWithLink() {
+ MailMessage.Builder b = newMailMessageBuilder();
+ b.htmlContent(
+ newHtmlBody(
+ "Looks good to me",
+ "How about [1]? This would help IMHO.</div><div>[1] "
+ + "<a href=\"http://gerritcodereview.com\">http://gerritcodereview.com</a>",
+ null,
+ "Also have a comment here.",
+ null,
+ null,
+ null));
+
+ List<Comment> comments = defaultComments();
+ List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, CHANGE_URL);
+
+ assertThat(parsedComments).hasSize(3);
+ assertChangeMessage("Looks good to me", parsedComments.get(0));
+ assertInlineComment(
+ "How about [1]? This would help IMHO.\n\n[1] http://gerritcodereview.com",
+ parsedComments.get(1),
+ comments.get(1));
+ assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(4));
+ }
+
+ @Test
public void simpleFileComment() {
MailMessage.Builder b = newMailMessageBuilder();
b.htmlContent(
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 3f96a82..315a115 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 3f96a82f15d9d592a86a989874b160567cd66f53
+Subproject commit 315a11558913fa8f9c6d3b1723e45583b25afa1c
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 223730f..4672856 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 223730f10cf16f6a8ed2c0a3867371ca336d9ae7
+Subproject commit 467285664ebf8eb6f1e03ff13ebc706eee6d8662
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 2c42f31..9e65c40 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 2c42f316091726ba34a4f3809cb20197d8a46575
+Subproject commit 9e65c400d31b2f6e51c9d2a07dd4484011c5a713
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
index 2cfa0b2..8bceff1 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
@@ -35,19 +35,24 @@
:host {
display: inline-block;
}
- input {
- width: 25em;
+ input:not([type="checkbox"]),
+ gr-autocomplete,
+ iron-autogrow-textarea {
+ width: 100%;
+ }
+ .value {
+ width: 32em;
+ }
+ section {
+ align-items: center;
+ display: flex;
+ }
+ #description {
+ align-items: initial;
}
gr-autocomplete {
- border: none;
- float: right;
--gr-autocomplete: {
- border: 1px solid #d1d2d3;
- border-radius: 2px;
- font-size: var(--font-size-normal);
- height: 2em;
padding: 0 .15em;
- width: 20em;
}
}
</style>
@@ -65,40 +70,50 @@
</span>
</section>
<section>
- <span class="title">Enter topic for new change (optional)</span>
- <input
- is="iron-input"
- id="tagNameInput"
- maxlength="1024"
- bind-value="{{topic}}">
+ <span class="title">Enter topic for new change</span>
+ <span class="value">
+ <input
+ is="iron-input"
+ id="tagNameInput"
+ maxlength="1024"
+ placeholder="(optional)"
+ bind-value="{{topic}}">
+ </span>
</section>
- <section>
+ <section id="description">
<span class="title">Description</span>
- <iron-autogrow-textarea
- id="messageInput"
- class="message"
- autocomplete="on"
- rows="4"
- max-rows="15"
- bind-value="{{subject}}"
- placeholder="Insert the description of the change.">
- </iron-autogrow-textarea>
+ <span class="value">
+ <iron-autogrow-textarea
+ id="messageInput"
+ class="message"
+ autocomplete="on"
+ rows="4"
+ max-rows="15"
+ bind-value="{{subject}}"
+ placeholder="Insert the description of the change.">
+ </iron-autogrow-textarea>
+ </span>
</section>
<section>
- <span class="title">Options</span>
- <section>
- <label for="privateChangeCheckBox">Private Change</label>
+ <label
+ class="title"
+ for="privateChangeCheckBox">Private Change</label>
+ <span class="value">
<input
type="checkbox"
id="privateChangeCheckBox"
checked$="[[_repoConfig.private_by_default.inherited_value]]">
- </section>
- <section>
- <label for="wipChangeCheckBox">WIP Change</label>
+ </span>
+ </section>
+ <section>
+ <label
+ class="title"
+ for="wipChangeCheckBox">WIP Change</label>
+ <span class="value">
<input
type="checkbox"
id="wipChangeCheckBox">
- </section>
+ </span>
</section>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 6854f7a..a954559 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -52,7 +52,6 @@
ABANDON: 'abandon',
DELETE: '/',
DELETE_EDIT: 'deleteEdit',
- DONE_EDIT: 'doneEdit',
EDIT: 'edit',
IGNORE: 'ignore',
MOVE: 'move',
@@ -63,6 +62,7 @@
RESTORE: 'restore',
REVERT: 'revert',
REVIEWED: 'reviewed',
+ STOP_EDIT: 'stopEdit',
UNIGNORE: 'unignore',
UNREVIEWED: 'unreviewed',
WIP: 'wip',
@@ -159,11 +159,11 @@
__type: 'change',
};
- const DONE_EDIT = {
+ const STOP_EDIT = {
enabled: true,
- label: 'Done Editing',
+ label: 'Stop editing',
title: 'Stop editing this change',
- __key: 'doneEdit',
+ __key: 'stopEdit',
__primary: false,
__type: 'change',
};
@@ -569,14 +569,19 @@
delete this.actions.edit;
this.notifyPath('actions.edit');
}
- if (!changeActions.doneEdit) {
- this.set('actions.doneEdit', DONE_EDIT);
- }
} else {
if (!changeActions.edit) { this.set('actions.edit', EDIT); }
- if (changeActions.doneEdit) {
- delete this.actions.doneEdit;
- this.notifyPath('actions.doneEdit');
+ }
+ // Only show STOP_EDIT if edit mode is enabled, but no edit patch set
+ // is loaded.
+ if (editMode && !editPatchsetLoaded) {
+ if (!changeActions.stopEdit) {
+ this.set('actions.stopEdit', STOP_EDIT);
+ }
+ } else {
+ if (changeActions.stopEdit) {
+ delete this.actions.stopEdit;
+ this.notifyPath('actions.stopEdit');
}
}
}
@@ -831,8 +836,8 @@
case ChangeActions.EDIT:
this._handleEditTap();
break;
- case ChangeActions.DONE_EDIT:
- this._handleDoneEditTap();
+ case ChangeActions.STOP_EDIT:
+ this._handleStopEditTap();
break;
case ChangeActions.DELETE:
this._handleDeleteTap();
@@ -1316,8 +1321,8 @@
this.dispatchEvent(new CustomEvent('edit-tap', {bubbles: false}));
},
- _handleDoneEditTap() {
- this.dispatchEvent(new CustomEvent('done-edit-tap', {bubbles: false}));
+ _handleStopEditTap() {
+ this.dispatchEvent(new CustomEvent('stop-edit-tap', {bubbles: false}));
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index e76e494..a7a9636 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -437,7 +437,7 @@
assert.isOk(element.$$('gr-button[data-action-key="rebaseEdit"]'));
assert.isOk(element.$$('gr-button[data-action-key="deleteEdit"]'));
assert.isNotOk(element.$$('gr-button[data-action-key="edit"]'));
- assert.isOk(element.$$('gr-button[data-action-key="doneEdit"]'));
+ assert.isNotOk(element.$$('gr-button[data-action-key="stopEdit"]'));
});
test('edit patchset is loaded, does not need rebase', () => {
@@ -451,7 +451,7 @@
assert.isNotOk(element.$$('gr-button[data-action-key="rebaseEdit"]'));
assert.isOk(element.$$('gr-button[data-action-key="deleteEdit"]'));
assert.isNotOk(element.$$('gr-button[data-action-key="edit"]'));
- assert.isOk(element.$$('gr-button[data-action-key="doneEdit"]'));
+ assert.isNotOk(element.$$('gr-button[data-action-key="stopEdit"]'));
});
test('edit mode is loaded, no edit patchset', () => {
@@ -464,7 +464,7 @@
assert.isNotOk(element.$$('gr-button[data-action-key="rebaseEdit"]'));
assert.isNotOk(element.$$('gr-button[data-action-key="deleteEdit"]'));
assert.isNotOk(element.$$('gr-button[data-action-key="edit"]'));
- assert.isOk(element.$$('gr-button[data-action-key="doneEdit"]'));
+ assert.isOk(element.$$('gr-button[data-action-key="stopEdit"]'));
});
test('normal patch set', () => {
@@ -477,7 +477,7 @@
assert.isNotOk(element.$$('gr-button[data-action-key="rebaseEdit"]'));
assert.isNotOk(element.$$('gr-button[data-action-key="deleteEdit"]'));
assert.isOk(element.$$('gr-button[data-action-key="edit"]'));
- assert.isNotOk(element.$$('gr-button[data-action-key="doneEdit"]'));
+ assert.isNotOk(element.$$('gr-button[data-action-key="stopEdit"]'));
});
test('edit action', done => {
@@ -487,7 +487,7 @@
flushAsynchronousOperations();
assert.isNotOk(element.$$('gr-button[data-action-key="edit"]'));
- assert.isOk(element.$$('gr-button[data-action-key="doneEdit"]'));
+ assert.isOk(element.$$('gr-button[data-action-key="stopEdit"]'));
element.set('editMode', false);
flushAsynchronousOperations();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 66e4331..268a6a2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -367,7 +367,7 @@
edit-based-on-current-patch-set="[[hasEditBasedOnCurrentPatchSet(_allPatchSets)]]"
on-reload-change="_handleReloadChange"
on-edit-tap="_handleEditTap"
- on-done-edit-tap="_handleDoneEditTap"
+ on-stop-edit-tap="_handleStopEditTap"
on-download-tap="_handleOpenDownloadDialog"></gr-change-actions>
</div><!-- end commit actions -->
</div><!-- end header -->
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 5fdef79..2c49e6c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -49,6 +49,11 @@
NEW_MESSAGE: 'There are new messages on this change',
};
+ const DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
Polymer({
is: 'gr-change-view',
@@ -251,6 +256,7 @@
'shift+r': '_handleCapitalRKey',
'a': '_handleAKey',
'd': '_handleDKey',
+ 'm': '_handleMKey',
's': '_handleSKey',
'u': '_handleUKey',
'x': '_handleXKey',
@@ -311,6 +317,18 @@
});
},
+ _handleMKey(e) {
+ if (this.shouldSuppressKeyboardShortcut(e) ||
+ this.modifierPressed(e)) { return; }
+
+ e.preventDefault();
+ if (this.viewState.diffMode === DiffViewMode.SIDE_BY_SIDE) {
+ this.set('viewState.diffMode', DiffViewMode.UNIFIED);
+ } else {
+ this.set('viewState.diffMode', DiffViewMode.SIDE_BY_SIDE);
+ }
+ },
+
_handleEditCommitMessage(e) {
this._editingCommitMessage = true;
this.$.commitMessageEditor.focusTextarea();
@@ -1451,15 +1469,8 @@
Gerrit.Nav.navigateToChange(this._change, patchNum, null, true);
},
- /**
- * Navigate to the latest non-edit patch set.
- */
- _handleDoneEditTap() {
- let patchNum = this._patchRange.patchNum;
- if (this.patchNumEquals(patchNum, this.EDIT_NAME)) {
- patchNum = this.computeLatestPatchNum(this._allPatchSets);
- }
- Gerrit.Nav.navigateToChange(this._change, patchNum);
+ _handleStopEditTap() {
+ Gerrit.Nav.navigateToChange(this._change, this._patchRange.patchNum);
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 347100e..b7ae295 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -257,6 +257,21 @@
MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
assert.isTrue(stub.called);
});
+
+ test('m should toggle diff mode', () => {
+ sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
+ const e = {preventDefault: () => {}};
+ flushAsynchronousOperations();
+
+ // Initial state.
+ element.viewState.diffMode = 'SIDE_BY_SIDE';
+
+ element._handleMKey(e);
+ assert.equal(element.viewState.diffMode, 'UNIFIED_DIFF');
+
+ element._handleMKey(e);
+ assert.equal(element.viewState.diffMode, 'SIDE_BY_SIDE');
+ });
});
suite('reloading drafts', () => {
@@ -1535,42 +1550,20 @@
});
});
- suite('_handleDoneEditTap', () => {
- let fireDone;
-
- setup(() => {
- fireDone = () => {
- element.$.actions.dispatchEvent(new CustomEvent('done-edit-tap',
- {bubbles: false}));
- };
- sandbox.stub(element.$.metadata, '_computeShowLabelStatus');
- sandbox.stub(element.$.metadata, '_computeLabelNames');
- navigateToChangeStub.restore();
+ test('_handleStopEditTap', done => {
+ sandbox.stub(element.$.metadata, '_computeShowLabelStatus');
+ sandbox.stub(element.$.metadata, '_computeLabelNames');
+ navigateToChangeStub.restore();
+ sandbox.stub(element, 'computeLatestPatchNum').returns(1);
+ sandbox.stub(Gerrit.Nav, 'navigateToChange', (...args) => {
+ assert.equal(args.length, 2);
+ assert.equal(args[1], 1); // patchNum
+ done();
});
- test('edit patchset loaded', done => {
- sandbox.stub(element, 'computeLatestPatchNum').returns(1);
- sandbox.stub(Gerrit.Nav, 'navigateToChange', (...args) => {
- assert.equal(args.length, 2);
- assert.equal(args[1], 1); // patchNum
- done();
- });
-
- element._patchRange = {patchNum: element.EDIT_NAME};
- fireDone();
- });
-
- test('edit patchset loaded', done => {
- sandbox.stub(element, 'computeLatestPatchNum').returns(1);
- sandbox.stub(Gerrit.Nav, 'navigateToChange', (...args) => {
- assert.equal(args.length, 2);
- assert.equal(args[1], 1); // patchNum
- done();
- });
-
- element._patchRange = {patchNum: 1};
- fireDone();
- });
+ element._patchRange = {patchNum: 1};
+ element.$.actions.dispatchEvent(new CustomEvent('stop-edit-tap',
+ {bubbles: false}));
});
suite('plugin endpoints', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index c30f726..ddec441 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -379,6 +379,12 @@
</tr>
<tr>
<td>
+ <span class="key">m</span>
+ </td>
+ <td>Toggle unified/side-by-side diff</td>
+ </tr>
+ <tr>
+ <td>
<span class="key">c</span>
</td>
<td>Draft new comment</td>
@@ -453,6 +459,12 @@
</tr>
<tr>
<td>
+ <span class="key">m</span>
+ </td>
+ <td>Toggle unified/side-by-side diff</td>
+ </tr>
+ <tr>
+ <td>
<span class="key">c</span>
</td>
<td>Draft new comment</td>
@@ -486,12 +498,6 @@
<td><span class="key">,</span></td>
<td>Show diff preferences</td>
</tr>
- <tr>
- <td>
- <span class="key">v</span>
- </td>
- <td>Toggle Unified/Side-by-side diff</td>
- </tr>
</tbody>
</table>
</main>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index ab2077d..9b830cf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -76,7 +76,6 @@
margin-left: 1em;
}
</style>
-
<gr-overlay id="prefsOverlay" with-backdrop>
<div class="header">
Diff View Preferences
@@ -141,6 +140,14 @@
<input is="iron-input" type="checkbox" id="syntaxHighlightInput"
on-tap="_handleSyntaxHighlightTap">
</div>
+ <div class="pref">
+ <label for="automaticReviewInput">Automatically mark viewed files reviewed</label>
+ <input
+ is="iron-input"
+ id="automaticReviewInput"
+ type="checkbox"
+ on-tap="_handleAutomaticReviewTap">
+ </div>
</div>
<div class="actions">
<gr-button id="cancelButton" link on-tap="_handleCancel">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
index 185b047..1dcfc68 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
@@ -63,6 +63,7 @@
this.$.showTrailingWhitespaceInput.checked = prefs.show_whitespace_errors;
this.$.lineWrappingInput.checked = prefs.line_wrapping;
this.$.syntaxHighlightInput.checked = prefs.syntax_highlighting;
+ this.$.automaticReviewInput.checked = !prefs.manual_review;
},
_localPrefsChanged(changeRecord) {
@@ -93,6 +94,10 @@
this.set('_newPrefs.line_wrapping', Polymer.dom(e).rootTarget.checked);
},
+ _handleAutomaticReviewTap(e) {
+ this.set('_newPrefs.manual_review', !Polymer.dom(e).rootTarget.checked);
+ },
+
_handleSave(e) {
e.stopPropagation();
this.prefs = this._newPrefs;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index da95c3d..f1cc83e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -25,6 +25,11 @@
RIGHT: 'right',
};
+ const DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
Polymer({
is: 'gr-diff-view',
@@ -186,7 +191,7 @@
'a shift+a': '_handleAKey',
'u': '_handleUKey',
',': '_handleCommaKey',
- 'v': '_handleVKey',
+ 'm': '_handleMKey',
},
attached() {
@@ -426,15 +431,15 @@
this.$.diffPreferences.open();
},
- _handleVKey(e) {
+ _handleMKey(e) {
if (this.shouldSuppressKeyboardShortcut(e) ||
this.modifierPressed(e)) { return; }
e.preventDefault();
- if (this.changeViewState.diffMode=='SIDE_BY_SIDE') {
- this.set('changeViewState.diffMode', 'UNIFIED_DIFF');
+ if (this._getDiffViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+ this.set('changeViewState.diffMode', DiffViewMode.UNIFIED);
} else {
- this.set('changeViewState.diffMode', 'SIDE_BY_SIDE');
+ this.set('changeViewState.diffMode', DiffViewMode.SIDE_BY_SIDE);
}
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 8b6d82f..ae98fe9 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -798,6 +798,19 @@
assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
});
+ test('_handleMKey', () => {
+ sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
+ const e = {preventDefault: () => {}};
+ // Initial state.
+ assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
+
+ element._handleMKey(e);
+ assert.equal(element._getDiffViewMode(), 'UNIFIED_DIFF');
+
+ element._handleMKey(e);
+ assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
+ });
+
suite('_loadComments', () => {
test('empty', done => {
element._loadComments().then(() => {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index 866a62b..4fedf73 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -141,11 +141,17 @@
_getFileData(changeNum, path, patchNum) {
return this.$.restAPI.getFileContent(changeNum, path, patchNum)
.then(res => {
- if (!res.ok) { return; }
-
- this._type = res.type || '';
this._newContent = res.content || '';
this._content = res.content || '';
+
+ // A non-ok response may result if the file does not yet exist.
+ // The `type` field of the response is only valid when the file
+ // already exists.
+ if (res.ok && res.type) {
+ this._type = res.type;
+ } else {
+ this._type = '';
+ }
});
},
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 18ff1f5..ea87f56 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -242,9 +242,9 @@
// Ensure no data is set with a bad response.
return element._getFileData('1', 'test/path', 'edit').then(() => {
- assert.equal(element._newContent, 'initial');
- assert.equal(element._content, 'initial');
- assert.equal(element._type, 'initial');
+ assert.equal(element._newContent, '');
+ assert.equal(element._content, '');
+ assert.equal(element._type, '');
});
});
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 68b9b59..46f4649 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -78,13 +78,14 @@
footer {
color: var(--primary-text-color);
}
- gr-main-header.shadow {
- /* Make it obvious for shadow dom testing */
- border-bottom: 1px solid pink;
- }
gr-main-header {
background-color: var(--header-background-color);
padding: 0 var(--default-horizontal-margin);
+ border-bottom: 1px solid #ddd;
+ }
+ gr-main-header.shadow {
+ /* Make it obvious for shadow dom testing */
+ border-bottom: 1px solid pink;
}
footer {
background-color: var(--footer-background-color);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index cbb2287..6749437 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1418,9 +1418,15 @@
* @param {number|string} patchNum
*/
getFileContent(changeNum, path, patchNum) {
+ // 404s indicate the file does not exist yet in the revision, so suppress
+ // them.
+ const suppress404s = res => {
+ if (res && res.status !== 404) { this.fire('server-error', {res}); }
+ return res;
+ };
const promise = this.patchNumEquals(patchNum, this.EDIT_NAME) ?
this._getFileInChangeEdit(changeNum, path) :
- this._getFileInRevision(changeNum, path, patchNum);
+ this._getFileInRevision(changeNum, path, patchNum, suppress404s);
return promise.then(res => {
if (!res.ok) { return res; }
@@ -1439,12 +1445,13 @@
* @param {number|string} changeNum
* @param {string} path
* @param {number|string} patchNum
+ * @param {?function(?Response, string=)=} opt_errFn
*/
- _getFileInRevision(changeNum, path, patchNum) {
+ _getFileInRevision(changeNum, path, patchNum, opt_errFn) {
const e = `/files/${encodeURIComponent(path)}/content`;
const headers = {Accept: 'application/json'};
- return this.getChangeURLAndSend(changeNum, 'GET', patchNum, e, null, null,
- null, null, headers);
+ return this.getChangeURLAndSend(changeNum, 'GET', patchNum, e, null,
+ opt_errFn, null, null, headers);
},
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index efe70a9..352f759 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -1277,6 +1277,23 @@
return Promise.all([edit, normal]);
});
+ test('getFileContent suppresses 404s', done => {
+ const res = {status: 404};
+ const handler = e => {
+ assert.isFalse(e.detail.res.status === 404);
+ done();
+ };
+ element.addEventListener('server-error', handler);
+ sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve(res));
+ sandbox.stub(element, '_changeBaseURL').returns(Promise.resolve(''));
+ element.getFileContent('1', 'tst/path', '1').then(() => {
+ flushAsynchronousOperations();
+
+ res.status = 500;
+ element.getFileContent('1', 'tst/path', '1');
+ });
+ });
+
test('getChangeFilesAsSpeciallySortedArray is edit-sensitive', () => {
const fn = element.getChangeFilesAsSpeciallySortedArray.bind(element);
const getChangeFilesStub = sandbox.stub(element, 'getChangeFiles')