Merge "bazel: add rules for vulcanize & crisper, and use them in //polygerrit-ui/app."
diff --git a/.buckversion b/.buckversion
index efb68ecf..af38772 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-fd3105a0b62899f74662f4cdc156de6990bdc24c
+7b7817c48f30687781040b2b82ac9218d5c4eaa4
diff --git a/.gitignore b/.gitignore
index 815c5fa..c89cfb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
/.settings/org.maven.ide.eclipse.prefs
/.settings/org.eclipse.m2e.core.prefs
/.settings/org.eclipse.ltk.core.refactoring.prefs
+/.metadata
/test_site
/.idea
*.iml
diff --git a/BUILD b/BUILD
index 76e2177..4fa30f2 100644
--- a/BUILD
+++ b/BUILD
@@ -8,6 +8,14 @@
visibility = ['//visibility:public'],
)
+genrule(
+ name = "LICENSES",
+ srcs = ["//Documentation:licenses.txt"],
+ cmd = "cp $< $@",
+ outs = ["LICENSES.txt"],
+ visibility = ['//visibility:public'],
+)
+
pkg_war(name = 'gerrit')
pkg_war(name = 'headless', ui = None)
-#pkg_war(name = 'release', ui = 'ui_optdbg_r', context = ['//plugins:core'])
+pkg_war(name = 'release', ui = 'ui_optdbg_r', context = ['//plugins:core'])
diff --git a/Documentation/BUILD b/Documentation/BUILD
index c2acc9c..0542b5d 100644
--- a/Documentation/BUILD
+++ b/Documentation/BUILD
@@ -1,6 +1,46 @@
-
+load("//tools/bzl:asciidoc.bzl", "documentation_attributes")
+load("//tools/bzl:asciidoc.bzl", "genasciidoc")
+load("//tools/bzl:asciidoc.bzl", "genasciidoc_zip")
load("//tools/bzl:license.bzl", "license_map")
+exports_files([
+ "replace_macros.py",
+])
+
+filegroup(
+ name = "prettify_files",
+ srcs = [
+ ":prettify.min.css",
+ ":prettify.min.js",
+ ],
+)
+
+genrule(
+ name = "prettify_min_css",
+ srcs = ["//gerrit-prettify:src/main/resources/com/google/gerrit/prettify/client/prettify.css"],
+ cmd = "cp $< $@",
+ outs = ["prettify.min.css"],
+)
+
+genrule(
+ name = "prettify_min_js",
+ srcs = ["//gerrit-prettify:src/main/resources/com/google/gerrit/prettify/client/prettify.js"],
+ cmd = "cp $< $@",
+ outs = ["prettify.min.js"],
+)
+
+filegroup(
+ name = "resources",
+ srcs = glob([
+ "images/*.jpg",
+ "images/*.png",
+ ]) + [
+ ":prettify_files",
+ "//:LICENSES.txt",
+ ],
+ visibility = ['//visibility:public'],
+)
+
license_map(
name = "licenses",
targets = [
@@ -8,10 +48,11 @@
"//gerrit-gwtui:ui_module",
],
opts = ["--asciidoctor"],
+ visibility = ['//visibility:public'],
)
DOC_DIR = "Documentation"
-SRCS = glob(["*.txt"])
+SRCS = glob(["*.txt"]) + [":licenses.txt"]
genrule(
name = "index",
@@ -20,12 +61,38 @@
'--prefix "%s/" ' % DOC_DIR +
'--in-ext ".txt" ' +
'--out-ext ".html" ' +
- "$(SRCS) " +
- "$(location :licenses.txt)",
- tools = [
- ":licenses.txt",
- "//lib/asciidoctor:doc_indexer",
- ],
+ "$(SRCS)",
+ tools = ["//lib/asciidoctor:doc_indexer"],
srcs = SRCS,
outs = ["index.jar"],
)
+
+# For the same srcs, we can have multiple genasciidoc_zip rules, but only one
+# genasciidoc rule. Because multiple genasciidoc rules will have conflicting
+# output files.
+genasciidoc(
+ name = "Documentation",
+ srcs = SRCS,
+ attributes = documentation_attributes(),
+ backend = "html5",
+ visibility = ["//visibility:public"],
+)
+
+genasciidoc_zip(
+ name = "html",
+ srcs = SRCS,
+ attributes = documentation_attributes(),
+ backend = "html5",
+ directory = DOC_DIR,
+ visibility = ["//visibility:public"],
+)
+
+genasciidoc_zip(
+ name = "searchfree",
+ srcs = SRCS,
+ attributes = documentation_attributes(),
+ backend = "html5",
+ directory = DOC_DIR,
+ searchbox = False,
+ visibility = ["//visibility:public"],
+)
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 15409c6..f063b88 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -83,6 +83,36 @@
=== Plugins
+----
+ bazel build plugins:core
+----
+
+The output JAR files for individual plugins will be placed in:
+
+----
+ bazel-bin/plugins/<name>/<name>_deploy.jar
+----
+
+The JAR files will also be packaged in:
+
+----
+ bazel-genfiles/plugins/core.zip
+----
+
+To build a specific plugin:
+
+----
+ bazel build plugins/<name>:<name>_deploy.jar
+----
+
+The output JAR file will be be placed in:
+
+----
+ bazel-bin/plugins/<name>/<name>_deploy.jar
+----
+
+Note that when building an individual plugin, the `core.zip` package
+is not regenerated.
[[documentation]]
@@ -93,6 +123,9 @@
[[release]]
=== Gerrit Release WAR File
+----
+ bazel build release
+----
[[tests]]
== Running Unit Tests
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index f3938b3..ae02475 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -303,7 +303,7 @@
[[submittable]]
--
-* `SUBMITTABLE`: include the `submittable` field in link:#commit-info[CommitInfo],
+* `SUBMITTABLE`: include the `submittable` field in link:#change-info[ChangeInfo],
which can be used to tell if the change is reviewed and ready for submit.
--
@@ -3430,7 +3430,8 @@
(This assumes no non-fastforward pushes).
You need to give a parameter '?format=zip' or '?format=tar' to specify the
-format for the outer container.
+format for the outer container. It is always possible to use tgz, even if
+tgz is not in the list of allowed archive formats.
To make good use of this call, you would roughly need code as found at:
----
@@ -5578,7 +5579,9 @@
Allowed values are `DELETE`, `PUBLISH`, `PUBLISH_ALL_REVISIONS` and
`KEEP`. All values except `PUBLISH_ALL_REVISIONS` operate only on drafts
for a single revision. +
-If not set, the default is `DELETE`.
+Only `KEEP` is allowed when used in conjunction with `on_behalf_of`. +
+If not set, the default is `DELETE`, unless `on_behalf_of` is set, in
+which case the default is `KEEP` and any other value is disallowed.
|`notify` |optional|
Notify handling that defines to whom email notifications should be sent
after the review is stored. +
@@ -5709,6 +5712,8 @@
|`robot_id` ||The ID of the robot that generated this comment.
|`robot_run_id` ||An ID of the run of the robot.
|`url` |optional|URL to more information.
+|`properties` |optional|
+Robot specific properties as map that maps arbitrary keys to values.
|===========================
[[robot-comment-input]]
diff --git a/ReleaseNotes/BUILD b/ReleaseNotes/BUILD
new file mode 100644
index 0000000..9bf572e
--- /dev/null
+++ b/ReleaseNotes/BUILD
@@ -0,0 +1,27 @@
+load("//tools/bzl:asciidoc.bzl", "release_notes_attributes")
+load("//tools/bzl:asciidoc.bzl", "genasciidoc")
+load("//tools/bzl:asciidoc.bzl", "genasciidoc_zip")
+
+
+SRCS = glob(['*.txt'])
+
+
+genasciidoc(
+ name = 'ReleaseNotes',
+ srcs = SRCS,
+ attributes = release_notes_attributes(),
+ backend = 'html5',
+ searchbox = False,
+ resources = False,
+ visibility = ["//visibility:public"],
+)
+
+genasciidoc_zip(
+ name = "html",
+ srcs = SRCS,
+ attributes = release_notes_attributes(),
+ backend = 'html5',
+ searchbox = False,
+ resources = False,
+ visibility = ["//visibility:public"],
+)
diff --git a/contrib/git-push-review b/contrib/git-push-review
index aeea552..87eaa4c 100755
--- a/contrib/git-push-review
+++ b/contrib/git-push-review
@@ -72,7 +72,8 @@
opts = collections.defaultdict(list)
is_hashtag = lambda x: x.startswith('#')
opts['r'].extend(
- get_config('reviewer.' + r) for r in args.args if not is_hashtag(r))
+ (get_config('reviewer.' + r) or r)
+ for r in args.args if not is_hashtag(r))
opts['t'].extend(t[1:] for t in args.args if is_hashtag(t))
if args.topic:
opts['topic'].append(args.topic)
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index c35f82c..b77c41a 100644
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -182,14 +182,15 @@
def get_random_users(num_users):
- users = [(f, l) for f in FIRST_NAMES for l in LAST_NAMES][:num_users]
+ users = random.sample([(f, l) for f in FIRST_NAMES for l in LAST_NAMES],
+ num_users)
names = []
for u in users:
names.append({"firstname": u[0],
"lastname": u[1],
"name": u[0] + " " + u[1],
"username": u[0] + u[1],
- "email": u[0] + "." + u[1] + "@gmail.com",
+ "email": u[0] + "." + u[1] + "@gerritcodereview.com",
"http_password": "secret",
"groups": []})
return names
@@ -293,6 +294,7 @@
project_names = create_gerrit_projects(group_names)
for idx, u in enumerate(gerrit_users):
- create_change(u, project_names[4 * idx / len(gerrit_users)])
+ for _ in xrange(random.randint(1, 5)):
+ create_change(u, project_names[4 * idx / len(gerrit_users)])
main()
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index bce0b5a..114ef6a 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -14,6 +14,7 @@
package com.google.gerrit.acceptance;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.US_ASCII;
@@ -104,6 +105,7 @@
for (String n : groups) {
AccountGroup.NameKey k = new AccountGroup.NameKey(n);
AccountGroup g = groupCache.get(k);
+ checkArgument(g != null, "group not found: %s", n);
AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(id, g.getId()));
db.accountGroupMembers().insert(Collections.singleton(m));
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
index 669b991..e5182df 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance;
import com.google.common.base.CharMatcher;
+import com.google.gerrit.common.Nullable;
import org.apache.http.HttpHost;
import org.apache.http.client.fluent.Executor;
@@ -24,17 +25,20 @@
import java.net.URI;
public class HttpSession {
-
+ protected TestAccount account;
protected final String url;
private final Executor executor;
- public HttpSession(GerritServer server, TestAccount account) {
+ public HttpSession(GerritServer server, @Nullable TestAccount account) {
this.url = CharMatcher.is('/').trimTrailingFrom(server.getUrl());
URI uri = URI.create(url);
- this.executor = Executor
- .newInstance()
- .auth(new HttpHost(uri.getHost(), uri.getPort()),
+ this.executor = Executor.newInstance();
+ this.account = account;
+ if (account != null) {
+ executor.auth(
+ new HttpHost(uri.getHost(), uri.getPort()),
account.username, account.httpPassword);
+ }
}
public String url() {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 90ece46..689b2d0 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -18,6 +18,7 @@
import com.google.common.base.Preconditions;
import com.google.common.net.HttpHeaders;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.server.OutputFormat;
@@ -32,7 +33,7 @@
public class RestSession extends HttpSession {
- public RestSession(GerritServer server, TestAccount account) {
+ public RestSession(GerritServer server, @Nullable TestAccount account) {
super(server, account);
}
@@ -47,7 +48,7 @@
public RestResponse getWithHeader(String endPoint, Header header)
throws IOException {
- Request get = Request.Get(url + "/a" + endPoint);
+ Request get = Request.Get(getUrl(endPoint));
if (header != null) {
get.addHeader(header);
}
@@ -55,7 +56,7 @@
}
public RestResponse head(String endPoint) throws IOException {
- return execute(Request.Head(url + "/a" + endPoint));
+ return execute(Request.Head(getUrl(endPoint)));
}
public RestResponse put(String endPoint) throws IOException {
@@ -73,7 +74,7 @@
public RestResponse putWithHeader(String endPoint, Header header,
Object content) throws IOException {
- Request put = Request.Put(url + "/a" + endPoint);
+ Request put = Request.Put(getUrl(endPoint));
if (header != null) {
put.addHeader(header);
}
@@ -88,7 +89,7 @@
public RestResponse putRaw(String endPoint, RawInput stream) throws IOException {
Preconditions.checkNotNull(stream);
- Request put = Request.Put(url + "/a" + endPoint);
+ Request put = Request.Put(getUrl(endPoint));
put.addHeader(new BasicHeader("Content-Type", stream.getContentType()));
put.body(new BufferedHttpEntity(
new InputStreamEntity(
@@ -102,7 +103,15 @@
}
public RestResponse post(String endPoint, Object content) throws IOException {
- Request post = Request.Post(url + "/a" + endPoint);
+ return postWithHeader(endPoint, content, null);
+ }
+
+ public RestResponse postWithHeader(String endPoint, Object content,
+ Header header) throws IOException {
+ Request post = Request.Post(getUrl(endPoint));
+ if (header != null) {
+ post.addHeader(header);
+ }
if (content != null) {
post.addHeader(new BasicHeader("Content-Type", "application/json"));
post.body(new StringEntity(
@@ -113,6 +122,10 @@
}
public RestResponse delete(String endPoint) throws IOException {
- return execute(Request.Delete(url + "/a" + endPoint));
+ return execute(Request.Delete(getUrl(endPoint)));
+ }
+
+ private String getUrl(String endPoint) {
+ return url + (account != null ? "/a" : "") + endPoint;
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 2475efa..3dd1ff3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -415,37 +415,6 @@
}
@Test
- public void voteOnBehalfOf() throws Exception {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- LabelType codeReviewType = Util.codeReview();
- String forCodeReviewAs = Permission.forLabelAs(codeReviewType.getName());
- String heads = "refs/heads/*";
- AccountGroup.UUID owner =
- SystemGroupBackend.getGroup(CHANGE_OWNER).getUUID();
- Util.allow(cfg, forCodeReviewAs, -1, 1, owner, heads);
- saveProjectConfig(project, cfg);
-
- PushOneCommit.Result r = createChange();
- RevisionApi revision = gApi.changes()
- .id(r.getChangeId())
- .current();
-
- ReviewInput in = ReviewInput.recommend();
- in.onBehalfOf = user.id.toString();
- revision.review(in);
-
- ChangeInfo c = gApi.changes()
- .id(r.getChangeId())
- .get();
-
- LabelInfo codeReview = c.labels.get("Code-Review");
- assertThat(codeReview.all).hasSize(1);
- ApprovalInfo approval = codeReview.all.get(0);
- assertThat(approval._accountId).isEqualTo(user.id.get());
- assertThat(approval.value).isEqualTo(1);
- }
-
- @Test
public void rebaseUpToDateChange() throws Exception {
PushOneCommit.Result r = createChange();
exception.expect(ResourceConflictException.class);
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 545be3e..7f5c6bc 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
@@ -21,7 +21,6 @@
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.junit.Assert.fail;
@@ -31,8 +30,6 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
@@ -40,7 +37,6 @@
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.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
@@ -51,23 +47,17 @@
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.change.GetRevisionActions;
import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
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.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -87,13 +77,6 @@
@Inject
private GetRevisionActions getRevisionActions;
- private TestAccount admin2;
-
- @Before
- public void setUp() throws Exception {
- admin2 = accounts.admin2();
- }
-
@Test
public void reviewTriplet() throws Exception {
PushOneCommit.Result r = createChange();
@@ -143,70 +126,6 @@
.isEqualTo(ChangeStatus.MERGED);
}
- private void allowSubmitOnBehalfOf() throws Exception {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- Util.allow(cfg,
- Permission.SUBMIT_AS,
- SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID(),
- "refs/heads/*");
- saveProjectConfig(project, cfg);
- }
-
- @Test
- public void submitOnBehalfOf() throws Exception {
- allowSubmitOnBehalfOf();
- PushOneCommit.Result r = createChange();
- String changeId = project.get() + "~master~" + r.getChangeId();
- gApi.changes()
- .id(changeId)
- .current()
- .review(ReviewInput.approve());
- SubmitInput in = new SubmitInput();
- in.onBehalfOf = admin2.email;
- gApi.changes()
- .id(changeId)
- .current()
- .submit(in);
- assertThat(gApi.changes().id(changeId).get().status)
- .isEqualTo(ChangeStatus.MERGED);
- }
-
- @Test
- public void submitOnBehalfOfInvalidUser() throws Exception {
- allowSubmitOnBehalfOf();
- PushOneCommit.Result r = createChange();
- String changeId = project.get() + "~master~" + r.getChangeId();
- gApi.changes()
- .id(changeId)
- .current()
- .review(ReviewInput.approve());
- SubmitInput in = new SubmitInput();
- in.onBehalfOf = "doesnotexist";
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Account Not Found: doesnotexist");
- gApi.changes()
- .id(changeId)
- .current()
- .submit(in);
- }
-
- @Test
- public void submitOnBehalfOfNotPermitted() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes()
- .id(project.get() + "~master~" + r.getChangeId())
- .current()
- .review(ReviewInput.approve());
- SubmitInput in = new SubmitInput();
- in.onBehalfOf = admin2.email;
- exception.expect(AuthException.class);
- exception.expectMessage("submit on behalf of not permitted");
- gApi.changes()
- .id(project.get() + "~master~" + r.getChangeId())
- .current()
- .submit(in);
- }
-
@Test
public void deleteDraft() throws Exception {
PushOneCommit.Result r = createDraft();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index 064b206..7aa5876 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -76,6 +76,31 @@
}
@Test
+ public void noOptionalFields() throws Exception {
+ assume().that(notesMigration.enabled()).isTrue();
+
+ PushOneCommit.Result r = createChange();
+ RobotCommentInput in = createRobotCommentInputWithMandatoryFields();
+ ReviewInput reviewInput = new ReviewInput();
+ Map<String, List<RobotCommentInput>> robotComments = new HashMap<>();
+ robotComments.put(in.path, Collections.singletonList(in));
+ reviewInput.robotComments = robotComments;
+ reviewInput.message = "comment test";
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(reviewInput);
+
+ Map<String, List<RobotCommentInfo>> out = gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .robotComments();
+ assertThat(out).hasSize(1);
+ RobotCommentInfo comment = Iterables.getOnlyElement(out.get(in.path));
+ assertRobotComment(comment, in, false);
+ }
+
+ @Test
public void robotCommentsNotSupported() throws Exception {
assume().that(notesMigration.enabled()).isFalse();
@@ -95,17 +120,25 @@
.review(reviewInput);
}
- private RobotCommentInput createRobotCommentInput() {
+ private RobotCommentInput createRobotCommentInputWithMandatoryFields() {
RobotCommentInput in = new RobotCommentInput();
in.robotId = "happyRobot";
in.robotRunId = "1";
- in.url = "http://www.happy-robot.com";
in.line = 1;
in.message = "nit: trailing whitespace";
in.path = FILE_NAME;
return in;
}
+ private RobotCommentInput createRobotCommentInput() {
+ RobotCommentInput in = createRobotCommentInputWithMandatoryFields();
+ in.url = "http://www.happy-robot.com";
+ in.properties = new HashMap<>();
+ in.properties.put("key1", "value1");
+ in.properties.put("key2", "value2");
+ return in;
+ }
+
private void assertRobotComment(RobotCommentInfo c,
RobotCommentInput expected) {
assertRobotComment(c, expected, true);
@@ -116,6 +149,7 @@
assertThat(c.robotId).isEqualTo(expected.robotId);
assertThat(c.robotRunId).isEqualTo(expected.robotRunId);
assertThat(c.url).isEqualTo(expected.url);
+ assertThat(c.properties).isEqualTo(expected.properties);
assertThat(c.line).isEqualTo(expected.line);
assertThat(c.message).isEqualTo(expected.message);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
new file mode 100644
index 0000000..c1f4237
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -0,0 +1,656 @@
+// Copyright (C) 2016 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.acceptance.rest.account;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+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.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.api.groups.GroupInput;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.RobotComment;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.CommentsUtil;
+import com.google.gerrit.server.account.AccountControl;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.Util;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ImpersonationIT extends AbstractDaemonTest {
+ @Inject
+ private AccountControl.Factory accountControlFactory;
+
+ @Inject
+ private ApprovalsUtil approvalsUtil;
+
+ @Inject
+ private ChangeMessagesUtil cmUtil;
+
+ @Inject
+ private CommentsUtil commentsUtil;
+
+ private RestSession anonRestSession;
+ private TestAccount admin2;
+ private GroupInfo newGroup;
+
+ @Before
+ public void setUp() throws Exception {
+ anonRestSession = new RestSession(server, null);
+ admin2 = accounts.admin2();
+ GroupInput gi = new GroupInput();
+ gi.name = name("New-Group");
+ gi.members = ImmutableList.of(user.id.toString());
+ newGroup = gApi.groups().create(gi).get();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ removeRunAs();
+ }
+
+ @Test
+ public void voteOnBehalfOf() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = ReviewInput.recommend();
+ in.onBehalfOf = user.id.toString();
+ in.message = "Message on behalf of";
+ revision.review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(
+ r.getChange().approvals().values());
+ assertThat(psa.getPatchSetId().get()).isEqualTo(1);
+ assertThat(psa.getLabel()).isEqualTo("Code-Review");
+ assertThat(psa.getAccountId()).isEqualTo(user.id);
+ assertThat(psa.getValue()).isEqualTo(1);
+ assertThat(psa.getRealAccountId()).isEqualTo(admin.id);
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(db, cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(user.id);
+ assertThat(m.getRealAuthor()).isEqualTo(admin.id);
+ }
+
+ @Test
+ public void voteOnBehalfOfRequiresLabel() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.message = "Message on behalf of";
+
+ exception.expect(AuthException.class);
+ exception.expectMessage(
+ "label required to post review on behalf of \"" + in.onBehalfOf + '"');
+ revision.review(in);
+ }
+
+ @Test
+ public void voteOnBehalfOfInvalidLabel() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.strictLabels = true;
+ in.label("Not-A-Label", 5);
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage(
+ "label \"Not-A-Label\" is not a configured label");
+ revision.review(in);
+ }
+
+ @Test
+ public void voteOnBehalfOfInvalidLabelIgnoredWithoutStrictLabels()
+ throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.strictLabels = false;
+ in.label("Code-Review", 1);
+ in.label("Not-A-Label", 5);
+
+ revision.review(in);
+
+ assertThat(gApi.changes().id(r.getChangeId()).get().labels)
+ .doesNotContainKey("Not-A-Label");
+ }
+
+ @Test
+ public void voteOnBehalfOfLabelNotPermitted() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ LabelType verified = Util.verified();
+ cfg.getLabelSections().put(verified.getName(), verified);
+ saveProjectConfig(project, cfg);
+
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Verified", 1);
+
+ exception.expect(AuthException.class);
+ exception.expectMessage(
+ "not permitted to modify label \"Verified\" on behalf of \""
+ + in.onBehalfOf + '"');
+ revision.review(in);
+ }
+
+ @Test
+ public void voteOnBehalfOfWithComment() throws Exception {
+ testVoteOnBehalfOfWithComment();
+ }
+
+ @GerritConfig(name = "notedb.writeJson", value = "true")
+ @Test
+ public void voteOnBehalfOfWithCommentWritingJson() throws Exception {
+ assume().that(notesMigration.readChanges()).isTrue();
+ testVoteOnBehalfOfWithComment();
+ }
+
+ private void testVoteOnBehalfOfWithComment() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Code-Review", 1);
+ CommentInput ci = new CommentInput();
+ ci.path = Patch.COMMIT_MSG;
+ ci.side = Side.REVISION;
+ ci.line = 1;
+ ci.message = "message";
+ in.comments = ImmutableMap.of(ci.path, ImmutableList.of(ci));
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(
+ r.getChange().approvals().values());
+ assertThat(psa.getPatchSetId().get()).isEqualTo(1);
+ assertThat(psa.getLabel()).isEqualTo("Code-Review");
+ assertThat(psa.getAccountId()).isEqualTo(user.id);
+ assertThat(psa.getValue()).isEqualTo(1);
+ assertThat(psa.getRealAccountId()).isEqualTo(admin.id);
+
+ ChangeData cd = r.getChange();
+ Comment c = Iterables.getOnlyElement(
+ commentsUtil.publishedByChange(db, cd.notes()));
+ assertThat(c.message).isEqualTo(ci.message);
+ assertThat(c.author.getId()).isEqualTo(user.id);
+ assertThat(c.getRealAuthor().getId()).isEqualTo(admin.id);
+ }
+
+ @GerritConfig(name = "notedb.writeJson", value = "true")
+ @Test
+ public void voteOnBehalfOfWithRobotComment() throws Exception {
+ assume().that(notesMigration.readChanges()).isTrue();
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Code-Review", 1);
+ RobotCommentInput ci = new RobotCommentInput();
+ ci.robotId = "my-robot";
+ ci.robotRunId = "abcd1234";
+ ci.path = Patch.COMMIT_MSG;
+ ci.side = Side.REVISION;
+ ci.line = 1;
+ ci.message = "message";
+ in.robotComments = ImmutableMap.of(ci.path, ImmutableList.of(ci));
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ ChangeData cd = r.getChange();
+ RobotComment c = Iterables.getOnlyElement(
+ commentsUtil.robotCommentsByChange(cd.notes()));
+ assertThat(c.message).isEqualTo(ci.message);
+ assertThat(c.robotId).isEqualTo(ci.robotId);
+ assertThat(c.robotRunId).isEqualTo(ci.robotRunId);
+ assertThat(c.author.getId()).isEqualTo(user.id);
+ assertThat(c.getRealAuthor().getId()).isEqualTo(admin.id);
+ }
+
+ @Test
+ public void voteOnBehalfOfCannotModifyDrafts() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+
+ setApiUser(user);
+ DraftInput di = new DraftInput();
+ di.path = Patch.COMMIT_MSG;
+ di.side = Side.REVISION;
+ di.line = 1;
+ di.message = "message";
+ gApi.changes().id(r.getChangeId()).current().createDraft(di);
+
+ setApiUser(admin);
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Code-Review", 1);
+ in.drafts = DraftHandling.PUBLISH;
+
+ exception.expect(AuthException.class);
+ exception.expectMessage("not allowed to modify other user's drafts");
+ gApi.changes().id(r.getChangeId()).current().review(in);
+ }
+
+ @Test
+ public void voteOnBehalfOfMissingUser() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = "doesnotexist";
+ in.label("Code-Review", 1);
+
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Account Not Found: doesnotexist");
+ revision.review(in);
+ }
+
+ @Test
+ public void voteOnBehalfOfFailsWhenUserCannotSeeDestinationRef()
+ throws Exception {
+ blockRead(newGroup);
+
+ allowCodeReviewOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Code-Review", 1);
+
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage(
+ "on_behalf_of account " + user.id + " cannot see destination ref");
+ revision.review(in);
+ }
+
+ @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
+ @Test
+ public void voteOnBehalfOfInvisibleUserNotAllowed() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ setApiUser(accounts.user2());
+ assertThat(accountControlFactory.get().canSee(user.id)).isFalse();
+
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes()
+ .id(r.getChangeId())
+ .current();
+
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.label("Code-Review", 1);
+
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Account Not Found: " + in.onBehalfOf);
+ revision.review(in);
+ }
+
+ @Test
+ public void submitOnBehalfOf() throws Exception {
+ allowSubmitOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = admin2.email;
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+
+ ChangeData cd = r.getChange();
+ assertThat(cd.change().getStatus()).isEqualTo(Change.Status.MERGED);
+ PatchSetApproval submitter = approvalsUtil.getSubmitter(
+ db, cd.notes(), cd.change().currentPatchSetId());
+ assertThat(submitter.getAccountId()).isEqualTo(admin2.id);
+ assertThat(submitter.getRealAccountId()).isEqualTo(admin.id);
+ }
+
+ @Test
+ public void submitOnBehalfOfInvalidUser() throws Exception {
+ allowSubmitOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = "doesnotexist";
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Account Not Found: doesnotexist");
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+ }
+
+ @Test
+ public void submitOnBehalfOfNotPermitted() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes()
+ .id(project.get() + "~master~" + r.getChangeId())
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = admin2.email;
+ exception.expect(AuthException.class);
+ exception.expectMessage("submit on behalf of not permitted");
+ gApi.changes()
+ .id(project.get() + "~master~" + r.getChangeId())
+ .current()
+ .submit(in);
+ }
+
+ @Test
+ public void submitOnBehalfOfFailsWhenUserCannotSeeDestinationRef()
+ throws Exception {
+ blockRead(newGroup);
+
+ allowSubmitOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = user.email;
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage(
+ "on_behalf_of account " + user.id + " cannot see destination ref");
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+ }
+
+ @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
+ @Test
+ public void submitOnBehalfOfInvisibleUserNotAllowed() throws Exception {
+ allowSubmitOnBehalfOf();
+ setApiUser(accounts.user2());
+ assertThat(accountControlFactory.get().canSee(user.id)).isFalse();
+
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = user.email;
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Account Not Found: " + in.onBehalfOf);
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+ }
+
+ @Test
+ public void runAsValidUser() throws Exception {
+ allowRunAs();
+ RestResponse res =
+ adminRestSession.getWithHeader("/accounts/self", runAsHeader(user.id));
+ res.assertOK();
+ AccountInfo account =
+ newGson().fromJson(res.getEntityContent(), AccountInfo.class);
+ assertThat(account._accountId).isEqualTo(user.id.get());
+ }
+
+ @GerritConfig(name = "auth.enableRunAs", value = "false")
+ @Test
+ public void runAsDisabledByConfig() throws Exception {
+ allowRunAs();
+ RestResponse res =
+ adminRestSession.getWithHeader("/changes/", runAsHeader(user.id));
+ res.assertForbidden();
+ assertThat(res.getEntityContent())
+ .isEqualTo("X-Gerrit-RunAs disabled by auth.enableRunAs = false");
+ }
+
+ @Test
+ public void runAsNotPermitted() throws Exception {
+ RestResponse res =
+ adminRestSession.getWithHeader("/changes/", runAsHeader(user.id));
+ res.assertForbidden();
+ assertThat(res.getEntityContent())
+ .isEqualTo("not permitted to use X-Gerrit-RunAs");
+ }
+
+ @Test
+ public void runAsNeverPermittedForAnonymousUsers() throws Exception {
+ allowRunAs();
+ RestResponse res =
+ anonRestSession.getWithHeader("/changes/", runAsHeader(user.id));
+ res.assertForbidden();
+ assertThat(res.getEntityContent())
+ .isEqualTo("not permitted to use X-Gerrit-RunAs");
+ }
+
+ @Test
+ public void runAsInvalidUser() throws Exception {
+ allowRunAs();
+ RestResponse res = adminRestSession.getWithHeader(
+ "/changes/", runAsHeader("doesnotexist"));
+ res.assertForbidden();
+ assertThat(res.getEntityContent())
+ .isEqualTo("no account matches X-Gerrit-RunAs");
+ }
+
+ @Test
+ public void voteUsingRunAsAvoidsRestrictionsOfOnBehalfOf() throws Exception {
+ allowRunAs();
+ PushOneCommit.Result r = createChange();
+
+ setApiUser(user);
+ DraftInput di = new DraftInput();
+ di.path = Patch.COMMIT_MSG;
+ di.side = Side.REVISION;
+ di.line = 1;
+ di.message = "inline comment";
+ gApi.changes().id(r.getChangeId()).current().createDraft(di);
+ setApiUser(admin);
+
+ // Things that aren't allowed with on_behalf_of:
+ // - no labels.
+ // - publish other user's drafts.
+ ReviewInput in = new ReviewInput();
+ in.message = "message";
+ in.drafts = DraftHandling.PUBLISH;
+ RestResponse res = adminRestSession.postWithHeader(
+ "/changes/" + r.getChangeId() + "/revisions/current/review", in,
+ runAsHeader(user.id));
+ res.assertOK();
+
+ ChangeMessageInfo m = Iterables.getLast(
+ gApi.changes().id(r.getChangeId()).get().messages);
+ assertThat(m.message).endsWith(in.message);
+ assertThat(m.author._accountId).isEqualTo(user.id.get());
+
+ CommentInfo c = Iterables.getOnlyElement(
+ gApi.changes().id(r.getChangeId()).comments().get(di.path));
+ assertThat(c.author._accountId).isEqualTo(user.id.get());
+ assertThat(c.message).isEqualTo(di.message);
+
+ setApiUser(user);
+ assertThat(gApi.changes().id(r.getChangeId()).drafts()).isEmpty();
+ }
+
+ @Test
+ public void runAsWithOnBehalfOf() throws Exception {
+ // - Has the same restrictions as on_behalf_of (e.g. requires labels).
+ // - Takes the effective user from on_behalf_of (user).
+ // - Takes the real user from the real caller, not the intermediate
+ // X-Gerrit-RunAs user (user2).
+ allowRunAs();
+ allowCodeReviewOnBehalfOf();
+ TestAccount user2 = accounts.user2();
+
+ PushOneCommit.Result r = createChange();
+ ReviewInput in = new ReviewInput();
+ in.onBehalfOf = user.id.toString();
+ in.message = "Message on behalf of";
+
+ String endpoint =
+ "/changes/" + r.getChangeId() + "/revisions/current/review";
+ RestResponse res =
+ adminRestSession.postWithHeader(endpoint, in, runAsHeader(user2.id));
+ res.assertForbidden();
+ assertThat(res.getEntityContent()).isEqualTo(
+ "label required to post review on behalf of \"" + in.onBehalfOf + '"');
+
+ in.label("Code-Review", 1);
+ adminRestSession.postWithHeader(endpoint, in, runAsHeader(user2.id))
+ .assertOK();
+
+ PatchSetApproval psa = Iterables.getOnlyElement(
+ r.getChange().approvals().values());
+ assertThat(psa.getPatchSetId().get()).isEqualTo(1);
+ assertThat(psa.getLabel()).isEqualTo("Code-Review");
+ assertThat(psa.getAccountId()).isEqualTo(user.id);
+ assertThat(psa.getValue()).isEqualTo(1);
+ assertThat(psa.getRealAccountId()).isEqualTo(admin.id); // not user2
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(db, cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(user.id);
+ assertThat(m.getRealAuthor()).isEqualTo(admin.id); // not user2
+ }
+
+ private void allowCodeReviewOnBehalfOf() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ LabelType codeReviewType = Util.codeReview();
+ String forCodeReviewAs = Permission.forLabelAs(codeReviewType.getName());
+ String heads = "refs/heads/*";
+ AccountGroup.UUID uuid =
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
+ Util.allow(cfg, forCodeReviewAs, -1, 1, uuid, heads);
+ saveProjectConfig(project, cfg);
+ }
+
+ private void allowSubmitOnBehalfOf() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ String heads = "refs/heads/*";
+ AccountGroup.UUID uuid =
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
+ Util.allow(cfg, Permission.SUBMIT_AS, uuid, heads);
+ Util.allow(cfg, Permission.SUBMIT, uuid, heads);
+ LabelType codeReviewType = Util.codeReview();
+ Util.allow(cfg, Permission.forLabel(codeReviewType.getName()),
+ -2, 2, uuid, heads);
+ saveProjectConfig(project, cfg);
+ }
+
+ private void blockRead(GroupInfo group) throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.block(
+ cfg, Permission.READ, new AccountGroup.UUID(group.id), "refs/heads/master");
+ saveProjectConfig(project, cfg);
+ }
+
+ private void allowRunAs() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ Util.allow(cfg, GlobalCapability.RUN_AS,
+ SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID());
+ saveProjectConfig(allProjects, cfg);
+ }
+
+ private void removeRunAs() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ Util.remove(cfg, GlobalCapability.RUN_AS,
+ SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID());
+ saveProjectConfig(allProjects, cfg);
+ }
+
+ private static Header runAsHeader(Object user) {
+ return new BasicHeader("X-Gerrit-RunAs", user.toString());
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index a30674f..dcdce92 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
@@ -25,14 +26,18 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.common.data.GlobalCapability;
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.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
@@ -41,6 +46,7 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -54,8 +60,10 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.RepoRefCache;
import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.ChangeBundle;
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -63,6 +71,7 @@
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
+import com.google.gerrit.server.project.Util;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.NoteDbChecker;
import com.google.gerrit.testutil.NoteDbMode;
@@ -72,6 +81,8 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -85,6 +96,7 @@
import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -971,6 +983,94 @@
checker.rebuildAndCheckChanges(id);
}
+ @Test
+ public void rebuildEntitiesCreatedByImpersonation() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getPatchSetId().getParentKey();
+ PatchSet.Id psId = new PatchSet.Id(id, 1);
+ String prefix = "/changes/" + id + "/revisions/current/";
+
+ // For each of the entities that have a real user field, create one entity
+ // without impersonation and one with.
+ CommentInput ci = new CommentInput();
+ ci.path = Patch.COMMIT_MSG;
+ ci.side = Side.REVISION;
+ ci.line = 1;
+ ci.message = "comment without impersonation";
+ ReviewInput ri = new ReviewInput();
+ ri.label("Code-Review", -1);
+ ri.message = "message without impersonation";
+ ri.drafts = DraftHandling.KEEP;
+ ri.comments = ImmutableMap.of(ci.path, ImmutableList.of(ci));
+ userRestSession.post(prefix + "review", ri).assertOK();
+
+ DraftInput di = new DraftInput();
+ di.path = Patch.COMMIT_MSG;
+ di.side = Side.REVISION;
+ di.line = 1;
+ di.message = "draft without impersonation";
+ userRestSession.put(prefix + "drafts", di).assertCreated();
+
+ allowRunAs();
+ try {
+ Header runAs = new BasicHeader("X-Gerrit-RunAs", user.id.toString());
+ ci.message = "comment with impersonation";
+ ri.message = "message with impersonation";
+ ri.label("Code-Review", 1);
+ adminRestSession.postWithHeader(prefix + "review", ri, runAs).assertOK();
+
+ di.message = "draft with impersonation";
+ adminRestSession.putWithHeader(prefix + "drafts", runAs, di)
+ .assertCreated();
+ } finally {
+ removeRunAs();
+ }
+
+ List<ChangeMessage> msgs =
+ Ordering.natural().onResultOf(ChangeMessage::getWrittenOn)
+ .sortedCopy(db.changeMessages().byChange(id));
+ assertThat(msgs).hasSize(3);
+ assertThat(msgs.get(1).getMessage())
+ .endsWith("message without impersonation");
+ assertThat(msgs.get(1).getAuthor()).isEqualTo(user.id);
+ assertThat(msgs.get(1).getRealAuthor()).isEqualTo(user.id);
+ assertThat(msgs.get(2).getMessage()).endsWith("message with impersonation");
+ assertThat(msgs.get(2).getAuthor()).isEqualTo(user.id);
+ assertThat(msgs.get(2).getRealAuthor()).isEqualTo(admin.id);
+
+ List<PatchSetApproval> psas = db.patchSetApprovals().byChange(id).toList();
+ assertThat(psas).hasSize(1);
+ assertThat(psas.get(0).getLabel()).isEqualTo("Code-Review");
+ assertThat(psas.get(0).getValue()).isEqualTo(1);
+ assertThat(psas.get(0).getAccountId()).isEqualTo(user.id);
+ assertThat(psas.get(0).getRealAccountId()).isEqualTo(admin.id);
+
+ Ordering<PatchLineComment> commentOrder =
+ Ordering.natural().onResultOf(PatchLineComment::getWrittenOn);
+ List<PatchLineComment> drafts = commentOrder.sortedCopy(
+ db.patchComments().draftByPatchSetAuthor(psId, user.id));
+ assertThat(drafts).hasSize(2);
+ assertThat(drafts.get(0).getMessage())
+ .isEqualTo("draft without impersonation");
+ assertThat(drafts.get(0).getAuthor()).isEqualTo(user.id);
+ assertThat(drafts.get(0).getRealAuthor()).isEqualTo(user.id);
+ assertThat(drafts.get(1).getMessage())
+ .isEqualTo("draft with impersonation");
+ assertThat(drafts.get(1).getAuthor()).isEqualTo(user.id);
+ assertThat(drafts.get(1).getRealAuthor()).isEqualTo(admin.id);
+
+ List<PatchLineComment> pub = commentOrder.sortedCopy(
+ db.patchComments().publishedByPatchSet(psId));
+ assertThat(pub).hasSize(2);
+ assertThat(pub.get(0).getMessage())
+ .isEqualTo("comment without impersonation");
+ assertThat(pub.get(0).getAuthor()).isEqualTo(user.id);
+ assertThat(pub.get(0).getRealAuthor()).isEqualTo(user.id);
+ assertThat(pub.get(1).getMessage()).isEqualTo("comment with impersonation");
+ assertThat(pub.get(1).getAuthor()).isEqualTo(user.id);
+ assertThat(pub.get(1).getRealAuthor()).isEqualTo(admin.id);
+ }
+
private void assertChangesReadOnly(RestApiException e) throws Exception {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(UpdateException.class);
@@ -1086,4 +1186,19 @@
ReviewDb db = dbProvider.get();
return ReviewDbUtil.unwrapDb(db);
}
+
+ private void allowRunAs() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ Util.allow(cfg, GlobalCapability.RUN_AS,
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
+ saveProjectConfig(allProjects, cfg);
+ }
+
+ private void removeRunAs() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ Util.remove(cfg, GlobalCapability.RUN_AS,
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
+ saveProjectConfig(allProjects, cfg);
+ }
+
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index 8f1e283..472559b 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -49,8 +49,11 @@
/**
* How to process draft comments already in the database that were not also
* described in this input request.
+ * <p>
+ * Defaults to DELETE, unless {@link #onBehalfOf} is set, in which case it
+ * defaults to KEEP and any other value is disallowed.
*/
- public DraftHandling drafts = DraftHandling.DELETE;
+ public DraftHandling drafts;
/** Who to send email notifications to after review is stored. */
public NotifyHandling notify = NotifyHandling.ALL;
@@ -99,6 +102,7 @@
public String robotId;
public String robotRunId;
public String url;
+ public Map<String, String> properties;
}
public ReviewInput message(String msg) {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
index a6b7593..9028a1d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
@@ -14,8 +14,11 @@
package com.google.gerrit.extensions.common;
+import java.util.Map;
+
public class RobotCommentInfo extends CommentInfo {
public String robotId;
public String robotRunId;
public String url;
+ public Map<String, String> properties;
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
index 6c820a8..a33f605 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
@@ -30,7 +30,7 @@
* Ported from JavaScript in {@code com.google.gwt.user.UserAgent.gwt.xml}.
*/
public class UserAgentRule {
- private static final Pattern msie = compile(".*msie ([0-9]+)\\.([0-9]+).*");
+ private static final Pattern msie = compile(".*msie ([0-11]+)\\.([0-11]+).*");
private static final Pattern gecko = compile(".*rv:([0-9]+)\\.([0-9]+).*");
public String getName() {
@@ -58,6 +58,9 @@
Matcher m = msie.matcher(ua);
if (m.matches() && m.groupCount() == 2) {
int v = makeVersion(m);
+ if (v >= 11000) {
+ return "ie11";
+ }
if (v >= 10000) {
return "ie10";
}
@@ -70,6 +73,8 @@
}
return null;
+ } else if (ua.contains("edge")) {
+ return "edge";
} else if (ua.contains("gecko")) {
Matcher m = gecko.matcher(ua);
if (m.matches() && m.groupCount() == 2) {
diff --git a/gerrit-gwtui/gwt.defs b/gerrit-gwtui/gwt.defs
index 3b34d16..cd8fa74 100644
--- a/gerrit-gwtui/gwt.defs
+++ b/gerrit-gwtui/gwt.defs
@@ -18,12 +18,14 @@
'firefox',
'gecko1_8',
'safari',
- 'msie', 'ie8', 'ie9',
+ 'msie', 'ie8', 'ie9', 'ie10', 'ie11',
+ 'edge',
]
ALIASES = {
'chrome': 'safari',
'firefox': 'gecko1_8',
- 'msie': 'ie9',
+ 'msie': 'ie11',
+ 'edge': 'edge',
}
MODULE = 'com.google.gerrit.GerritGwtUI'
CPU_COUNT = cpu_count()
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
index c02518b..9644093 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
@@ -34,6 +34,8 @@
<when-property-is name="user.agent" value="ie8"/>
<when-property-is name="user.agent" value="ie9"/>
<when-property-is name="user.agent" value="ie10"/>
+ <when-property-is name="user.agent" value="ie11"/>
+ <when-property-is name="user.agent" value="edge"/>
</any>
</replace-with>
</module>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java
index 7921ebe..2956ffc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.change;
import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.NotSignedInDialog;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.Util;
@@ -36,6 +37,8 @@
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
@@ -161,6 +164,10 @@
public void onSuccess(AccountInfo result) {
onCloseForm();
setAssignee(result);
+ Reviewers reviewers = getReviewers();
+ if (reviewers != null) {
+ reviewers.updateReviewerList();
+ }
}
@Override
@@ -180,7 +187,7 @@
private void setAssignee(AccountInfo assignee) {
currentAssignee = assignee;
- assigneeLink.setText(assignee != null ? assignee.name() : null);
+ assigneeLink.setText(assignee != null ? getName(assignee) : null);
assigneeLink.setTargetHistoryToken(assignee != null
? PageLinks.toAssigneeQuery(assignee.name() != null
? assignee.name()
@@ -189,4 +196,26 @@
: String.valueOf(assignee._accountId()))
: "");
}
+
+ private Reviewers getReviewers() {
+ Element e = DOM.getParent(getElement());
+ for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
+ EventListener l = DOM.getEventListener(e);
+ if (l instanceof ChangeScreen) {
+ ChangeScreen screen = (ChangeScreen) l;
+ return screen.reviewers;
+ }
+ }
+ return null;
+ }
+
+ private String getName(AccountInfo info) {
+ if (info.name() != null) {
+ return info.name();
+ }
+ if (info.email() != null) {
+ return info.email();
+ }
+ return Gerrit.info().user().anonymousCowardName();
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
index b69d1c0..aa30760 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -198,7 +198,7 @@
});
}
- private void updateReviewerList() {
+ void updateReviewerList() {
ChangeApi.detail(changeId.get(),
new GerritCallback<ChangeInfo>() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css
index e72c840..f4f1e83 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css
@@ -63,7 +63,8 @@
-webkit-user-select: initial;
-khtml-user-select: initial;
-moz-user-select: text;
- -ms-user-select: initial;
+ -ms-user-select: text;
+ user-select: initial;
}
.commentBox {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
index 210800d..7e71639 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -90,7 +90,10 @@
}
CurrentUser self = session.get().getUser();
- if (!self.getCapabilities().canRunAs()) {
+ if (!self.getCapabilities().canRunAs()
+ // Always disallow for anonymous users, even if permitted by the ACL,
+ // because that would be crazy.
+ || !self.isIdentifiedUser()) {
replyError(req, res,
SC_FORBIDDEN,
"not permitted to use " + RUN_AS,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 8d8937c..18445b3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -98,6 +98,11 @@
serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class);
filter("/a/*").through(RequireIdentifiedUserFilter.class);
+
+ // Must be after RequireIdentifiedUserFilter so auth happens before checking
+ // for RunAs capability.
+ install(new RunAsFilter.Module());
+
serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
// Bind servlets for REST root collections.
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index fa551e8..d5a4b00 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -56,8 +56,6 @@
bind(RequestScopePropagator.class).to(GuiceRequestScopePropagator.class);
bind(HttpRequestContext.class);
- install(new RunAsFilter.Module());
-
installAuthModule();
if (options.enableMasterFeatures()) {
install(new UrlModule(options, authConfig));
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 1d7195b..c3d516b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -115,6 +115,7 @@
extractMailExample("FooterHtml.soy");
extractMailExample("HeaderHtml.soy");
extractMailExample("Merged.soy");
+ extractMailExample("MergedHtml.soy");
extractMailExample("NewChange.soy");
extractMailExample("NewChangeHtml.soy");
extractMailExample("RegisterNewEmail.soy");
diff --git a/gerrit-prettify/BUILD b/gerrit-prettify/BUILD
index 063feee..b8d4dd6 100644
--- a/gerrit-prettify/BUILD
+++ b/gerrit-prettify/BUILD
@@ -33,3 +33,8 @@
],
visibility = ['//visibility:public'],
)
+
+exports_files([
+ 'src/main/resources/com/google/gerrit/prettify/client/prettify.css',
+ 'src/main/resources/com/google/gerrit/prettify/client/prettify.js',
+])
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ChangeMessage.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
index 898dc94..db44d33 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
@@ -18,6 +18,7 @@
import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
+import java.util.Objects;
/** A message attached to a {@link Change}. */
public final class ChangeMessage {
@@ -78,6 +79,13 @@
@Column(id = 6, notNull = false)
protected String tag;
+ /**
+ * Real user that added this message on behalf of the user recorded in {@link
+ * #author}.
+ */
+ @Column(id = 7, notNull = false)
+ protected Account.Id realAuthor;
+
protected ChangeMessage() {
}
@@ -105,6 +113,15 @@
author = accountId;
}
+ public Account.Id getRealAuthor() {
+ return realAuthor != null ? realAuthor : getAuthor();
+ }
+
+ public void setRealAuthor(Account.Id id) {
+ // Use null for same real author, as before the column was added.
+ realAuthor = Objects.equals(getAuthor(), id) ? null : id;
+ }
+
public Timestamp getWrittenOn() {
return writtenOn;
}
@@ -142,6 +159,7 @@
return "ChangeMessage{"
+ "key=" + key
+ ", author=" + author
+ + ", realAuthor=" + realAuthor
+ ", writtenOn=" + writtenOn
+ ", patchset=" + patchset
+ ", tag=" + tag
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Comment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Comment.java
index f37b6bd..5ec3e47 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Comment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Comment.java
@@ -181,6 +181,7 @@
public Key key;
public int lineNbr;
public Identity author;
+ protected Identity realAuthor;
public Timestamp writtenOn;
public short side;
public String message;
@@ -194,6 +195,7 @@
this(new Key(c.key), c.author.getId(), new Timestamp(c.writtenOn.getTime()),
c.side, c.message, c.serverId);
this.lineNbr = c.lineNbr;
+ this.realAuthor = c.realAuthor;
this.range = c.range != null ? new Range(c.range) : null;
this.tag = c.tag;
this.revId = c.revId;
@@ -203,6 +205,7 @@
short side, String message, String serverId) {
this.key = key;
this.author = new Comment.Identity(author);
+ this.realAuthor = this.author;
this.writtenOn = writtenOn;
this.side = side;
this.message = message;
@@ -229,6 +232,16 @@
this.revId = revId != null ? revId.get() : null;
}
+ public void setRealAuthor(Account.Id id) {
+ realAuthor = id != null && id.get() != author.id
+ ? new Comment.Identity(id)
+ : null;
+ }
+
+ public Identity getRealAuthor() {
+ return realAuthor != null ? realAuthor : author;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Comment) {
@@ -249,6 +262,9 @@
.append("key=").append(key).append(',')
.append("lineNbr=").append(lineNbr).append(',')
.append("author=").append(author.getId().get()).append(',')
+ .append("realAuthor=")
+ .append(realAuthor != null ? realAuthor.getId().get() : "")
+ .append(',')
.append("writtenOn=").append(writtenOn.toString()).append(',')
.append("side=").append(side).append(',')
.append("message=").append(Objects.toString(message, "")).append(',')
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
index 5a3cc16..5d2f3bb 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
@@ -123,6 +123,7 @@
plc.setTag(c.tag);
plc.setRevId(new RevId(c.revId));
plc.setStatus(status);
+ plc.setRealAuthor(c.getRealAuthor().getId());
return plc;
}
@@ -167,6 +168,13 @@
protected String tag;
/**
+ * Real user that added this comment on behalf of the user recorded in {@link
+ * #author}.
+ */
+ @Column(id = 11, notNull = false)
+ protected Account.Id realAuthor;
+
+ /**
* The RevId for the commit to which this comment is referring.
*
* Note that this field is not stored in the database. It is just provided
@@ -192,6 +200,7 @@
key = o.key;
lineNbr = o.lineNbr;
author = o.author;
+ realAuthor = o.realAuthor;
writtenOn = o.writtenOn;
status = o.status;
side = o.side;
@@ -227,6 +236,15 @@
return author;
}
+ public Account.Id getRealAuthor() {
+ return realAuthor != null ? realAuthor : getAuthor();
+ }
+
+ public void setRealAuthor(Account.Id id) {
+ // Use null for same real author, as before the column was added.
+ realAuthor = Objects.equals(getAuthor(), id) ? null : id;
+ }
+
public Timestamp getWrittenOn() {
return writtenOn;
}
@@ -309,6 +327,7 @@
c.lineNbr = lineNbr;
c.parentUuid = parentUuid;
c.tag = tag;
+ c.setRealAuthor(getRealAuthor());
return c;
}
@@ -343,6 +362,8 @@
builder.append("key=").append(key).append(',');
builder.append("lineNbr=").append(lineNbr).append(',');
builder.append("author=").append(author.get()).append(',');
+ builder.append("realAuthor=")
+ .append(realAuthor != null ? realAuthor.get() : "").append(',');
builder.append("writtenOn=").append(writtenOn.toString()).append(',');
builder.append("status=").append(status).append(',');
builder.append("side=").append(side).append(',');
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
index 1d0d29b..9cc7816 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
@@ -93,6 +93,13 @@
@Column(id = 6, notNull = false)
protected String tag;
+ /**
+ * Real user that made this approval on behalf of the user recorded in {@link
+ * Key#accountId}.
+ */
+ @Column(id = 7, notNull = false)
+ protected Account.Id realAccountId;
+
// DELETED: id = 4 (changeOpen)
// DELETED: id = 5 (changeSortKey)
@@ -110,6 +117,7 @@
new PatchSetApproval.Key(psId, src.getAccountId(), src.getLabelId());
value = src.getValue();
granted = src.granted;
+ realAccountId = src.realAccountId;
}
public PatchSetApproval.Key getKey() {
@@ -124,6 +132,15 @@
return key.accountId;
}
+ public Account.Id getRealAccountId() {
+ return realAccountId != null ? realAccountId : getAccountId();
+ }
+
+ public void setRealAccountId(Account.Id id) {
+ // Use null for same real author, as before the column was added.
+ realAccountId = Objects.equals(getAccountId(), id) ? null : id;
+ }
+
public LabelId getLabelId() {
return key.categoryId;
}
@@ -166,8 +183,12 @@
@Override
public String toString() {
- return new StringBuilder().append('[').append(key).append(": ")
- .append(value).append(",tag:").append(tag).append(']').toString();
+ return "["
+ + key + ": "
+ + value
+ + ",tag:" + tag
+ + ",realAccountId:" + realAccountId
+ + ']';
}
@Override
@@ -177,7 +198,8 @@
return Objects.equals(key, p.key)
&& Objects.equals(value, p.value)
&& Objects.equals(granted, p.granted)
- && Objects.equals(tag, p.tag);
+ && Objects.equals(tag, p.tag)
+ && Objects.equals(realAccountId, p.realAccountId);
}
return false;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java
index da9584d..ecb952a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RobotComment.java
@@ -15,12 +15,14 @@
package com.google.gerrit.reviewdb.client;
import java.sql.Timestamp;
+import java.util.Map;
import java.util.Objects;
public class RobotComment extends Comment {
public String robotId;
public String robotRunId;
public String url;
+ public Map<String, String> properties;
public RobotComment(Key key, Account.Id author, Timestamp writtenOn,
short side, String message, String serverId, String robotId,
@@ -39,6 +41,9 @@
.append("robotRunId=").append(robotRunId).append(',')
.append("lineNbr=").append(lineNbr).append(',')
.append("author=").append(author.getId().get()).append(',')
+ .append("realAuthor=")
+ .append(realAuthor != null ? realAuthor.getId().get() : "")
+ .append(',')
.append("writtenOn=").append(writtenOn.toString()).append(',')
.append("side=").append(side).append(',')
.append("message=").append(Objects.toString(message, "")).append(',')
@@ -47,7 +52,8 @@
.append("range=").append(Objects.toString(range, "")).append(',')
.append("revId=").append(revId != null ? revId : "").append(',')
.append("tag=").append(Objects.toString(tag, "")).append(',')
- .append("url=").append(url)
+ .append("url=").append(url).append(',')
+ .append("properties=").append(properties != null ? properties : "")
.append('}')
.toString();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index d69ad3f..1fcb5b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -161,7 +161,8 @@
continue;
}
- ChangeKind kind = changeKindCache.getChangeKind(project, repo,
+ ChangeKind kind = changeKindCache.getChangeKind(
+ project.getProject().getNameKey(), repo,
ObjectId.fromString(priorPs.getRevision().get()),
ObjectId.fromString(ps.getRevision().get()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 43f20cc..67f07bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.Comparator.comparing;
@@ -26,6 +27,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
+import com.google.common.primitives.Shorts;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
@@ -86,6 +88,19 @@
return SORT_APPROVALS.sortedCopy(approvals);
}
+ public static PatchSetApproval newApproval(PatchSet.Id psId, CurrentUser user,
+ LabelId labelId, int value, Date when) {
+ PatchSetApproval psa = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ psId,
+ user.getAccountId(),
+ labelId),
+ Shorts.checkedCast(value),
+ when);
+ user.updateRealAccountId(psa::setRealAccountId);
+ return psa;
+ }
+
private static Iterable<PatchSetApproval> filterApprovals(
Iterable<PatchSetApproval> psas, final Account.Id accountId) {
return Iterables.filter(
@@ -266,7 +281,7 @@
}
/**
- * Adds approvals to ChangeUpdate and writes to ReviewDb.
+ * Adds approvals to ChangeUpdate for a new patch set, and writes to ReviewDb.
*
* @param db review database.
* @param update change update.
@@ -276,9 +291,14 @@
* @param approvals approvals to add.
* @throws OrmException
*/
- public Iterable<PatchSetApproval> addApprovals(ReviewDb db, ChangeUpdate update,
- LabelTypes labelTypes, PatchSet ps, ChangeControl changeCtl,
- Map<String, Short> approvals) throws OrmException {
+ public Iterable<PatchSetApproval> addApprovalsForNewPatchSet(ReviewDb db,
+ ChangeUpdate update, LabelTypes labelTypes, PatchSet ps,
+ ChangeControl changeCtl, Map<String, Short> approvals)
+ throws OrmException {
+ Account.Id accountId = changeCtl.getUser().getAccountId();
+ checkArgument(accountId.equals(ps.getUploader()),
+ "expected user %s to match patch set uploader %s",
+ accountId, ps.getUploader());
if (approvals.isEmpty()) {
return Collections.emptyList();
}
@@ -287,12 +307,9 @@
Date ts = update.getWhen();
for (Map.Entry<String, Short> vote : approvals.entrySet()) {
LabelType lt = labelTypes.byLabel(vote.getKey());
- cells.add(new PatchSetApproval(new PatchSetApproval.Key(
- ps.getId(),
- ps.getUploader(),
- lt.getLabelId()),
- vote.getValue(),
- ts));
+ cells.add(
+ newApproval(ps.getId(), changeCtl.getUser(), lt.getLabelId(),
+ vote.getValue(), ts));
}
for (PatchSetApproval psa : cells) {
update.putApproval(psa.getLabel(), psa.getValue());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
index f3fdbcb..13b289f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -14,12 +14,15 @@
package com.google.gerrit.server;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
@@ -27,6 +30,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -39,6 +43,26 @@
*/
@Singleton
public class ChangeMessagesUtil {
+ public static ChangeMessage newMessage(BatchUpdate.ChangeContext ctx,
+ String body) throws OrmException {
+ return newMessage(
+ ctx.getDb(), ctx.getChange().currentPatchSetId(),
+ ctx.getUser(), ctx.getWhen(), body);
+ }
+
+ public static ChangeMessage newMessage(
+ ReviewDb db, PatchSet.Id psId, CurrentUser user, Timestamp when,
+ String body) throws OrmException {
+ checkNotNull(psId);
+ Account.Id accountId = user.isInternalUser() ? null : user.getAccountId();
+ ChangeMessage m = new ChangeMessage(
+ new ChangeMessage.Key(psId.getParentKey(), ChangeUtil.messageUUID(db)),
+ accountId, when, psId);
+ m.setMessage(body);
+ user.updateRealAccountId(m::setRealAuthor);
+ return m;
+ }
+
private static List<ChangeMessage> sortChangeMessages(
Iterable<ChangeMessage> changeMessage) {
return ChangeNotes.MESSAGE_BY_TIME.sortedCopy(changeMessage);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
index 8197689..81ec4eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CommentsUtil.java
@@ -39,6 +39,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -129,6 +130,26 @@
this.serverId = serverId;
}
+ public Comment newComment(ChangeContext ctx, String path, PatchSet.Id psId,
+ short side, String message) throws OrmException {
+ Comment c = new Comment(
+ new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), path, psId.get()),
+ ctx.getUser().getAccountId(), ctx.getWhen(), side, message, serverId);
+ ctx.getUser().updateRealAccountId(c::setRealAuthor);
+ return c;
+ }
+
+ public RobotComment newRobotComment(ChangeContext ctx, String path,
+ PatchSet.Id psId, short side, String message, String robotId,
+ String robotRunId) throws OrmException {
+ RobotComment c = new RobotComment(
+ new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), path, psId.get()),
+ ctx.getUser().getAccountId(), ctx.getWhen(), side, message, serverId,
+ robotId, robotRunId);
+ ctx.getUser().updateRealAccountId(c::setRealAuthor);
+ return c;
+ }
+
public Optional<Comment> get(ReviewDb db, ChangeNotes notes,
Comment.Key key) throws OrmException {
if (!migration.readChanges()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 34a2d02..668b344 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -20,6 +20,8 @@
import com.google.gerrit.server.account.GroupMembership;
import com.google.inject.servlet.RequestScoped;
+import java.util.function.Consumer;
+
/**
* Information about the currently logged in user.
* <p>
@@ -72,6 +74,16 @@
}
/**
+ * If the {@link #getRealUser()} has an account ID associated with it, call
+ * the given setter with that ID.
+ */
+ public void updateRealAccountId(Consumer<Account.Id> setter) {
+ if (getRealUser().isIdentifiedUser()) {
+ setter.accept(getRealUser().getAccountId());
+ }
+ }
+
+ /**
* Get the set of groups the user is currently a member of.
* <p>
* The returned set may be a subset of the user's actual groups; if the user's
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index 04ebc87..c7ce1b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -90,15 +91,7 @@
*/
public IdentifiedUser parse(String id) throws AuthException,
UnprocessableEntityException, OrmException {
- IdentifiedUser user = parseId(id);
- if (user == null) {
- throw new UnprocessableEntityException(String.format(
- "Account Not Found: %s", id));
- } else if (!accountControlFactory.get().canSee(user.getAccount())) {
- throw new UnprocessableEntityException(String.format(
- "Account Not Found: %s", id));
- }
- return user;
+ return parseOnBehalfOf(null, id);
}
/**
@@ -115,6 +108,29 @@
* @throws OrmException
*/
public IdentifiedUser parseId(String id) throws AuthException, OrmException {
+ return parseIdOnBehalfOf(null, id);
+ }
+
+ /**
+ * Like {@link #parse(String)}, but also sets the {@link
+ * CurrentUser#getRealUser()} on the result.
+ */
+ public IdentifiedUser parseOnBehalfOf(@Nullable CurrentUser caller,
+ String id)
+ throws AuthException, UnprocessableEntityException, OrmException {
+ IdentifiedUser user = parseIdOnBehalfOf(caller, id);
+ if (user == null) {
+ throw new UnprocessableEntityException(String.format(
+ "Account Not Found: %s", id));
+ } else if (!accountControlFactory.get().canSee(user.getAccount())) {
+ throw new UnprocessableEntityException(String.format(
+ "Account Not Found: %s", id));
+ }
+ return user;
+ }
+
+ private IdentifiedUser parseIdOnBehalfOf(@Nullable CurrentUser caller,
+ String id) throws AuthException, OrmException {
if (id.equals("self")) {
CurrentUser user = self.get();
if (user.isIdentifiedUser()) {
@@ -130,7 +146,8 @@
if (match == null) {
return null;
}
- return userFactory.create(match.getId());
+ CurrentUser realUser = caller != null ? caller.getRealUser() : null;
+ return userFactory.runAs(null, match.getId(), realUser);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index c4bd68d..45f0afe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -31,7 +31,6 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.ChangeAbandoned;
@@ -106,7 +105,7 @@
public Change abandon(ChangeControl control, String msgTxt,
NotifyHandling notifyHandling) throws RestApiException, UpdateException {
- Op op = new Op(control.getUser(), msgTxt, notifyHandling);
+ Op op = new Op(msgTxt, notifyHandling);
try (BatchUpdate u =
batchUpdateFactory.create(
dbProvider.get(),
@@ -142,8 +141,7 @@
control.getProject().getNameKey().get(),
project.get()));
}
- u.addOp(
- control.getId(), new Op(control.getUser(), msgTxt, notifyHandling));
+ u.addOp(control.getId(), new Op(msgTxt, notifyHandling));
}
u.execute();
}
@@ -164,18 +162,14 @@
private class Op extends BatchUpdate.Op {
private final String msgTxt;
private final NotifyHandling notifyHandling;
- private final Account account;
private Change change;
private PatchSet patchSet;
private ChangeMessage message;
- private Op(CurrentUser user, String msgTxt, NotifyHandling notifyHandling) {
+ private Op(String msgTxt, NotifyHandling notifyHandling) {
this.msgTxt = msgTxt;
this.notifyHandling = notifyHandling;
- account = user.isIdentifiedUser()
- ? user.asIdentifiedUser().getAccount()
- : null;
}
@Override
@@ -208,19 +202,14 @@
msg.append(msgTxt.trim());
}
- ChangeMessage message = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- account != null ? account.getId() : null,
- ctx.getWhen(),
- change.currentPatchSetId());
- message.setMessage(msg.toString());
- return message;
+ return ChangeMessagesUtil.newMessage(ctx, msg.toString());
}
@Override
public void postUpdate(Context ctx) throws OrmException {
+ Account account = ctx.getUser().isIdentifiedUser()
+ ? ctx.getUser().asIdentifiedUser().getAccount()
+ : null;
try {
ReplyToChangeSender cm =
abandonedSenderFactory.create(ctx.getProject(), change.getId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 6906fb2..188d4a4d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -34,7 +34,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.events.CommitReceivedEvent;
@@ -361,14 +360,12 @@
patchSetInfo,
filterOnChangeVisibility(db, ctx.getNotes(), reviewers),
Collections.<Account.Id> emptySet());
- approvalsUtil.addApprovals(db, update, labelTypes, patchSet,
- ctx.getControl(), approvals);
+ approvalsUtil.addApprovalsForNewPatchSet(
+ db, update, labelTypes, patchSet, ctx.getControl(), approvals);
if (message != null) {
- changeMessage =
- new ChangeMessage(new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(db)), ctx.getAccountId(),
- patchSet.getCreatedOn(), patchSet.getId());
- changeMessage.setMessage(message);
+ changeMessage = ChangeMessagesUtil.newMessage(
+ db, patchSet.getId(), ctx.getUser(), patchSet.getCreatedOn(),
+ message);
cmUtil.addChangeMessage(db, update, changeMessage);
}
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
index f0075ee..e971eff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -18,8 +18,8 @@
import com.google.gerrit.extensions.client.ChangeKind;
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.server.ReviewDb;
-import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import org.eclipse.jgit.lib.ObjectId;
@@ -32,7 +32,7 @@
* implementation changes, which might invalidate old entries).
*/
public interface ChangeKindCache {
- ChangeKind getChangeKind(ProjectState project, @Nullable Repository repo,
+ ChangeKind getChangeKind(Project.NameKey project, @Nullable Repository repo,
ObjectId prior, ObjectId next);
ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index b23bcf8..c0c0492 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -33,8 +33,6 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.InMemoryInserter;
import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -85,7 +83,6 @@
public static class NoCache implements ChangeKindCache {
private final boolean useRecursiveMerge;
private final ChangeData.Factory changeDataFactory;
- private final ProjectCache projectCache;
private final GitRepositoryManager repoManager;
@@ -93,25 +90,21 @@
NoCache(
@GerritServerConfig Config serverConfig,
ChangeData.Factory changeDataFactory,
- ProjectCache projectCache,
GitRepositoryManager repoManager) {
this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
this.changeDataFactory = changeDataFactory;
- this.projectCache = projectCache;
this.repoManager = repoManager;
}
@Override
- public ChangeKind getChangeKind(ProjectState project,
+ public ChangeKind getChangeKind(Project.NameKey project,
@Nullable Repository repo, ObjectId prior, ObjectId next) {
try {
Key key = new Key(prior, next, useRecursiveMerge);
- return new Loader(
- key, repoManager, project.getProject().getNameKey(), repo)
- .call();
+ return new Loader(key, repoManager, project, repo).call();
} catch (IOException e) {
log.warn("Cannot check trivial rebase of new patch set " + next.name()
- + " in " + project.getProject().getName(), e);
+ + " in " + project, e);
return ChangeKind.REWORK;
}
}
@@ -120,13 +113,13 @@
public ChangeKind getChangeKind(ReviewDb db, Change change,
PatchSet patch) {
return getChangeKindInternal(this, db, change, patch, changeDataFactory,
- projectCache, repoManager);
+ repoManager);
}
@Override
public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd,
PatchSet patch) {
- return getChangeKindInternal(this, repo, cd, patch, projectCache);
+ return getChangeKindInternal(this, repo, cd, patch);
}
}
@@ -322,7 +315,6 @@
private final Cache<Key, ChangeKind> cache;
private final boolean useRecursiveMerge;
private final ChangeData.Factory changeDataFactory;
- private final ProjectCache projectCache;
private final GitRepositoryManager repoManager;
@Inject
@@ -330,27 +322,22 @@
@GerritServerConfig Config serverConfig,
@Named(ID_CACHE) Cache<Key, ChangeKind> cache,
ChangeData.Factory changeDataFactory,
- ProjectCache projectCache,
GitRepositoryManager repoManager) {
this.cache = cache;
this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
this.changeDataFactory = changeDataFactory;
- this.projectCache = projectCache;
this.repoManager = repoManager;
}
@Override
- public ChangeKind getChangeKind(ProjectState project,
+ public ChangeKind getChangeKind(Project.NameKey project,
@Nullable Repository repo, ObjectId prior, ObjectId next) {
try {
Key key = new Key(prior, next, useRecursiveMerge);
- return cache.get(
- key,
- new Loader(
- key, repoManager, project.getProject().getNameKey(), repo));
+ return cache.get(key, new Loader(key, repoManager, project, repo));
} catch (ExecutionException e) {
log.warn("Cannot check trivial rebase of new patch set " + next.name()
- + " in " + project.getProject().getName(), e);
+ + " in " + project, e);
return ChangeKind.REWORK;
}
}
@@ -358,27 +345,25 @@
@Override
public ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch) {
return getChangeKindInternal(this, db, change, patch, changeDataFactory,
- projectCache, repoManager);
+ repoManager);
}
@Override
public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd,
PatchSet patch) {
- return getChangeKindInternal(this, repo, cd, patch, projectCache);
+ return getChangeKindInternal(this, repo, cd, patch);
}
private static ChangeKind getChangeKindInternal(
ChangeKindCache cache,
@Nullable Repository repo,
ChangeData change,
- PatchSet patch,
- ProjectCache projectCache) {
+ PatchSet patch) {
ChangeKind kind = ChangeKind.REWORK;
// Trivial case: if we're on the first patch, we don't need to use
// the repository.
if (patch.getId().get() > 1) {
try {
- ProjectState projectState = projectCache.checkedGet(change.project());
Collection<PatchSet> patchSetCollection = change.patchSets();
PatchSet priorPs = patch;
for (PatchSet ps : patchSetCollection) {
@@ -396,11 +381,11 @@
// and deletes the draft.
if (priorPs != patch) {
kind =
- cache.getChangeKind(projectState, repo,
+ cache.getChangeKind(change.project(), repo,
ObjectId.fromString(priorPs.getRevision().get()),
ObjectId.fromString(patch.getRevision().get()));
}
- } catch (IOException | OrmException e) {
+ } catch (OrmException e) {
// Do nothing; assume we have a complex change
log.warn("Unable to get change kind for patchSet " + patch.getPatchSetId() +
"of change " + change.getId(), e);
@@ -415,7 +400,6 @@
Change change,
PatchSet patch,
ChangeData.Factory changeDataFactory,
- ProjectCache projectCache,
GitRepositoryManager repoManager) {
// TODO - dborowitz: add NEW_CHANGE type for default.
ChangeKind kind = ChangeKind.REWORK;
@@ -424,8 +408,7 @@
if (patch.getId().get() > 1) {
try (Repository repo = repoManager.openRepository(change.getProject())) {
kind = getChangeKindInternal(cache, repo,
- changeDataFactory.create(db, change), patch,
- projectCache);
+ changeDataFactory.create(db, change), patch);
} catch (IOException e) {
// Do nothing; assume we have a complex change
log.warn("Unable to get change kind for patchSet " + patch.getPatchSetId() +
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 0c620e2..248acd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -272,10 +272,6 @@
@Override
public boolean updateChange(ChangeContext ctx) throws OrmException {
- ChangeMessage changeMessage = new ChangeMessage(
- new ChangeMessage.Key(
- ctx.getChange().getId(), ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(), psId);
StringBuilder sb = new StringBuilder("Patch Set ")
.append(psId.get())
.append(": Cherry Picked")
@@ -284,8 +280,8 @@
.append(destBranch)
.append(" as commit ")
.append(cherryPickCommit.name());
- changeMessage.setMessage(sb.toString());
-
+ ChangeMessage changeMessage = ChangeMessagesUtil.newMessage(
+ ctx.getDb(), psId, ctx.getUser(), ctx.getWhen(), sb.toString());
cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
index be019fb..54f73bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
@@ -180,6 +180,7 @@
rci.robotId = c.robotId;
rci.robotRunId = c.robotRunId;
rci.url = c.url;
+ rci.properties = c.properties;
fillCommentInfo(c, rci, loader);
return rci;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
index 7ca8478..47821f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -30,10 +30,8 @@
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.UpdateException;
@@ -53,7 +51,6 @@
private final CommentsUtil commentsUtil;
private final PatchSetUtil psUtil;
private final PatchListCache patchListCache;
- private final String serverId;
@Inject
CreateDraftComment(Provider<ReviewDb> db,
@@ -61,15 +58,13 @@
Provider<CommentJson> commentJson,
CommentsUtil commentsUtil,
PatchSetUtil psUtil,
- PatchListCache patchListCache,
- @GerritServerId String serverId) {
+ PatchListCache patchListCache) {
this.db = db;
this.updateFactory = updateFactory;
this.commentJson = commentJson;
this.commentsUtil = commentsUtil;
this.psUtil = psUtil;
this.patchListCache = patchListCache;
- this.serverId = serverId;
}
@Override
@@ -113,14 +108,8 @@
if (ps == null) {
throw new ResourceNotFoundException("patch set not found: " + psId);
}
- comment = new Comment(
- new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), in.path,
- ps.getPatchSetId()),
- ctx.getAccountId(),
- ctx.getWhen(),
- in.side(),
- in.message.trim(),
- serverId);
+ comment = commentsUtil.newComment(
+ ctx, in.path, ps.getId(), in.side(), in.message.trim());
comment.parentUuid = Url.decode(in.inReplyTo);
comment.setLineNbrAndRange(in.line, in.range);
comment.tag = in.tag;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
index a572c58..70c2ae7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
@@ -25,7 +25,6 @@
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.account.AccountJson;
import com.google.gerrit.server.change.DeleteAssignee.Input;
@@ -115,14 +114,8 @@
private void addMessage(BatchUpdate.ChangeContext ctx,
ChangeUpdate update, Account deleted) throws OrmException {
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(
- ctx.getChange().getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(),
- ctx.getChange().currentPatchSetId());
- cmsg.setMessage(
- "Assignee deleted: " + deleted.getName(anonymousCowardName));
+ ChangeMessage cmsg = ChangeMessagesUtil.newMessage(
+ ctx, "Assignee deleted: " + deleted.getName(anonymousCowardName));
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index 099f8e0..72ffa77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -36,7 +36,6 @@
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.ReviewerDeleted;
@@ -185,11 +184,7 @@
ChangeUpdate update = ctx.getUpdate(currPs.getId());
update.removeReviewer(reviewerId);
- changeMessage = new ChangeMessage(
- new ChangeMessage.Key(currChange.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(), currPs.getId());
- changeMessage.setMessage(msg.toString());
+ changeMessage = ChangeMessagesUtil.newMessage(ctx, msg.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index e25e2292..eda2fcf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -35,7 +35,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.VoteDeleted;
@@ -170,20 +169,13 @@
ctx.getDb().patchSetApprovals().upsert(
Collections.singleton(deletedApproval(ctx)));
- changeMessage =
- new ChangeMessage(new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(),
- ctx.getWhen(),
- change.currentPatchSetId());
StringBuilder msg = new StringBuilder();
msg.append("Removed ");
LabelVote.appendTo(msg, label, checkNotNull(oldApprovals.get(label)));
- changeMessage.setMessage(
- msg.append(" by ")
- .append(userFactory.create(accountId).getNameEmail())
- .append("\n")
- .toString());
+ msg.append(" by ")
+ .append(userFactory.create(accountId).getNameEmail())
+ .append("\n");
+ changeMessage = ChangeMessagesUtil.newMessage(ctx, msg.toString());
cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId),
changeMessage);
@@ -191,6 +183,9 @@
}
private PatchSetApproval deletedApproval(ChangeContext ctx) {
+ // Set the effective user to the account we're trying to remove, and don't
+ // set the real user; this preserves the calling user as the NoteDb
+ // committer.
return new PatchSetApproval(
new PatchSetApproval.Key(
ps.getId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
index 2139ec4..954392c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
@@ -33,8 +33,6 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -94,7 +92,7 @@
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
req.getChange().getProject(), control.getUser(), TimeUtil.nowTs())) {
- u.addOp(req.getChange().getId(), new Op(control, input));
+ u.addOp(req.getChange().getId(), new Op(input));
u.execute();
}
@@ -103,14 +101,12 @@
private class Op extends BatchUpdate.Op {
private final MoveInput input;
- private final IdentifiedUser caller;
private Change change;
private Branch.NameKey newDestKey;
- Op(ChangeControl ctl, MoveInput input) {
+ Op(MoveInput input) {
this.input = input;
- this.caller = ctl.getUser().asIdentifiedUser();
}
@Override
@@ -179,11 +175,8 @@
msgBuf.append("\n\n");
msgBuf.append(input.message);
}
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- caller.getAccountId(), ctx.getWhen(), change.currentPatchSetId());
- cmsg.setMessage(msgBuf.toString());
+ ChangeMessage cmsg =
+ ChangeMessagesUtil.newMessage(ctx, msgBuf.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 3f38fc3..5914ccb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -30,7 +30,6 @@
import com.google.gerrit.server.ApprovalCopier;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.events.CommitReceivedEvent;
@@ -225,9 +224,8 @@
}
if (message != null) {
- changeMessage = new ChangeMessage(
- new ChangeMessage.Key(ctl.getId(), ChangeUtil.messageUUID(db)),
- ctx.getAccountId(), ctx.getWhen(), patchSet.getId());
+ changeMessage = ChangeMessagesUtil.newMessage(
+ db, patchSet.getId(), ctx.getUser(), ctx.getWhen(), message);
changeMessage.setMessage(message);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 74b1b20..9210d2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.change;
-import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
@@ -58,6 +57,7 @@
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -66,13 +66,11 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.account.AccountsCollection;
-import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.extensions.events.CommentAdded;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -121,7 +119,6 @@
private final EmailReviewComments.Factory email;
private final CommentAdded commentAdded;
private final PostReviewers postReviewers;
- private final String serverId;
private final NotesMigration migration;
@Inject
@@ -138,7 +135,6 @@
EmailReviewComments.Factory email,
CommentAdded commentAdded,
PostReviewers postReviewers,
- @GerritServerId String serverId,
NotesMigration migration) {
this.db = db;
this.batchUpdateFactory = batchUpdateFactory;
@@ -153,7 +149,6 @@
this.email = email;
this.commentAdded = commentAdded;
this.postReviewers = postReviewers;
- this.serverId = serverId;
this.migration = migration;
}
@@ -173,6 +168,8 @@
}
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, input);
+ } else if (input.drafts == null) {
+ input.drafts = DraftHandling.DELETE;
}
if (input.labels != null) {
checkLabels(revision, input.strictLabels, input.labels);
@@ -248,6 +245,13 @@
"label required to post review on behalf of \"%s\"",
in.onBehalfOf));
}
+ if (in.drafts == null) {
+ in.drafts = DraftHandling.KEEP;
+ }
+ if (in.drafts != DraftHandling.KEEP) {
+ throw new AuthException("not allowed to modify other user's drafts");
+ }
+
ChangeControl caller = rev.getControl();
Iterator<Map.Entry<String, Short>> itr = in.labels.entrySet().iterator();
@@ -275,7 +279,13 @@
in.onBehalfOf));
}
- ChangeControl target = caller.forUser(accounts.parse(in.onBehalfOf));
+ ChangeControl target = caller.forUser(
+ accounts.parseOnBehalfOf(caller.getUser(), in.onBehalfOf));
+ if (!target.getRefControl().isVisible()) {
+ throw new UnprocessableEntityException(String.format(
+ "on_behalf_of account %s cannot see destination ref",
+ target.getUser().getAccountId()));
+ }
return new RevisionResource(changes.parse(target), rev.getPatchSet());
}
@@ -512,14 +522,7 @@
String parent = Url.decode(c.inReplyTo);
Comment e = drafts.remove(Url.decode(c.id));
if (e == null) {
- e = new Comment(
- new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), path,
- psId.get()),
- user.getAccountId(),
- ctx.getWhen(),
- c.side(),
- c.message,
- serverId);
+ e = commentsUtil.newComment(ctx, path, psId, c.side(), c.message);
} else {
e.writtenOn = ctx.getWhen();
e.side = c.side();
@@ -540,7 +543,7 @@
}
}
- switch (firstNonNull(in.drafts, DraftHandling.DELETE)) {
+ switch (in.drafts) {
case KEEP:
default:
break;
@@ -577,13 +580,11 @@
for (Map.Entry<String, List<RobotCommentInput>> ent : in.robotComments.entrySet()) {
String path = ent.getKey();
for (RobotCommentInput c : ent.getValue()) {
- RobotComment e = new RobotComment(
- new Comment.Key(ChangeUtil.messageUUID(ctx.getDb()), path,
- psId.get()),
- user.getAccountId(), ctx.getWhen(), c.side(), c.message, serverId,
- c.robotId, c.robotRunId);
+ RobotComment e = commentsUtil.newRobotComment(
+ ctx, path, psId, c.side(), c.message, c.robotId, c.robotRunId);
e.parentUuid = Url.decode(c.inReplyTo);
e.url = c.url;
+ e.properties = c.properties;
e.setLineNbrAndRange(c.line, c.range);
e.tag = in.tag;
setCommentRevId(e, patchListCache, ctx.getChange(), ps);
@@ -646,6 +647,10 @@
Comment c, PatchSet ps) throws OrmException {
c.writtenOn = ctx.getWhen();
c.tag = in.tag;
+ // Draft may have been created by a different real user; copy the current
+ // real user. (Only applies to X-Gerrit-RunAs, since modifying drafts via
+ // on_behalf_of is not allowed.)
+ ctx.getUser().updateRealAccountId(c::setRealAuthor);
setCommentRevId(c, patchListCache, ctx.getChange(), checkNotNull(ps));
return c;
}
@@ -766,6 +771,7 @@
c.setValue(ent.getValue());
c.setGranted(ctx.getWhen());
c.setTag(in.tag);
+ ctx.getUser().updateRealAccountId(c::setRealAccountId);
ups.add(c);
addLabelDelta(normName, c.getValue());
oldApprovals.put(normName, previous.get(normName));
@@ -776,11 +782,8 @@
oldApprovals.put(normName, null);
approvals.put(normName, c.getValue());
} else if (c == null) {
- c = new PatchSetApproval(new PatchSetApproval.Key(
- psId,
- user.getAccountId(),
- lt.getLabelId()),
- ent.getValue(), ctx.getWhen());
+ c = ApprovalsUtil.newApproval(
+ psId, user, lt.getLabelId(), ent.getValue(), ctx.getWhen());
c.setTag(in.tag);
c.setGranted(ctx.getWhen());
ups.add(c);
@@ -818,12 +821,10 @@
if (del.isEmpty()) {
// If no existing label is being set to 0, hack in the caller
// as a reviewer by picking the first server-wide LabelType.
- PatchSetApproval c = new PatchSetApproval(new PatchSetApproval.Key(
- psId,
- user.getAccountId(),
- ctx.getControl().getLabelTypes().getLabelTypes().get(0)
- .getLabelId()),
- (short) 0, ctx.getWhen());
+ LabelId labelId = ctx.getControl().getLabelTypes().getLabelTypes()
+ .get(0).getLabelId();
+ PatchSetApproval c = ApprovalsUtil.newApproval(
+ psId, user, labelId, 0, ctx.getWhen());
c.setTag(in.tag);
c.setGranted(ctx.getWhen());
ups.add(c);
@@ -882,17 +883,10 @@
return false;
}
- message = new ChangeMessage(
- new ChangeMessage.Key(
- psId.getParentKey(), ChangeUtil.messageUUID(ctx.getDb())),
- user.getAccountId(),
- ctx.getWhen(),
- psId);
+ message = ChangeMessagesUtil.newMessage(
+ ctx.getDb(), psId, user, ctx.getWhen(),
+ "Patch Set " + psId.get() + ":" + buf);
message.setTag(in.tag);
- message.setMessage(String.format(
- "Patch Set %d:%s",
- psId.get(),
- buf.toString()));
cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), message);
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
index b4ed31b..435832a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
@@ -75,6 +75,12 @@
throw new BadRequestException("format is not specified");
}
ArchiveFormat f = allowedFormats.extensions.get("." + format);
+ if (f == null && format.equals("tgz")) {
+ // Always allow tgz, even when the allowedFormats doesn't contain it.
+ // Then we allow at least one format even if the list of allowed
+ // formats is empty.
+ f = ArchiveFormat.TGZ;
+ }
if (f == null) {
throw new BadRequestException("unknown archive format");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 9d997f6..35b0ac7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -34,7 +34,7 @@
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountResolver;
@@ -252,15 +252,12 @@
private void sendReplacePatchSet(Context ctx)
throws EmailException, OrmException {
- Account.Id accountId = ctx.getAccountId();
- ChangeMessage msg =
- new ChangeMessage(new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())), accountId,
- ctx.getWhen(), psId);
- msg.setMessage("Uploaded patch set " + psId.get() + ".");
+ ChangeMessage msg = ChangeMessagesUtil.newMessage(
+ ctx.getDb(), psId, ctx.getUser(), ctx.getWhen(),
+ "Uploaded patch set " + psId.get() + ".");
ReplacePatchSetSender cm =
replacePatchSetFactory.create(ctx.getProject(), change.getId());
- cm.setFrom(accountId);
+ cm.setFrom(ctx.getAccountId());
cm.setPatchSet(patchSet, patchSetInfo);
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
cm.addReviewers(recipients.getReviewers());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index 0808f95..91ad849 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -121,6 +121,9 @@
}
Comment origComment = maybeComment.get();
comment = new Comment(origComment);
+ // Copy constructor preserved old real author; replace with current real
+ // user.
+ ctx.getUser().updateRealAccountId(comment::setRealAuthor);
PatchSet.Id psId =
new PatchSet.Id(ctx.getChange().getId(), origComment.key.patchSetId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 31ae892..c0b3517 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -26,7 +26,6 @@
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.PutTopic.Input;
import com.google.gerrit.server.extensions.events.TopicEdited;
import com.google.gerrit.server.git.BatchUpdate;
@@ -115,13 +114,7 @@
change.setTopic(Strings.emptyToNull(newTopicName));
update.setTopic(change.getTopic());
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(),
- change.currentPatchSetId());
- cmsg.setMessage(summary);
+ ChangeMessage cmsg = ChangeMessagesUtil.newMessage(ctx, summary);
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 9c4c6d9..f317ef2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -29,7 +29,6 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.ChangeRestored;
import com.google.gerrit.server.git.BatchUpdate;
@@ -131,16 +130,7 @@
msg.append("\n\n");
msg.append(input.message.trim());
}
-
- ChangeMessage message = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(),
- ctx.getWhen(),
- change.currentPatchSetId());
- message.setMessage(msg.toString());
- return message;
+ return ChangeMessagesUtil.newMessage(ctx, msg.toString());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 3ca496a..c42b257 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -34,7 +34,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.PatchSetUtil;
@@ -274,14 +273,8 @@
public boolean updateChange(ChangeContext ctx) throws Exception {
Change change = ctx.getChange();
PatchSet.Id patchSetId = change.currentPatchSetId();
- ChangeMessage changeMessage = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(db.get())),
- ctx.getAccountId(), ctx.getWhen(), patchSetId);
- StringBuilder msgBuf = new StringBuilder();
- msgBuf.append("Created a revert of this change as ")
- .append("I").append(computedChangeId.name());
- changeMessage.setMessage(msgBuf.toString());
+ ChangeMessage changeMessage = ChangeMessagesUtil.newMessage(ctx,
+ "Created a revert of this change as I" + computedChangeId.name());
cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(patchSetId),
changeMessage);
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetAssigneeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetAssigneeOp.java
index a8ca147..dba5358 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetAssigneeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetAssigneeOp.java
@@ -25,7 +25,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.account.AccountsCollection;
@@ -139,13 +138,7 @@
msg.append(" to: ");
msg.append(newAssignee.getName(anonymousCowardName));
}
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(),
- change.currentPatchSetId());
- cmsg.setMessage(msg.toString());
+ ChangeMessage cmsg = ChangeMessagesUtil.newMessage(ctx, msg.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
index 34c611b..410e6ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -28,7 +28,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.extensions.events.HashtagsEdited;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -129,18 +128,12 @@
return true;
}
- private void addMessage(Context ctx, ChangeUpdate update)
+ private void addMessage(ChangeContext ctx, ChangeUpdate update)
throws OrmException {
StringBuilder msg = new StringBuilder();
appendHashtagMessage(msg, "added", toAdd);
appendHashtagMessage(msg, "removed", toRemove);
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(),
- change.currentPatchSetId());
- cmsg.setMessage(msg.toString());
+ ChangeMessage cmsg = ChangeMessagesUtil.newMessage(ctx, msg.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index b8443be..e80e758 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -485,16 +485,12 @@
if (!caller.canSubmitAs()) {
throw new AuthException("submit on behalf of not permitted");
}
- IdentifiedUser targetUser = accounts.parseId(in.onBehalfOf);
- if (targetUser == null) {
- throw new UnprocessableEntityException(String.format(
- "Account Not Found: %s", in.onBehalfOf));
- }
- ChangeControl target = caller.forUser(targetUser);
+ ChangeControl target = caller.forUser(
+ accounts.parseOnBehalfOf(caller.getUser(), in.onBehalfOf));
if (!target.getRefControl().isVisible()) {
throw new UnprocessableEntityException(String.format(
"on_behalf_of account %s cannot see destination ref",
- targetUser.getAccountId()));
+ target.getUser().getAccountId()));
}
return new RevisionResource(changes.parse(target), rsrc.getPatchSet());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index b09e0fa..8fca453 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -41,8 +41,6 @@
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -71,7 +69,6 @@
private final PatchSetInserter.Factory patchSetInserterFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeIndexer indexer;
- private final ProjectCache projectCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> user;
private final ChangeKindCache changeKindCache;
@@ -83,7 +80,6 @@
PatchSetInserter.Factory patchSetInserterFactory,
ChangeControl.GenericFactory changeControlFactory,
ChangeIndexer indexer,
- ProjectCache projectCache,
Provider<ReviewDb> db,
Provider<CurrentUser> user,
ChangeKindCache changeKindCache,
@@ -93,7 +89,6 @@
this.patchSetInserterFactory = patchSetInserterFactory;
this.changeControlFactory = changeControlFactory;
this.indexer = indexer;
- this.projectCache = projectCache;
this.db = db;
this.user = user;
this.changeKindCache = changeKindCache;
@@ -196,10 +191,10 @@
.append(inserter.getPatchSetId().get())
.append(": ");
- ProjectState project = projectCache.get(change.getDest().getParentKey());
// Previously checked that the base patch set is the current patch set.
ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get());
- ChangeKind kind = changeKindCache.getChangeKind(project, repo, prior, squashed);
+ ChangeKind kind = changeKindCache.getChangeKind(
+ change.getProject(), repo, prior, squashed);
if (kind == ChangeKind.NO_CODE_CHANGE) {
message.append("Commit message was updated.");
} else {
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 a3125a5..71b29a1 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
@@ -47,7 +47,6 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -764,11 +763,10 @@
change.setStatus(Change.Status.ABANDONED);
- ChangeMessage msg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- null, change.getLastUpdatedOn(), change.currentPatchSetId());
- msg.setMessage("Project was deleted.");
+ ChangeMessage msg = ChangeMessagesUtil.newMessage(
+ ctx.getDb(), change.currentPatchSetId(),
+ internalUserFactory.create(), change.getLastUpdatedOn(),
+ "Project was deleted.");
cmUtil.addChangeMessage(ctx.getDb(),
ctx.getUpdate(change.currentPatchSetId()), msg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
index 2ccc849..6da1335 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -22,8 +22,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.ChangeMerged;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -149,19 +149,13 @@
}
}
msgBuf.append(".");
- ChangeMessage msg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(), psId);
- msg.setMessage(msgBuf.toString());
+ ChangeMessage msg = ChangeMessagesUtil.newMessage(
+ ctx.getDb(), psId, ctx.getUser(), ctx.getWhen(), msgBuf.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, msg);
- PatchSetApproval submitter = new PatchSetApproval(
- new PatchSetApproval.Key(
- change.currentPatchSetId(),
- ctx.getAccountId(),
- LabelId.legacySubmit()),
- (short) 1, ctx.getWhen());
+ PatchSetApproval submitter = ApprovalsUtil.newApproval(
+ change.currentPatchSetId(), ctx.getUser(), LabelId.legacySubmit(),
+ 1, ctx.getWhen());
update.putApproval(submitter.getLabel(), submitter.getValue());
ctx.getDb().patchSetApprovals().upsert(
Collections.singleton(submitter));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index a70fa7a..afb682c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -34,7 +34,6 @@
import com.google.gerrit.server.ApprovalCopier;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeKindCache;
@@ -193,7 +192,8 @@
@Override
public void updateRepo(RepoContext ctx) throws Exception {
- changeKind = changeKindCache.getChangeKind(projectControl.getProjectState(),
+ changeKind = changeKindCache.getChangeKind(
+ projectControl.getProject().getNameKey(),
ctx.getRepository(), priorCommit, commit);
if (checkMergedInto) {
@@ -256,7 +256,7 @@
MailRecipients oldRecipients =
getRecipientsFromReviewers(cd.reviewers());
Iterable<PatchSetApproval> newApprovals =
- approvalsUtil.addApprovals(ctx.getDb(), update,
+ approvalsUtil.addApprovalsForNewPatchSet(ctx.getDb(), update,
projectControl.getLabelTypes(), newPatchSet, ctx.getControl(),
approvals);
approvalCopier.copy(ctx.getDb(), ctx.getControl(), newPatchSet,
@@ -278,11 +278,8 @@
if (!Strings.isNullOrEmpty(reviewMessage)) {
message.append("\n").append(reviewMessage);
}
- msg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(ctx.getDb())),
- ctx.getAccountId(), ctx.getWhen(), patchSetId);
- msg.setMessage(message.toString());
+ msg = ChangeMessagesUtil.newMessage(ctx.getDb(), patchSetId, ctx.getUser(),
+ ctx.getWhen(), message.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, msg);
if (mergedByPushOp == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index b35de7b..7dacc6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -33,7 +33,8 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
@@ -329,15 +330,9 @@
byKey.put(psa.getKey(), psa);
}
- submitter = new PatchSetApproval(
- new PatchSetApproval.Key(
- psId,
- ctx.getAccountId(),
- LabelId.legacySubmit()),
- (short) 1, ctx.getWhen());
+ submitter = ApprovalsUtil.newApproval(
+ psId, ctx.getUser(), LabelId.legacySubmit(), 1, ctx.getWhen());
byKey.put(submitter.getKey(), submitter);
- submitter.setValue((short) 1);
- submitter.setGranted(ctx.getWhen());
// Flatten out existing approvals for this patch set based upon the current
// permissions. Once the change is closed the approvals are not updated at
@@ -415,7 +410,7 @@
}
private ChangeMessage message(ChangeContext ctx, CodeReviewCommit commit,
- CommitMergeStatus s) {
+ CommitMergeStatus s) throws OrmException {
checkNotNull(s, "CommitMergeStatus may not be null");
String txt = s.getMessage();
if (s == CommitMergeStatus.CLEAN_MERGE) {
@@ -452,19 +447,9 @@
}
private ChangeMessage message(ChangeContext ctx, PatchSet.Id psId,
- String body) {
- checkNotNull(psId);
- String uuid;
- try {
- uuid = ChangeUtil.messageUUID(ctx.getDb());
- } catch (OrmException e) {
- return null;
- }
- ChangeMessage m = new ChangeMessage(
- new ChangeMessage.Key(psId.getParentKey(), uuid),
- ctx.getAccountId(), ctx.getWhen(), psId);
- m.setMessage(body);
- return m;
+ String body) throws OrmException {
+ return ChangeMessagesUtil.newMessage(
+ ctx.getDb(), psId, ctx.getUser(), ctx.getWhen(), body);
}
private void setMerged(ChangeContext ctx, ChangeMessage msg)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
index 9aef496..26bd99e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
@@ -53,6 +53,7 @@
"FooterHtml.soy",
"HeaderHtml.soy",
"Merged.soy",
+ "MergedHtml.soy",
"NewChange.soy",
"NewChangeHtml.soy",
"RegisterNewEmail.soy",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index c2a3cdd..17a0854 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -59,6 +59,10 @@
@Override
protected void formatChange() throws EmailException {
appendText(textTemplate("Merged"));
+
+ if (useHtml()) {
+ appendHtml(soyHtmlTemplate("MergedHtml"));
+ }
}
public String getApprovals() {
@@ -129,4 +133,9 @@
super.setupSoyContext();
soyContextEmailData.put("approvals", getApprovals());
}
+
+ @Override
+ protected boolean supportsHtml() {
+ return true;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index 70a5f4f..fa23b80 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
@@ -45,6 +46,7 @@
protected final ChangeNoteUtil noteUtil;
protected final String anonymousCowardName;
protected final Account.Id accountId;
+ protected final Account.Id realAccountId;
protected final PersonIdent authorIdent;
protected final Date when;
@@ -69,6 +71,9 @@
this.notes = ctl.getNotes();
this.change = notes.getChange();
this.accountId = accountId(ctl.getUser());
+ Account.Id realAccountId = accountId(ctl.getUser().getRealUser());
+ this.realAccountId =
+ realAccountId != null ? realAccountId : accountId;
this.authorIdent =
ident(noteUtil, serverIdent, anonymousCowardName, ctl.getUser(), when);
this.when = when;
@@ -82,6 +87,7 @@
@Nullable ChangeNotes notes,
@Nullable Change change,
Account.Id accountId,
+ Account.Id realAccountId,
PersonIdent authorIdent,
Date when) {
checkArgument(
@@ -95,6 +101,7 @@
this.notes = notes;
this.change = change != null ? change : notes.getChange();
this.accountId = accountId;
+ this.realAccountId = realAccountId;
this.authorIdent = authorIdent;
this.when = when;
}
@@ -255,4 +262,18 @@
private static ObjectId emptyTree(ObjectInserter ins) throws IOException {
return ins.insert(Constants.OBJ_TREE, new byte[] {});
}
+
+ protected void verifyComment(Comment c) {
+ checkArgument(c.revId != null, "RevId required for comment: %s", c);
+ checkArgument(
+ c.author.getId().equals(getAccountId()),
+ "The author for the following comment does not match the author of"
+ + " this %s (%s): %s",
+ getClass().getSimpleName(), getAccountId(), c);
+ checkArgument(
+ c.getRealAuthor().getId().equals(realAccountId),
+ "The real author for the following comment does not match the real"
+ + " author of this %s (%s): %s",
+ getClass().getSimpleName(), realAccountId, c);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index ca595d7..3322776 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -226,13 +226,13 @@
checkColumns(Change.class,
1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 101);
checkColumns(ChangeMessage.Key.class, 1, 2);
- checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6);
+ checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
checkColumns(PatchSet.Id.class, 1, 2);
checkColumns(PatchSet.class, 1, 2, 3, 4, 5, 6, 8);
checkColumns(PatchSetApproval.Key.class, 1, 2, 3);
- checkColumns(PatchSetApproval.class, 1, 2, 3, 6);
+ checkColumns(PatchSetApproval.class, 1, 2, 3, 6, 7);
checkColumns(PatchLineComment.Key.class, 1, 2);
- checkColumns(PatchLineComment.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+ checkColumns(PatchLineComment.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
}
private final Change change;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index b4ab290..57d5dce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkArgument;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.auto.value.AutoValue;
@@ -62,10 +61,19 @@
*/
public class ChangeDraftUpdate extends AbstractChangeUpdate {
public interface Factory {
- ChangeDraftUpdate create(ChangeNotes notes, Account.Id accountId,
- PersonIdent authorIdent, Date when);
- ChangeDraftUpdate create(Change change, Account.Id accountId,
- PersonIdent authorIdent, Date when);
+ ChangeDraftUpdate create(
+ ChangeNotes notes,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
+ PersonIdent authorIdent,
+ Date when);
+
+ ChangeDraftUpdate create(
+ Change change,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
+ PersonIdent authorIdent,
+ Date when);
}
@AutoValue
@@ -91,11 +99,12 @@
AllUsersName allUsers,
ChangeNoteUtil noteUtil,
@Assisted ChangeNotes notes,
- @Assisted Account.Id accountId,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, notes, null,
- accountId, authorIdent, when);
+ accountId, realAccountId, authorIdent, when);
this.draftsProject = allUsers;
}
@@ -107,11 +116,12 @@
AllUsersName allUsers,
ChangeNoteUtil noteUtil,
@Assisted Change change,
- @Assisted Account.Id accountId,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
- accountId, authorIdent, when);
+ accountId, realAccountId, authorIdent, when);
this.draftsProject = allUsers;
}
@@ -129,12 +139,6 @@
delete.add(new AutoValue_ChangeDraftUpdate_Key(revId, key));
}
- private void verifyComment(Comment comment) {
- checkArgument(comment.author.getId().equals(accountId),
- "The author for the following comment does not match the author of"
- + " this ChangeDraftUpdate (%s): %s", accountId, comment);
- }
-
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, OrmException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 078adf1..239b54e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -71,6 +71,7 @@
public static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
public static final FooterKey FOOTER_LABEL = new FooterKey("Label");
public static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
+ public static final FooterKey FOOTER_REAL_USER = new FooterKey("Real-user");
public static final FooterKey FOOTER_STATUS = new FooterKey("Status");
public static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject");
public static final FooterKey FOOTER_SUBMISSION_ID =
@@ -88,6 +89,7 @@
private static final String PARENT = "Parent";
private static final String PARENT_NUMBER = "Parent-number";
private static final String PATCH_SET = "Patch-set";
+ private static final String REAL_AUTHOR = "Real-author";
private static final String REVISION = "Revision";
private static final String UUID = "UUID";
private static final String TAG = FOOTER_TAG.getName();
@@ -232,7 +234,14 @@
}
Timestamp commentTime = parseTimestamp(note, curr, changeId);
- Account.Id aId = parseAuthor(note, curr, changeId);
+ Account.Id aId = parseAuthor(note, curr, changeId, AUTHOR);
+ boolean hasRealAuthor =
+ (RawParseUtils.match(note, curr.value, REAL_AUTHOR.getBytes(UTF_8)))
+ != -1;
+ Account.Id raId = null;
+ if (hasRealAuthor) {
+ raId = parseAuthor(note, curr, changeId, REAL_AUTHOR);
+ }
boolean hasParent =
(RawParseUtils.match(note, curr.value, PARENT.getBytes(UTF_8))) != -1;
@@ -269,6 +278,9 @@
c.parentUuid = parentUUID;
c.tag = tag;
c.setRevId(revId);
+ if (raId != null) {
+ c.setRealAuthor(raId);
+ }
if (range.getStartCharacter() != -1) {
c.setRange(range);
@@ -411,15 +423,15 @@
}
private Account.Id parseAuthor(byte[] note, MutableInteger curr,
- Change.Id changeId) throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, AUTHOR, changeId);
+ Change.Id changeId, String fieldName) throws ConfigInvalidException {
+ checkHeaderLineFormat(note, curr, fieldName, changeId);
int startOfAccountId =
RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
PersonIdent ident =
RawParseUtils.parsePersonIdent(note, startOfAccountId);
Account.Id aId = parseIdent(ident, changeId);
curr.value = RawParseUtils.nextLF(note, curr.value);
- return checkResult(aId, "comment author", changeId);
+ return checkResult(aId, fieldName, changeId);
}
private static int parseCommentLength(byte[] note, MutableInteger curr,
@@ -564,15 +576,10 @@
writer.print(formatTime(serverIdent, c.writtenOn));
writer.print("\n");
- PersonIdent ident = newIdent(
- accountCache.get(c.author.getId()).getAccount(),
- c.writtenOn, serverIdent, anonymousCowardName);
- StringBuilder name = new StringBuilder();
- PersonIdent.appendSanitized(name, ident.getName());
- name.append(" <");
- PersonIdent.appendSanitized(name, ident.getEmailAddress());
- name.append('>');
- appendHeaderField(writer, AUTHOR, name.toString());
+ appendIdent(writer, AUTHOR, c.author.getId(), c.writtenOn);
+ if (!c.getRealAuthor().equals(c.author)) {
+ appendIdent(writer, REAL_AUTHOR, c.getRealAuthor().getId(), c.writtenOn);
+ }
String parent = c.parentUuid;
if (parent != null) {
@@ -592,4 +599,17 @@
writer.print(c.message);
writer.print("\n\n");
}
+
+ private void appendIdent(PrintWriter writer, String header, Account.Id id,
+ Timestamp ts) {
+ PersonIdent ident = newIdent(
+ accountCache.get(id).getAccount(),
+ ts, serverIdent, anonymousCowardName);
+ StringBuilder name = new StringBuilder();
+ PersonIdent.appendSanitized(name, ident.getName());
+ name.append(" <");
+ PersonIdent.appendSanitized(name, ident.getEmailAddress());
+ name.append('>');
+ appendHeaderField(writer, header, name.toString());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index de37b72..a3ef2ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -22,6 +22,7 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -303,6 +304,7 @@
if (accountId != null) {
ownerId = accountId;
}
+ Account.Id realAccountId = parseRealAccountId(commit, accountId);
if (changeId == null) {
changeId = parseChangeId(commit);
@@ -316,7 +318,7 @@
originalSubject = currSubject;
}
- parseChangeMessage(psId, accountId, commit, ts);
+ parseChangeMessage(psId, accountId, realAccountId, commit, ts);
if (topic == null) {
topic = parseTopic(commit);
}
@@ -342,7 +344,7 @@
}
for (String line : commit.getFooterLineValues(FOOTER_LABEL)) {
- parseApproval(psId, accountId, ts, line);
+ parseApproval(psId, accountId, realAccountId, ts, line);
}
for (ReviewerStateInternal state : ReviewerStateInternal.values()) {
@@ -379,6 +381,16 @@
return parseOneFooter(commit, FOOTER_SUBJECT);
}
+ private Account.Id parseRealAccountId(ChangeNotesCommit commit,
+ Account.Id effectiveAccountId) throws ConfigInvalidException {
+ String realUser = parseOneFooter(commit, FOOTER_REAL_USER);
+ if (realUser == null) {
+ return effectiveAccountId;
+ }
+ PersonIdent ident = RawParseUtils.parsePersonIdent(realUser);
+ return noteUtil.parseIdent(ident, id);
+ }
+
private String parseTopic(ChangeNotesCommit commit)
throws ConfigInvalidException {
return parseOneFooter(commit, FOOTER_TOPIC);
@@ -565,7 +577,8 @@
}
private void parseChangeMessage(PatchSet.Id psId,
- Account.Id accountId, ChangeNotesCommit commit, Timestamp ts) {
+ Account.Id accountId, Account.Id realAccountId,
+ ChangeNotesCommit commit, Timestamp ts) {
byte[] raw = commit.getRawBuffer();
int size = raw.length;
Charset enc = RawParseUtils.parseEncoding(raw);
@@ -614,11 +627,10 @@
changeMessageStart, changeMessageEnd + 1);
ChangeMessage changeMessage = new ChangeMessage(
new ChangeMessage.Key(psId.getParentKey(), commit.name()),
- accountId,
- ts,
- psId);
+ accountId, ts, psId);
changeMessage.setMessage(changeMsgString);
changeMessage.setTag(tag);
+ changeMessage.setRealAuthor(realAccountId);
changeMessagesByPatchSet.put(psId, changeMessage);
allChangeMessages.add(changeMessage);
}
@@ -647,31 +659,45 @@
}
private void parseApproval(PatchSet.Id psId, Account.Id accountId,
- Timestamp ts, String line) throws ConfigInvalidException {
+ Account.Id realAccountId, Timestamp ts, String line)
+ throws ConfigInvalidException {
if (accountId == null) {
throw parseException(
"patch set %s requires an identified user as uploader", psId.get());
}
if (line.startsWith("-")) {
- parseRemoveApproval(psId, accountId, ts, line);
+ parseRemoveApproval(psId, accountId, realAccountId, ts, line);
} else {
- parseAddApproval(psId, accountId, ts, line);
+ parseAddApproval(psId, accountId, realAccountId, ts, line);
}
}
private void parseAddApproval(PatchSet.Id psId, Account.Id committerId,
- Timestamp ts, String line) throws ConfigInvalidException {
- Account.Id accountId;
+ Account.Id realAccountId, Timestamp ts, String line)
+ throws ConfigInvalidException {
+ // There are potentially 3 accounts involved here:
+ // 1. The account from the commit, which is the effective IdentifiedUser
+ // that produced the update.
+ // 2. The account in the label footer itself, which is used during submit
+ // to copy other users' labels to a new patch set.
+ // 3. The account in the Real-user footer, indicating that the whole
+ // update operation was executed by this user on behalf of the effective
+ // user.
+ Account.Id effectiveAccountId;
String labelVoteStr;
int s = line.indexOf(' ');
if (s > 0) {
+ // Account in the label line (2) becomes the effective ID of the
+ // approval. If there is a real user (3) different from the commit user
+ // (2), we actually don't store that anywhere in this case; it's more
+ // important to record that the real user (3) actually initiated submit.
labelVoteStr = line.substring(0, s);
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
checkFooter(ident != null, FOOTER_LABEL, line);
- accountId = noteUtil.parseIdent(ident, id);
+ effectiveAccountId = noteUtil.parseIdent(ident, id);
} else {
labelVoteStr = line;
- accountId = committerId;
+ effectiveAccountId = committerId;
}
LabelVote l;
@@ -687,30 +713,36 @@
PatchSetApproval psa = new PatchSetApproval(
new PatchSetApproval.Key(
psId,
- accountId,
+ effectiveAccountId,
new LabelId(l.label())),
l.value(),
ts);
psa.setTag(tag);
- ApprovalKey k = ApprovalKey.create(psId, accountId, l.label(), tag);
+ if (!Objects.equals(realAccountId, committerId)) {
+ psa.setRealAccountId(realAccountId);
+ }
+ ApprovalKey k =
+ ApprovalKey.create(psId, effectiveAccountId, l.label(), tag);
if (!approvals.containsKey(k)) {
approvals.put(k, psa);
}
}
private void parseRemoveApproval(PatchSet.Id psId, Account.Id committerId,
- Timestamp ts, String line) throws ConfigInvalidException {
- Account.Id accountId;
+ Account.Id realAccountId, Timestamp ts, String line)
+ throws ConfigInvalidException {
+ // See comments in parseAddApproval about the various users involved.
+ Account.Id effectiveAccountId;
String label;
int s = line.indexOf(' ');
if (s > 0) {
label = line.substring(1, s);
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
checkFooter(ident != null, FOOTER_LABEL, line);
- accountId = noteUtil.parseIdent(ident, id);
+ effectiveAccountId = noteUtil.parseIdent(ident, id);
} else {
label = line.substring(1);
- accountId = committerId;
+ effectiveAccountId = committerId;
}
try {
@@ -731,11 +763,14 @@
PatchSetApproval remove = new PatchSetApproval(
new PatchSetApproval.Key(
psId,
- accountId,
+ effectiveAccountId,
new LabelId(label)),
(short) 0,
ts);
- ApprovalKey k = ApprovalKey.create(psId, accountId, label, tag);
+ if (!Objects.equals(realAccountId, committerId)) {
+ remove.setRealAccountId(realAccountId);
+ }
+ ApprovalKey k = ApprovalKey.create(psId, effectiveAccountId, label, tag);
if (!approvals.containsKey(k)) {
approvals.put(k, remove);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 96cd4c6..3642dff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -27,6 +27,7 @@
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMISSION_ID;
@@ -82,6 +83,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -100,8 +102,13 @@
public interface Factory {
ChangeUpdate create(ChangeControl ctl);
ChangeUpdate create(ChangeControl ctl, Date when);
- ChangeUpdate create(Change change, @Nullable Account.Id accountId,
- PersonIdent authorIdent, Date when,
+
+ ChangeUpdate create(
+ Change change,
+ @Assisted("effective") @Nullable Account.Id accountId,
+ @Assisted("real") @Nullable Account.Id realAccountId,
+ PersonIdent authorIdent,
+ Date when,
Comparator<String> labelNameComparator);
@VisibleForTesting
@@ -218,12 +225,13 @@
RobotCommentUpdate.Factory robotCommentUpdateFactory,
ChangeNoteUtil noteUtil,
@Assisted Change change,
- @Assisted @Nullable Account.Id accountId,
+ @Assisted("effective") @Nullable Account.Id accountId,
+ @Assisted("real") @Nullable Account.Id realAccountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator) {
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
- accountId, authorIdent, when);
+ accountId, realAccountId, authorIdent, when);
this.accountCache = accountCache;
this.draftUpdateFactory = draftUpdateFactory;
this.robotCommentUpdateFactory = robotCommentUpdateFactory;
@@ -345,11 +353,11 @@
if (draftUpdate == null) {
ChangeNotes notes = getNotes();
if (notes != null) {
- draftUpdate =
- draftUpdateFactory.create(notes, accountId, authorIdent, when);
+ draftUpdate = draftUpdateFactory.create(
+ notes, accountId, realAccountId, authorIdent, when);
} else {
draftUpdate = draftUpdateFactory.create(
- getChange(), accountId, authorIdent, when);
+ getChange(), accountId, realAccountId, authorIdent, when);
}
}
return draftUpdate;
@@ -360,24 +368,16 @@
if (robotCommentUpdate == null) {
ChangeNotes notes = getNotes();
if (notes != null) {
- robotCommentUpdate =
- robotCommentUpdateFactory.create(notes, accountId, authorIdent, when);
+ robotCommentUpdate = robotCommentUpdateFactory.create(
+ notes, accountId, realAccountId, authorIdent, when);
} else {
robotCommentUpdate = robotCommentUpdateFactory.create(
- getChange(), accountId, authorIdent, when);
+ getChange(), accountId, realAccountId, authorIdent, when);
}
}
return robotCommentUpdate;
}
- private void verifyComment(Comment c) {
- checkArgument(c.revId != null, "RevId required for comment: %s", c);
- checkArgument(c.author.getId().equals(getAccountId()),
- "The author for the following comment does not match the author of"
- + " this ChangeUpdate (%s): %s", getAccountId(), c);
-
- }
-
public void setTopic(String topic) {
this.topic = Strings.nullToEmpty(topic);
}
@@ -646,10 +646,8 @@
addFooter(msg, FOOTER_SUBMITTED_WITH)
.append(label.status).append(": ").append(label.label);
if (label.appliedBy != null) {
- PersonIdent ident =
- newIdent(accountCache.get(label.appliedBy).getAccount(), when);
- msg.append(": ").append(ident.getName())
- .append(" <").append(ident.getEmailAddress()).append('>');
+ msg.append(": ");
+ addIdent(msg, label.appliedBy);
}
msg.append('\n');
}
@@ -657,6 +655,11 @@
}
}
+ if (!Objects.equals(accountId, realAccountId)) {
+ addFooter(msg, FOOTER_REAL_USER);
+ addIdent(msg, realAccountId).append('\n');
+ }
+
cb.setMessage(msg.toString());
try {
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
index fd47d02..9744632 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.reviewdb.client.RefNames.robotCommentsRef;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -55,12 +54,21 @@
* <p>
* This class is not thread safe.
*/
-public class RobotCommentUpdate extends AbstractChangeUpdate{
+public class RobotCommentUpdate extends AbstractChangeUpdate {
public interface Factory {
- RobotCommentUpdate create(ChangeNotes notes, Account.Id accountId,
- PersonIdent authorIdent, Date when);
- RobotCommentUpdate create(Change change, Account.Id accountId,
- PersonIdent authorIdent, Date when);
+ RobotCommentUpdate create(
+ ChangeNotes notes,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
+ PersonIdent authorIdent,
+ Date when);
+
+ RobotCommentUpdate create(
+ Change change,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
+ PersonIdent authorIdent,
+ Date when);
}
private List<RobotComment> put = new ArrayList<>();
@@ -72,11 +80,12 @@
NotesMigration migration,
ChangeNoteUtil noteUtil,
@Assisted ChangeNotes notes,
- @Assisted Account.Id accountId,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, notes, null,
- accountId, authorIdent, when);
+ accountId, realAccountId, authorIdent, when);
}
@AssistedInject
@@ -86,11 +95,12 @@
NotesMigration migration,
ChangeNoteUtil noteUtil,
@Assisted Change change,
- @Assisted Account.Id accountId,
+ @Assisted("effective") Account.Id accountId,
+ @Assisted("real") Account.Id realAccountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
- accountId, authorIdent, when);
+ accountId, realAccountId, authorIdent, when);
}
public void putComment(RobotComment c) {
@@ -98,12 +108,6 @@
put.add(c);
}
- private void verifyComment(RobotComment comment) {
- checkArgument(comment.author.getId().equals(accountId),
- "The author for the following comment does not match the author of"
- + " this RobotCommentUpdate (%s): %s", accountId, comment);
- }
-
private CommitBuilder storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, OrmException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
index a00334d..3aba7c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
@@ -23,8 +23,8 @@
private PatchSetApproval psa;
ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) {
- super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted(),
- changeCreatedOn, psa.getTag());
+ super(psa.getPatchSetId(), psa.getAccountId(), psa.getRealAccountId(),
+ psa.getGranted(), changeCreatedOn, psa.getTag());
this.psa = psa;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
index a990e19..ed5cd8b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
@@ -31,17 +31,12 @@
private static final Pattern TOPIC_REMOVED_REGEXP =
Pattern.compile("^Topic (.+) removed$");
- private static final Pattern STATUS_ABANDONED_REGEXP =
- Pattern.compile("^Abandoned(\n.*)*$");
- private static final Pattern STATUS_RESTORED_REGEXP =
- Pattern.compile("^Restored(\n.*)*$");
-
private final ChangeMessage message;
private final Change noteDbChange;
ChangeMessageEvent(ChangeMessage message, Change noteDbChange,
Timestamp changeCreatedOn) {
- super(message.getPatchSetId(), message.getAuthor(),
+ super(message.getPatchSetId(), message.getAuthor(), message.getRealAuthor(),
message.getWrittenOn(), changeCreatedOn, message.getTag());
this.message = message;
this.noteDbChange = noteDbChange;
@@ -57,7 +52,6 @@
checkUpdate(update);
update.setChangeMessage(message.getMessage());
setTopic(update);
- setStatus(update);
}
private void setTopic(ChangeUpdate update) {
@@ -86,21 +80,4 @@
noteDbChange.setTopic(null);
}
}
-
- private void setStatus(ChangeUpdate update) {
- String msg = message.getMessage();
- if (msg == null) {
- return;
- }
- if (STATUS_ABANDONED_REGEXP.matcher(msg).matches()) {
- update.setStatus(Change.Status.ABANDONED);
- noteDbChange.setStatus(Change.Status.ABANDONED);
- return;
- }
-
- if (STATUS_RESTORED_REGEXP.matcher(msg).matches()) {
- update.setStatus(Change.Status.NEW);
- noteDbChange.setStatus(Change.Status.NEW);
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index d4caa6c..1916610 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -355,19 +355,16 @@
Change noteDbChange = new Change(null, null, null, null, null);
for (ChangeMessage msg : bundle.getChangeMessages()) {
- if (msg.getPatchSetId() == null) {
- // No dependency necessary; will get assigned to most recent patch set
- // in sortAndFillEvents.
- events.add(
- new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
- continue;
+ List<Event> msgEvents = parseChangeMessage(msg, change, noteDbChange);
+ if (msg.getPatchSetId() != null) {
+ PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
+ if (pse != null) {
+ for (Event e : msgEvents) {
+ e.addDep(pse);
+ }
+ }
}
- PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
- if (pse != null) {
- events.add(
- new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn())
- .addDep(pse));
- }
+ events.addAll(msgEvents);
}
sortAndFillEvents(change, noteDbChange, events, minPsNum);
@@ -396,6 +393,18 @@
}
}
+ private List<Event> parseChangeMessage(ChangeMessage msg, Change change,
+ Change noteDbChange) {
+ List<Event> events = new ArrayList<>(2);
+ events.add(new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
+ Optional<StatusChangeEvent> sce =
+ StatusChangeEvent.parseFromMessage(msg, change, noteDbChange);
+ if (sce.isPresent()) {
+ events.add(sce.get());
+ }
+ return events;
+ }
+
private static Integer getMinPatchSetNum(ChangeBundle bundle) {
Integer minPsNum = null;
for (PatchSet ps : bundle.getPatchSets()) {
@@ -418,13 +427,14 @@
private void sortAndFillEvents(Change change, Change noteDbChange,
List<Event> events, Integer minPsNum) {
- new EventSorter(events).sort();
events.add(new FinalUpdatesEvent(change, noteDbChange));
+ new EventSorter(events).sort();
// Ensure the first event in the list creates the change, setting the author
// and any required footers.
Event first = events.get(0);
- if (first instanceof PatchSetEvent && change.getOwner().equals(first.who)) {
+ if (first instanceof PatchSetEvent
+ && change.getOwner().equals(first.user)) {
((PatchSetEvent) first).createChange = true;
} else {
events.add(0, new CreateChangeEvent(change, minPsNum));
@@ -483,6 +493,7 @@
ChangeUpdate update = updateFactory.create(
change,
events.getAccountId(),
+ events.getRealAccountId(),
newAuthorIdent(events),
events.getWhen(),
labelNameComparator);
@@ -505,6 +516,7 @@
ChangeDraftUpdate update = draftUpdateFactory.create(
change,
events.getAccountId(),
+ events.getRealAccountId(),
newAuthorIdent(events),
events.getWhen());
update.setPatchSetId(events.getPatchSetId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java
index 51ade79..8f461a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java
@@ -34,7 +34,7 @@
CommentEvent(Comment c, Change change, PatchSet ps,
PatchListCache cache) {
super(CommentsUtil.getCommentPsId(change.getId(), c), c.author.getId(),
- c.writtenOn, change.getCreatedOn(), c.tag);
+ c.getRealAuthor().getId(), c.writtenOn, change.getCreatedOn(), c.tag);
this.c = c;
this.change = change;
this.ps = ps;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
index 886f6c4..b020911 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
@@ -37,8 +37,8 @@
}
CreateChangeEvent(Change change, Integer minPsNum) {
- super(psId(change, minPsNum), change.getOwner(), change.getCreatedOn(),
- change.getCreatedOn(), null);
+ super(psId(change, minPsNum), change.getOwner(), change.getOwner(),
+ change.getCreatedOn(), change.getCreatedOn(), null);
this.change = change;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java
index 360dfff..2938480 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java
@@ -34,7 +34,7 @@
DraftCommentEvent(Comment c, Change change, PatchSet ps,
PatchListCache cache) {
super(CommentsUtil.getCommentPsId(change.getId(), c), c.author.getId(),
- c.writtenOn, change.getCreatedOn(), c.tag);
+ c.getRealAuthor().getId(), c.writtenOn, change.getCreatedOn(), c.tag);
this.c = c;
this.change = change;
this.ps = ps;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
index d04d1b5..78df2d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
@@ -36,17 +36,24 @@
// NOTE: EventList only supports direct subclasses, not an arbitrary
// hierarchy.
- final Account.Id who;
+ final Account.Id user;
+ final Account.Id realUser;
final String tag;
final boolean predatesChange;
final List<Event> deps;
Timestamp when;
PatchSet.Id psId;
- protected Event(PatchSet.Id psId, Account.Id who, Timestamp when,
- Timestamp changeCreatedOn, String tag) {
+ protected Event(
+ PatchSet.Id psId,
+ Account.Id effectiveUser,
+ Account.Id realUser,
+ Timestamp when,
+ Timestamp changeCreatedOn,
+ String tag) {
this.psId = psId;
- this.who = who;
+ this.user = effectiveUser;
+ this.realUser = realUser != null ? realUser : effectiveUser;
this.tag = tag;
// Truncate timestamps at the change's createdOn timestamp.
predatesChange = when.before(changeCreatedOn);
@@ -61,9 +68,9 @@
checkState(when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS,
"event at %s outside update window starting at %s",
when, update.getWhen());
- checkState(Objects.equals(update.getNullableAccountId(), who),
+ checkState(Objects.equals(update.getNullableAccountId(), user),
"cannot apply event by %s to update by %s",
- who, update.getNullableAccountId());
+ user, update.getNullableAccountId());
}
Event addDep(Event e) {
@@ -79,15 +86,12 @@
abstract void apply(ChangeUpdate update) throws OrmException, IOException;
- protected boolean isPatchSet() {
- return false;
- }
-
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("psId", psId)
- .add("who", who)
+ .add("effectiveUser", user)
+ .add("realUser", realUser)
.add("when", when)
.toString();
}
@@ -95,12 +99,23 @@
@Override
public int compareTo(Event other) {
return ComparisonChain.start()
+ .compareFalseFirst(this.isFinalUpdates(), other.isFinalUpdates())
.compare(this.when, other.when)
.compareTrueFirst(isPatchSet(), isPatchSet())
.compareTrueFirst(this.predatesChange, other.predatesChange)
- .compare(this.who, other.who, ReviewDbUtil.intKeyOrdering())
+ .compare(this.user, other.user,
+ ReviewDbUtil.intKeyOrdering())
+ .compare(this.realUser, other.realUser, ReviewDbUtil.intKeyOrdering())
.compare(this.psId, other.psId,
ReviewDbUtil.intKeyOrdering().nullsLast())
.result();
}
+
+ private boolean isPatchSet() {
+ return this instanceof PatchSetEvent;
+ }
+
+ private boolean isFinalUpdates() {
+ return this instanceof FinalUpdatesEvent;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
index 398657b..4f6e2e8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
@@ -49,7 +49,8 @@
}
Event last = getLast();
- if (!Objects.equals(e.who, last.who)
+ if (!Objects.equals(e.user, last.user)
+ || !Objects.equals(e.realUser, last.realUser)
|| !e.psId.equals(last.psId)
|| !Objects.equals(e.tag, last.tag)) {
return false; // Different patch set, author, or tag.
@@ -93,10 +94,19 @@
}
Account.Id getAccountId() {
- Account.Id id = get(0).who;
+ Account.Id id = get(0).user;
for (int i = 1; i < size(); i++) {
- checkState(Objects.equals(id, get(i).who),
- "mismatched users in EventList: %s != %s", id, get(i).who);
+ checkState(Objects.equals(id, get(i).user),
+ "mismatched users in EventList: %s != %s", id, get(i).user);
+ }
+ return id;
+ }
+
+ Account.Id getRealAccountId() {
+ Account.Id id = get(0).realUser;
+ for (int i = 1; i < size(); i++) {
+ checkState(Objects.equals(id, get(i).realUser),
+ "mismatched real users in EventList: %s != %s", id, get(i).realUser);
}
return id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
index 3080be7..9babc28 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
@@ -25,7 +25,7 @@
private final Change noteDbChange;
FinalUpdatesEvent(Change change, Change noteDbChange) {
- super(change.currentPatchSetId(), change.getOwner(),
+ super(change.currentPatchSetId(), change.getOwner(), change.getOwner(),
change.getLastUpdatedOn(), change.getCreatedOn(), null);
this.change = change;
this.noteDbChange = noteDbChange;
@@ -46,9 +46,14 @@
// TODO(dborowitz): Stamp approximate approvals at this time.
update.fixStatus(change.getStatus());
}
- if (change.getSubmissionId() != null) {
+ if (change.getSubmissionId() != null
+ && noteDbChange.getSubmissionId() == null) {
update.setSubmissionId(change.getSubmissionId());
}
+ if (!Objects.equals(change.getAssignee(), noteDbChange.getAssignee())) {
+ // TODO(dborowitz): Parse intermediate values out from messages.
+ update.setAssignee(change.getAssignee());
+ }
if (!update.isEmpty()) {
update.setSubjectForCommit("Final NoteDb migration updates");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
index 21b3b6e..f5bea3e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
@@ -27,7 +27,7 @@
HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when,
Set<String> hashtags, Timestamp changeCreatdOn) {
- super(psId, who, when, changeCreatdOn,
+ super(psId, who, who, when, changeCreatdOn,
// Somewhat confusingly, hashtags do not use the setTag method on
// AbstractChangeUpdate, so pass null as the tag.
null);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
index 5baddd3..abac1b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
@@ -35,7 +35,7 @@
boolean createChange;
PatchSetEvent(Change change, PatchSet ps, RevWalk rw) {
- super(ps.getId(), ps.getUploader(), ps.getCreatedOn(),
+ super(ps.getId(), ps.getUploader(), ps.getUploader(), ps.getCreatedOn(),
change.getCreatedOn(), null);
this.change = change;
this.ps = ps;
@@ -66,11 +66,6 @@
}
}
- @Override
- protected boolean isPatchSet() {
- return true;
- }
-
private void setRevision(ChangeUpdate update, PatchSet ps)
throws IOException {
String rev = ps.getRevision().get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
index ef9c5c6..c82f108 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
@@ -34,7 +34,13 @@
// (although as an implementation detail they were in ReviewDb). Just
// use the latest patch set at the time of the event.
null,
- reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null);
+ reviewer.getColumnKey(),
+ // TODO(dborowitz): Real account ID shouldn't really matter for
+ // reviewers, but we might have to deal with this to avoid ChangeBundle
+ // diffs when run against real data.
+ reviewer.getColumnKey(),
+ reviewer.getValue(),
+ changeCreatedOn, null);
this.reviewer = reviewer;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java
new file mode 100644
index 0000000..c7632e8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2016 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.notedb.rebuild;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gwtorm.server.OrmException;
+
+import java.sql.Timestamp;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+class StatusChangeEvent extends Event {
+ private static final ImmutableMap<Change.Status, Pattern> PATTERNS =
+ ImmutableMap.of(
+ Change.Status.ABANDONED, Pattern.compile("^Abandoned(\n.*)*$"),
+ Change.Status.MERGED, Pattern.compile(
+ "^Change has been successfully"
+ + " (merged|cherry-picked|rebased|pushed).*$"),
+ Change.Status.NEW, Pattern.compile("^Restored(\n.*)*$"));
+
+ static Optional<StatusChangeEvent> parseFromMessage(ChangeMessage message,
+ Change change, Change noteDbChange) {
+ String msg = message.getMessage();
+ if (msg == null) {
+ return Optional.absent();
+ }
+ for (Map.Entry<Change.Status, Pattern> e : PATTERNS.entrySet()) {
+ if (e.getValue().matcher(msg).matches()) {
+ return Optional.of(new StatusChangeEvent(
+ message, change, noteDbChange, e.getKey()));
+ }
+ }
+ return Optional.absent();
+ }
+
+ private final Change change;
+ private final Change noteDbChange;
+ private final Change.Status status;
+
+ private StatusChangeEvent(ChangeMessage message, Change change,
+ Change noteDbChange, Change.Status status) {
+ this(message.getPatchSetId(), message.getAuthor(),
+ message.getWrittenOn(), change, noteDbChange, message.getTag(),
+ status);
+ }
+
+ private StatusChangeEvent(PatchSet.Id psId, Account.Id author,
+ Timestamp when, Change change, Change noteDbChange,
+ String tag, Change.Status status) {
+ super(psId, author, author, when, change.getCreatedOn(), tag);
+ this.change = change;
+ this.noteDbChange = noteDbChange;
+ this.status = status;
+ }
+
+ @Override
+ boolean uniquePerUpdate() {
+ return true;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ void apply(ChangeUpdate update) throws OrmException {
+ checkUpdate(update);
+ update.fixStatus(status);
+ noteDbChange.setStatus(status);
+ if (status == Change.Status.MERGED) {
+ update.setSubmissionId(change.getSubmissionId());
+ noteDbChange.setSubmissionId(change.getSubmissionId());
+ }
+ }
+}
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 be9bbad..73d7b1e 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
@@ -195,7 +195,7 @@
Iterable<String> exports;
private ClassData(Iterable<String> exports) {
- super(Opcodes.ASM4);
+ super(Opcodes.ASM5);
this.exports = exports;
}
@@ -264,7 +264,7 @@
private abstract static class AbstractAnnotationVisitor extends
AnnotationVisitor {
AbstractAnnotationVisitor() {
- super(Opcodes.ASM4);
+ super(Opcodes.ASM5);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index ab14d8b..a7a8191 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -33,7 +33,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_132> C = Schema_132.class;
+ public static final Class<Schema_133> C = Schema_133.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_133.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_133.java
new file mode 100644
index 0000000..31d330b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_133.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2016 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.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_133 extends SchemaVersion {
+ @Inject
+ Schema_133(Provider<Schema_132> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
new file mode 100644
index 0000000..33dd7b8
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2016 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.
+ */
+
+{namespace com.google.gerrit.server.mail.template}
+
+/**
+ * @param change
+ * @param email
+ * @param fromName
+ */
+{template .MergedHtml autoescape="strict" kind="html"}
+ <p>
+ {$fromName} has submitted this change and it was merged.
+ </p>
+
+ {if $email.changeUrl}
+ <p>
+ {call .ViewChangeButton data="all" /}
+ </p>
+ {/if}
+
+ <p>
+ Change subject: {$change.subject}
+ </p>
+ <hr/>
+
+ <pre>
+ {$email.changeDetail}
+ </pre>
+
+ <pre>
+ {$email.approvals}
+ </pre>
+
+ {if $email.includeDiff}
+ <pre>
+ {$email.unifiedDiff}
+ </pre>
+ {/if}
+{/template}
\ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
index 256be9c..d76c239 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
@@ -37,7 +37,7 @@
fi
curl --digest -u $gerrituser -w '%{http_code}' -o preview \
- $server/a/changes/$changeId/revisions/current/preview_submit?format=zip >http_code
+ $server/a/changes/$changeId/revisions/current/preview_submit?format=tgz >http_code
if ! grep 200 http_code >/dev/null
then
# error out:
@@ -45,9 +45,9 @@
cat preview
echo
else
- # valid zip file, extract and obtain a bundle for each project
+ # valid tgz file, extract and obtain a bundle for each project
mkdir tmp-bundles
- unzip preview -d tmp-bundles
+ (cd tmp-bundles && tar -zxf ../preview)
for project in $(cd tmp-bundles && find -type f)
do
# Projects may contain slashes, so create the required
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 393b8f8..ea33e65 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -46,6 +46,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.config.GerritServerId;
@@ -1666,6 +1667,59 @@
}
@Test
+ public void patchLineCommentNotesFormatRealAuthor() throws Exception {
+ Change c = newChange();
+ CurrentUser ownerAsOtherUser =
+ userFactory.runAs(null, otherUserId, changeOwner);
+ ChangeUpdate update = newUpdate(c, ownerAsOtherUser);
+ String uuid = "uuid";
+ String message = "comment";
+ CommentRange range = new CommentRange(1, 1, 2, 1);
+ Timestamp time = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
+ RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+
+ Comment comment = newComment(psId, "file", uuid, range,
+ range.getEndLine(), otherUser, null, time, message, (short) 1,
+ revId.get());
+ comment.setRealAuthor(changeOwner.getAccountId());
+ update.setPatchSetId(psId);
+ update.putComment(Status.PUBLISHED, comment);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+
+ try (RevWalk walk = new RevWalk(repo)) {
+ ArrayList<Note> notesInTree =
+ Lists.newArrayList(notes.revisionNoteMap.noteMap.iterator());
+ Note note = Iterables.getOnlyElement(notesInTree);
+
+ byte[] bytes =
+ walk.getObjectReader().open(
+ note.getData(), Constants.OBJ_BLOB).getBytes();
+ String noteString = new String(bytes, UTF_8);
+
+ if (!testJson()) {
+ assertThat(noteString).isEqualTo(
+ "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+ + "Patch-set: 1\n"
+ + "File: file\n"
+ + "\n"
+ + "1:1-2:1\n"
+ + ChangeNoteUtil.formatTime(serverIdent, time) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "Real-author: Change Owner <1@gerrit>\n"
+ + "UUID: uuid\n"
+ + "Bytes: 7\n"
+ + "comment\n"
+ + "\n");
+ }
+ }
+ assertThat(notes.getComments())
+ .isEqualTo(ImmutableMultimap.of(revId, comment));
+ }
+
+ @Test
public void patchLineCommentNotesFormatWeirdUser() throws Exception {
Account account = new Account(new Account.Id(3), TimeUtil.nowTs());
account.setFullName("Weird\n\u0002<User>\n");
@@ -2355,6 +2409,21 @@
assertThat(comments.get(1).message).isEqualTo("comment 2");
}
+ @Test
+ public void realUser() throws Exception {
+ Change c = newChange();
+ CurrentUser ownerAsOtherUser =
+ userFactory.runAs(null, otherUserId, changeOwner);
+ ChangeUpdate update = newUpdate(c, ownerAsOtherUser);
+ update.setChangeMessage("Message on behalf of other user");
+ update.commit();
+
+ ChangeMessage msg = Iterables.getLast(newNotes(c).getChangeMessages());
+ assertThat(msg.getMessage()).isEqualTo("Message on behalf of other user");
+ assertThat(msg.getAuthor()).isEqualTo(otherUserId);
+ assertThat(msg.getRealAuthor()).isEqualTo(changeOwner.getAccountId());
+ }
+
private boolean testJson() {
return noteUtil.getWriteJson();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 5a8c12b..b674159 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -22,6 +22,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.util.RequestId;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.TestChanges;
@@ -332,6 +333,29 @@
update.getResult());
}
+ @Test
+ public void realUser() throws Exception {
+ Change c = newChange();
+ CurrentUser ownerAsOtherUser =
+ userFactory.runAs(null, otherUserId, changeOwner);
+ ChangeUpdate update = newUpdate(c, ownerAsOtherUser);
+ update.setChangeMessage("Message on behalf of other user");
+ update.commit();
+
+ RevCommit commit = parseCommit(update.getResult());
+ PersonIdent author = commit.getAuthorIdent();
+ assertThat(author.getName()).isEqualTo("Other Account");
+ assertThat(author.getEmailAddress()).isEqualTo("2@gerrit");
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Message on behalf of other user\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Real-user: Change Owner <1@gerrit>\n",
+ commit);
+ }
+
private RevCommit parseCommit(ObjectId id) throws Exception {
if (id instanceof RevCommit) {
return (RevCommit) id;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
index 969adf0..f5fdf13 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
@@ -37,7 +37,7 @@
protected TestEvent(Timestamp when) {
super(
new PatchSet.Id(new Change.Id(1), 1),
- new Account.Id(1000),
+ new Account.Id(1000), new Account.Id(1000),
when, changeCreatedOn, null);
}
diff --git a/lib/asciidoctor/BUILD b/lib/asciidoctor/BUILD
index 4b4e958..d1b98f8 100644
--- a/lib/asciidoctor/BUILD
+++ b/lib/asciidoctor/BUILD
@@ -1,3 +1,10 @@
+java_binary(
+ name = "asciidoc",
+ main_class = "AsciiDoctor",
+ runtime_deps = [":asciidoc_lib"],
+ visibility = ["//visibility:public"],
+)
+
java_library(
name = "asciidoc_lib",
srcs = ["java/AsciiDoctor.java"],
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 8e18feb1..c66c4aa 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -24,12 +24,14 @@
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
-
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -41,6 +43,7 @@
private static final String DOCTYPE = "article";
private static final String ERUBY = "erb";
+ private static final String REVNUMBER_NAME = "revnumber";
@Option(name = "-b", usage = "set output format backend")
private String backend = "html5";
@@ -60,13 +63,26 @@
@Option(name = "--tmp", usage = "temporary output path")
private File tmpdir;
+ @Option(name = "--mktmp", usage = "create a temporary output path")
+ private boolean mktmp;
+
@Option(name = "-a", usage =
"a list of attributes, in the form key or key=value pair")
private List<String> attributes = new ArrayList<>();
+ @Option(name = "--bazel", usage =
+ "bazel mode: generate multiple output files instead of a single zip file")
+ private boolean bazel;
+
+ @Option(name = "--revnumber-file", usage =
+ "the file contains revnumber string")
+ private File revnumberFile;
+
@Argument(usage = "input files")
private List<String> inputFiles = new ArrayList<>();
+ private String revnumber;
+
public static String mapInFileToOutFile(
String inFile, String inExt, String outExt) {
String basename = new File(inFile).getName();
@@ -82,19 +98,26 @@
return basename + outExt;
}
- private Options createOptions(File outputFile) {
+ private Options createOptions(File base, File outputFile) {
OptionsBuilder optionsBuilder = OptionsBuilder.options();
- optionsBuilder.backend(backend).docType(DOCTYPE).eruby(ERUBY)
- .safe(SafeMode.UNSAFE).baseDir(basedir);
- // XXX(fishywang): ideally we should just output to a string and add the
- // content into zip. But asciidoctor will actually ignore all attributes if
- // not output to a file. So we *have* to output to a file then read the
- // content of the file into zip.
- optionsBuilder.toFile(outputFile);
+ optionsBuilder
+ .backend(backend)
+ .docType(DOCTYPE)
+ .eruby(ERUBY)
+ .safe(SafeMode.UNSAFE)
+ .baseDir(base)
+ // XXX(fishywang): ideally we should just output to a string and add the
+ // content into zip. But asciidoctor will actually ignore all attributes
+ // if not output to a file. So we *have* to output to a file then read
+ // the content of the file into zip.
+ .toFile(outputFile);
AttributesBuilder attributesBuilder = AttributesBuilder.attributes();
attributesBuilder.attributes(getAttributes());
+ if (revnumber != null) {
+ attributesBuilder.attribute(REVNUMBER_NAME, revnumber);
+ }
optionsBuilder.attributes(attributesBuilder.get());
return optionsBuilder.get();
@@ -133,31 +156,52 @@
return;
}
- try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(zipFile))) {
- for (String inputFile : inputFiles) {
- if (!inputFile.endsWith(inExt)) {
- // We have to use UNSAFE mode in order to make embedding work. But in
- // UNSAFE mode we'll also need css file in the same directory, so we
- // have to add css files into the SRCS.
- continue;
- }
-
- String outName = mapInFileToOutFile(inputFile, inExt, outExt);
- File out = new File(tmpdir, outName);
- out.getParentFile().mkdirs();
- Options options = createOptions(out);
- renderInput(options, new File(inputFile));
- zipFile(out, outName, zip);
+ if (revnumberFile != null) {
+ try (BufferedReader reader =
+ new BufferedReader(new FileReader(revnumberFile))) {
+ revnumber = reader.readLine();
}
+ }
- File[] cssFiles = tmpdir.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.endsWith(".css");
+ if (mktmp) {
+ tmpdir = Files.createTempDirectory("asciidoctor-").toFile();
+ }
+
+ if (bazel) {
+ renderFiles(inputFiles, null);
+ } else {
+ try (ZipOutputStream zip =
+ new ZipOutputStream(new FileOutputStream(zipFile))) {
+ renderFiles(inputFiles, zip);
+
+ File[] cssFiles = tmpdir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".css");
+ }
+ });
+ for (File css : cssFiles) {
+ zipFile(css, css.getName(), zip);
}
- });
- for (File css : cssFiles) {
- zipFile(css, css.getName(), zip);
+ }
+ }
+ }
+
+ private void renderFiles(List<String> inputFiles, ZipOutputStream zip)
+ throws IOException {
+ Asciidoctor asciidoctor = JRubyAsciidoctor.create();
+ for (String inputFile : inputFiles) {
+ String outName = mapInFileToOutFile(inputFile, inExt, outExt);
+ File out = bazel ? new File(outName) : new File(tmpdir, outName);
+ if (!bazel) {
+ out.getParentFile().mkdirs();
+ }
+ File input = new File(inputFile);
+ Options options =
+ createOptions(basedir != null ? basedir : input.getParentFile(), out);
+ asciidoctor.renderFile(input, options);
+ if (zip != null) {
+ zipFile(out, outName, zip);
}
}
}
@@ -171,11 +215,6 @@
zip.closeEntry();
}
- private void renderInput(Options options, File inputFile) {
- Asciidoctor asciidoctor = JRubyAsciidoctor.create();
- asciidoctor.renderFile(inputFile, options);
- }
-
public static void main(String[] args) {
try {
new AsciiDoctor().invoke(args);
diff --git a/lib/js/BUCK b/lib/js/BUCK
index 1c46d35..bb31b94 100644
--- a/lib/js/BUCK
+++ b/lib/js/BUCK
@@ -328,10 +328,10 @@
bower_component(
name = 'polymer',
package = 'polymer/polymer',
- version = '1.4.0',
+ version = '1.7.0',
deps = [':webcomponentsjs'],
license = 'polymer',
- sha1 = 'b84725939ead7c7bdf9917b065f68ef8dc790d06',
+ sha1 = 'e70caa58fdee0ce51c805d548f544f74cc27d143',
)
bower_component(
diff --git a/lib/prolog/prolog.bzl b/lib/prolog/prolog.bzl
index 3afb031..d4e9e08 100644
--- a/lib/prolog/prolog.bzl
+++ b/lib/prolog/prolog.bzl
@@ -22,7 +22,7 @@
genrule2(
name = name + '__pl2j',
cmd = '$(location //lib/prolog:compiler_bin) ' +
- '$$TMP $@ ' +
+ '$$(dirname $@) $@ ' +
'$(SRCS)',
srcs = srcs,
tools = ['//lib/prolog:compiler_bin'],
diff --git a/plugins/BUILD b/plugins/BUILD
new file mode 100644
index 0000000..ffa0713
--- /dev/null
+++ b/plugins/BUILD
@@ -0,0 +1,22 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+
+CORE = [
+ 'commit-message-length-validator',
+ 'download-commands',
+ 'hooks',
+ 'replication',
+ 'reviewnotes',
+ 'singleusergroup'
+]
+
+genrule2(
+ name = 'core',
+ srcs = ['//plugins/%s:%s_deploy.jar' % (n, n) for n in CORE],
+ cmd = 'mkdir -p $$TMP/WEB-INF/plugins;' +
+ 'for s in $(SRCS) ; do ' +
+ 'ln -s $$ROOT/$$s $$TMP/WEB-INF/plugins;done;' +
+ 'cd $$TMP;' +
+ 'zip -qr $$ROOT/$@ .',
+ out = 'core.zip',
+ visibility = ['//visibility:public'],
+)
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 474e06c..76b9115 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 474e06cccb1a6a821e19f70b8e03aa1f816ff219
+Subproject commit 76b9115b830cab453c12dd9014f5130c7b7f2ce5
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index 7a943fc..09981c0 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit 7a943fc5b1052068842c13793754cf161b45e370
+Subproject commit 09981c0638f7241a4f435baaa96bd6112a1edaa9
diff --git a/plugins/download-commands b/plugins/download-commands
index e5f1a9b..6326db6 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit e5f1a9ba057e9b287a7dd2b7e0136eaef183aa6c
+Subproject commit 6326db67dfa45b13a0c427643bbfa617c18855d7
diff --git a/plugins/replication b/plugins/replication
index 3103cdc..b3606eb 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 3103cdc028dfb1d7bc51883981cad67d94e98352
+Subproject commit b3606eb38eb8edc166260184177e68386539381a
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index eddd8bc..e9c66c6 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit eddd8bc74b180cf80e7ebae2c3f73157a1e7118e
+Subproject commit e9c66c6f08edb641d3c935c2fdcaa3fbf3a85d29
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 5f240eb..e985959 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 5f240eba762f7447ad12994ce6988ee976bb5c3b
+Subproject commit e9859591be48a157c7114d5f3c6acdf27384a408
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 383fb50..1e548d5 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -13,9 +13,21 @@
All other platforms: [download from
nodejs.org](https://nodejs.org/en/download/).
-## Optional: installing [go](https://golang.org/)
+## Installing [Buck](https://buckbuild.com/)
-This is only required for running the ```run-server.sh``` script for testing. See below.
+Follow the instructions
+[here](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_installation)
+to get and install Buck.
+
+## Local UI, Production Data
+
+This is a quick and easy way to test your local changes against real data.
+Unfortunately, you can't sign in, so testing certain features will require
+you to use the "test data" technique described below.
+
+### Installing [go](https://golang.org/)
+
+This is required for running the `run-server.sh` script below.
```sh
# Debian/Ubuntu
@@ -27,18 +39,18 @@
All other platforms: [download from golang.org](https://golang.org/)
-# Add [go] to your path
+Then add go to your path:
```
PATH=$PATH:/usr/local/go/bin
```
-## Local UI, Production Data
+### Running the server
To test the local UI against gerrit-review.googlesource.com:
```sh
-./polygerrit-ui/run-server.sh
+./run-server.sh
```
Then visit http://localhost:8081
@@ -47,10 +59,8 @@
One-time setup:
-1. [Install Buck](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_installation)
- for building Gerrit.
-2. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_gerrit_development_war_file)
- and set up a local test site. Docs
+1. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_gerrit_development_war_file)
+2. Set up a local test site. Docs
[here](https://gerrit-review.googlesource.com/Documentation/install-quick.html) and
[here](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init).
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
similarity index 94%
rename from polygerrit-ui/app/behaviors/gr-path-list-behavior.html
rename to polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
index fefecd4..fa8289f 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
@@ -31,11 +31,11 @@
var aLastDotIndex = a.lastIndexOf('.');
var aExt = a.substr(aLastDotIndex + 1);
- var aFile = a.substr(0, aLastDotIndex);
+ var aFile = a.substr(0, aLastDotIndex) || a;
var bLastDotIndex = b.lastIndexOf('.');
var bExt = b.substr(bLastDotIndex + 1);
- var bFile = b.substr(0, bLastDotIndex);
+ var bFile = b.substr(0, bLastDotIndex) || b;
// Sort header files above others with the same base name.
var headerExts = ['h', 'hxx', 'hpp'];
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
new file mode 100644
index 0000000..530b7be
--- /dev/null
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -0,0 +1,37 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<title>gr-path-list-behavior</title>
+
+<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-path-list-behavior.html">
+
+<script>
+ suite('gr-path-list-behavior tests', function() {
+ test('special sort', function() {
+ var sort = Gerrit.PathListBehavior.specialFilePathCompare;
+ var testFiles = [
+ '/a.h',
+ '/a.cpp',
+ '/COMMIT_MSG',
+ '/asdasd',
+ '/mrPeanutbutter.py'
+ ];
+ assert.deepEqual(testFiles.sort(sort),
+ ['/COMMIT_MSG', '/a.h', '/a.cpp', '/asdasd', '/mrPeanutbutter.py']);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior.html
index 4def9b2..b7cf467 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior.html
@@ -81,7 +81,13 @@
COMMIT_FOOTERS: 17,
// Include push certificate information along with any patch sets.
- PUSH_CERTIFICATES: 18
+ PUSH_CERTIFICATES: 18,
+
+ // Include change's reviewer updates.
+ REVIEWER_UPDATES: 19,
+
+ // Set the submittable boolean.
+ SUBMITTABLE: 20
},
listChangesOptionsToHex: function() {
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 4f9a09d..2260b38 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
@@ -266,12 +266,31 @@
},
_canSubmitChange: function() {
- return this.$.jsAPI.canSubmitChange();
+ return this.$.jsAPI.canSubmitChange(this.change,
+ this._getRevision(this.change, this.patchNum));
+ },
+
+ _getRevision: function(change, patchNum) {
+ var num = window.parseInt(patchNum, 10);
+ for (var hash in change.revisions) {
+ var rev = change.revisions[hash];
+ if (rev._number === num) {
+ return rev;
+ }
+ }
+ return null;
},
_modifyRevertMsg: function() {
return this.$.jsAPI.modifyRevertMsg(this.change,
- this.$.confirmRevertDialog.message);
+ this.$.confirmRevertDialog.message);
+ },
+
+ showRevertDialog: function() {
+ this.$.confirmRevertDialog.populateRevertMessage(
+ this.commitMessage, this.change.current_revision);
+ this.$.confirmRevertDialog.message = this._modifyRevertMsg();
+ this._showActionDialog(this.$.confirmRevertDialog);
},
_handleActionTap: function(e) {
@@ -286,9 +305,7 @@
if (type === ActionType.REVISION) {
this._handleRevisionAction(key);
} else if (key === ChangeActions.REVERT) {
- this.$.confirmRevertDialog.populateRevertMessage(this.commitMessage);
- this.$.confirmRevertDialog.message = this._modifyRevertMsg();
- this._showActionDialog(this.$.confirmRevertDialog);
+ this.showRevertDialog();
} else if (key === ChangeActions.ABANDON) {
this._showActionDialog(this.$.confirmAbandonDialog);
} else {
@@ -302,6 +319,7 @@
this._showActionDialog(this.$.confirmRebase);
break;
case RevisionActions.CHERRYPICK:
+ this.$.confirmCherrypick.branch = '';
this._showActionDialog(this.$.confirmCherrypick);
break;
case RevisionActions.SUBMIT:
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 83d5bdc..a342c87 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
@@ -125,7 +125,26 @@
});
});
+ test('get revision object from change', function() {
+ var revObj = {_number: 2, foo: 'bar'};
+ var change = {
+ revisions: {
+ rev1: {_number: 1},
+ rev2: revObj,
+ },
+ };
+ assert.deepEqual(element._getRevision(change, '2'), revObj);
+ });
+
test('submit change', function(done) {
+ element.change = {
+ revisions: {
+ rev1: {_number: 1},
+ rev2: {_number: 2},
+ },
+ };
+ element.patchNum = '2';
+
flush(function() {
var submitButton = element.$$('gr-button[data-action-key="submit"]');
assert.ok(submitButton);
@@ -223,8 +242,9 @@
});
test('works', function() {
- var rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
- MockInteractions.tap(rebaseButton);
+ var cherryPickButton =
+ element.$$('gr-button[data-action-key="cherrypick"]');
+ MockInteractions.tap(cherryPickButton);
var action = {
__key: 'cherrypick',
__type: 'revision',
@@ -252,6 +272,25 @@
}
]);
});
+
+ test('branch name cleared when re-open cherrypick', function() {
+ var cherryPickButton =
+ element.$$('gr-button[data-action-key="cherrypick"]');
+ var action = {
+ __key: 'cherrypick',
+ __type: 'revision',
+ __primary: false,
+ enabled: true,
+ label: 'Cherry Pick',
+ method: 'POST',
+ title: 'Cherry pick change to a different branch',
+ };
+ var emptyBranchName = '';
+ element.$.confirmCherrypick.branch = 'master';
+
+ MockInteractions.tap(cherryPickButton);
+ assert.equal(element.$.confirmCherrypick.branch, emptyBranchName);
+ });
});
test('custom actions', function(done) {
@@ -295,6 +334,9 @@
});
test('revert change with plugin hook', function(done) {
+ element.change = {
+ current_revision: 'abc1234',
+ };
var newRevertMsg = 'Modified revert msg';
var modifyRevertMsgStub = sinon.stub(element, '_modifyRevertMsg',
function() { return newRevertMsg; });
@@ -314,6 +356,9 @@
});
test('works', function() {
+ element.change = {
+ current_revision: 'abc1234',
+ };
var populateRevertMsgStub = sinon.stub(
element.$.confirmRevertDialog, 'populateRevertMessage',
function() { return 'original msg'; });
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 84b2391..108ac92 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -16,7 +16,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
+<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-label/gr-label.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
@@ -45,12 +45,13 @@
}
.labelValueContainer .approved,
.labelValueContainer .notApproved {
- display: inline-block;
+ display: inline-flex;
padding: .1em .3em;
border-radius: 3px;
}
.labelValue {
display: inline-block;
+ padding-right: .3em;
}
.approved {
background-color: #d4ffd4;
@@ -150,7 +151,7 @@
<span class="title">[[labelName]]</span>
<span class="value">
<template is="dom-repeat"
- items="[[_computeLabelValues(labelName, change.labels)]]"
+ items="[[_computeLabelValues(labelName, change.labels.*)]]"
as="label">
<div class="labelValueContainer">
<span class$="[[label.className]]">
@@ -160,7 +161,13 @@
class="labelValue">
[[label.value]]
</gr-label>
- <gr-account-link account="[[label.account]]"></gr-account-link>
+ <gr-account-chip
+ account="[[label.account]]"
+ data-account-id$="[[label.account._account_id]]"
+ label-name="[[labelName]]"
+ removable="[[_computeCanDeleteVote(label.account, mutable)]]"
+ transparent-background
+ on-remove="_onDeleteVote"></gr-account-chip>
</span>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 20c117e..28935ce 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -55,8 +55,9 @@
return Object.keys(labels).sort();
},
- _computeLabelValues: function(labelName, labels) {
+ _computeLabelValues: function(labelName, _labels) {
var result = [];
+ var labels = _labels.base;
var t = labels[labelName];
if (!t) { return result; }
var approvals = t.all || [];
@@ -101,5 +102,46 @@
_computeShowReviewersByState: function(serverConfig) {
return !!serverConfig.note_db_enabled;
},
+
+ /**
+ * A user is able to delete a vote iff the mutable property is true and the
+ * reviewer that left the vote exists in the list of removable_reviewers
+ * received from the backend.
+ *
+ * @param {!Object} reviewer An object describing the reviewer that left the
+ * vote.
+ * @param {boolean} mutable this.mutable describes whether the
+ * change-metadata section is modifiable by the current user.
+ */
+ _computeCanDeleteVote: function(reviewer, mutable) {
+ if (!mutable) { return false; }
+ for (var i = 0; i < this.change.removable_reviewers.length; i++) {
+ if (this.change.removable_reviewers[i]._account_id ===
+ reviewer._account_id) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _onDeleteVote: function(e) {
+ e.preventDefault();
+ var target = Polymer.dom(e).rootTarget;
+ var labelName = target.labelName;
+ var accountID = parseInt(target.getAttribute('data-account-id'), 10);
+ this._xhrPromise =
+ this.$.restAPI.deleteVote(this.change.id, accountID, labelName)
+ .then(function(response) {
+ if (!response.ok) { return response; }
+
+ var labels = this.change.labels[labelName].all || [];
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i]._account_id === accountID) {
+ this.splice(['change.labels', labelName, 'all'], i, 1);
+ break;
+ }
+ }
+ }.bind(this));
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index a2d4946..218e124 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -77,5 +77,74 @@
element.serverConfig = {note_db_enabled: true};
assert.isTrue(hasCc());
});
+
+ suite('remove reviewer votes', function() {
+ var sandbox;
+ setup(function() {
+ sandbox = sinon.sandbox.create();
+ sandbox.stub(element, '_computeValueTooltip').returns('');
+ sandbox.stub(element, '_computeTopicReadOnly').returns(true);
+ element.change = {
+ status: 'NEW',
+ submit_type: 'CHERRY_PICK',
+ labels: {
+ test: {
+ all: [{_account_id: 1, name: 'bojack', value: 1}],
+ default_value: 0,
+ },
+ },
+ removable_reviewers: [],
+ };
+ });
+
+ teardown(function() {
+ sandbox.restore();
+ });
+
+ test('_computeCanDeleteVote hides delete button', function() {
+ flushAsynchronousOperations();
+ var button = element.$$('gr-account-chip').$$('gr-button');
+ assert.isTrue(button.hasAttribute('hidden'));
+ element.mutable = true;
+ assert.isTrue(button.hasAttribute('hidden'));
+ });
+
+ test('_computeCanDeleteVote shows delete button', function() {
+ element.change.removable_reviewers = [
+ {
+ _account_id: 1,
+ name: 'bojack',
+ }
+ ];
+ element.mutable = true;
+ flushAsynchronousOperations();
+ var button = element.$$('gr-account-chip').$$('gr-button');
+ assert.isFalse(button.hasAttribute('hidden'));
+ });
+
+ test('deletes votes', function(done) {
+ sandbox.stub(element.$.restAPI, 'deleteVote')
+ .returns(Promise.resolve({'ok': true}));
+ element.change.removable_reviewers = [
+ {
+ _account_id: 1,
+ name: 'bojack',
+ }
+ ];
+ element.mutable = true;
+ flushAsynchronousOperations();
+ var button = element.$$('gr-account-chip').$$('gr-button');
+ MockInteractions.tap(button);
+ flushAsynchronousOperations();
+ var spliceStub = sinon.stub(element, 'splice',
+ function(path, index, length) {
+ assert.deepEqual(path, ['change.labels', 'test', 'all']);
+ assert.equal(index, 0);
+ assert.equal(length, 1);
+ spliceStub.restore();
+ done();
+ });
+ });
+ });
});
</script>
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 37c9f62..3d4e10d 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
@@ -224,6 +224,10 @@
mutable="[[_loggedIn]]"
on-show-reply-dialog="_handleShowReplyDialog">
</gr-change-metadata>
+ <!-- Plugins insert content into following container.
+ Stop-gap until PolyGerrit plugins interface is ready.
+ This will not work with Shadow DOM. -->
+ <div id="change_plugins"></div>
</div>
<div class="changeInfo-column mainChangeInfo">
<div class="commitActions" hidden$="[[!_loggedIn]]"">
@@ -298,7 +302,8 @@
drafts="[[_diffDrafts]]"
revisions="[[_change.revisions]]"
projectConfig="[[_projectConfig]]"
- selected-index="{{viewState.selectedFileIndex}}"></gr-file-list>
+ selected-index="{{viewState.selectedFileIndex}}"
+ diff-view-mode="{{viewState.diffMode}}"></gr-file-list>
</section>
<gr-messages-list id="messageList"
change-num="[[_changeNum]]"
@@ -311,6 +316,7 @@
</div>
<gr-overlay id="downloadOverlay" with-backdrop>
<gr-download-dialog
+ id="downloadDialog"
change="[[_change]]"
logged-in="[[_loggedIn]]"
patch-num="[[_patchRange.patchNum]]"
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 7933031..6eaf4ff 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
@@ -248,7 +248,11 @@
_handleDownloadTap: function(e) {
e.preventDefault();
- this.$.downloadOverlay.open();
+ this.$.downloadOverlay.open().then(function() {
+ this.$.downloadOverlay
+ .setFocusStops(this.$.downloadDialog.getFocusStops());
+ this.$.downloadDialog.focus();
+ }.bind(this));
},
_handleDownloadDialogClose: function(e) {
@@ -259,7 +263,11 @@
var msg = e.detail.message.message;
var quoteStr = msg.split('\n').map(
function(line) { return '> ' + line; }).join('\n') + '\n\n';
- this.$.replyDialog.draft += quoteStr;
+
+ if (quoteStr !== this.$.replyDialog.quote) {
+ this.$.replyDialog.draft = quoteStr;
+ }
+ this.$.replyDialog.quote = quoteStr;
this._openReplyDialog();
},
@@ -360,6 +368,8 @@
this._maybeShowReplyDialog();
+ this._maybeShowRevertDialog();
+
this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
change: this._change,
patchNum: this._patchRange.patchNum,
@@ -387,6 +397,35 @@
}
},
+ _getLocationSearch: function() {
+ // Not inlining to make it easier to test.
+ return window.location.search;
+ },
+
+ _getUrlParameter: function(param) {
+ var pageURL = this._getLocationSearch().substring(1);
+ var vars = pageURL.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ var name = vars[i].split('=');
+ if (name[0] == param) {
+ return name[0];
+ }
+ }
+ return null;
+ },
+
+ _maybeShowRevertDialog: function() {
+ this._getLoggedIn().then(function(loggedIn) {
+ if (!loggedIn || this._change.status !== this.ChangeStatus.MERGED) {
+ // Do not display dialog if not logged-in or the change is not merged.
+ return;
+ }
+ if (!!this._getUrlParameter('revert')) {
+ this.$.actions.showRevertDialog();
+ }
+ }.bind(this));
+ },
+
_maybeShowReplyDialog: function() {
this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) { return; }
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 4cc4867..b49f05d 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
@@ -35,6 +35,7 @@
<script>
suite('gr-change-view tests', function() {
var element;
+ var TEST_SCROLL_TOP_PX = 100;
setup(function() {
stub('gr-rest-api-interface', {
@@ -444,7 +445,77 @@
assert.equal(element._computePatchInfoClass('4', allPatcheSets), '');
});
+ test('getUrlParameter functionality', function() {
+ var locationStub = sinon.stub(element, '_getLocationSearch');
+
+ locationStub.returns('?test');
+ assert.equal(element._getUrlParameter('test'), 'test');
+ locationStub.returns('?test2=12&test=3');
+ assert.equal(element._getUrlParameter('test'), 'test');
+ locationStub.returns('');
+ assert.isNull(element._getUrlParameter('test'));
+ locationStub.returns('?');
+ assert.isNull(element._getUrlParameter('test'));
+ locationStub.returns('?test2');
+ assert.isNull(element._getUrlParameter('test'));
+
+ locationStub.restore();
+ });
+
+ test('revert dialog opened with revert param', function(done) {
+ sinon.stub(element.$.restAPI, 'getLoggedIn', function() {
+ return Promise.resolve(true);
+ });
+
+ element._patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 2,
+ };
+ element._change = {
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev1: {_number: 1},
+ },
+ current_revision: 'rev1',
+ status: element.ChangeStatus.MERGED,
+ labels: {},
+ actions: {},
+ };
+
+ var urlParamStub = sinon.stub(element, '_getUrlParameter',
+ function(param) {
+ assert.equal(param, 'revert');
+ urlParamStub.restore();
+ element.$.restAPI.getLoggedIn.restore();
+ return param;
+ });
+
+ var revertDialogStub = sinon.stub(element.$.actions, 'showRevertDialog',
+ function() {
+ revertDialogStub.restore();
+ done();
+ });
+
+ element._maybeShowRevertDialog();
+ });
+
suite('scroll related tests', function() {
+ test('document scrolling calls function to set scroll height',
+ function(done) {
+ var scrollStub = sinon.stub(element, '_handleScroll',
+ function() {
+ assert.isTrue(scrollStub.called);
+ document.body.style.height =
+ originalHeight + 'px';
+ scrollStub.restore();
+ done();
+ });
+ var originalHeight = document.body.scrollHeight;
+ document.body.style.height = '10000px';
+ document.body.scrollTop = TEST_SCROLL_TOP_PX;
+ element._handleScroll();
+ });
+
test('history is loaded correctly', function() {
history.replaceState(
{
@@ -466,22 +537,49 @@
// changes to match a regex of change view type.
element._paramsChanged({view: 'gr-change-view'});
reloadStub.restore();
+ });
+ });
+ suite('reply dialog tests', function() {
+ setup(function() {
+ sinon.stub(element.$.replyDialog, '_draftChanged');
});
- test('document scrolling calls function to set scroll height',
- function(done) {
- var scrollStub = sinon.stub(element, '_handleScroll', function() {
- assert.isTrue(scrollStub.called);
- document.getElementsByTagName('body')[0].style.height =
- originalHeight + 'px';
- scrollStub.restore();
- done();
- });
+ test('reply from comment adds quote text', function() {
+ var e = {detail: {message: {message: 'quote text'}}};
+ element._handleMessageReply(e);
+ assert.equal(element.$.replyDialog.draft, '> quote text\n\n');
+ assert.equal(element.$.replyDialog.quote, '> quote text\n\n');
+ });
- var originalHeight = document.body.scrollHeight;
- document.getElementsByTagName('body')[0].style.height = '10000px';
- window.scroll(0, 100);
+ test('reply from comment replaces quote text', function() {
+ element.$.replyDialog.draft = '> old quote text\n\n some draft text';
+ element.$.replyDialog.quote = '> old quote text\n\n';
+ var e = {detail: {message: {message: 'quote text'}}};
+ element._handleMessageReply(e);
+ assert.equal(element.$.replyDialog.draft, '> quote text\n\n');
+ assert.equal(element.$.replyDialog.quote, '> quote text\n\n');
+ });
+
+ test('reply from same comment preserves quote text', function() {
+ element.$.replyDialog.draft = '> quote text\n\n some draft text';
+ element.$.replyDialog.quote = '> quote text\n\n';
+ var e = {detail: {message: {message: 'quote text'}}};
+ element._handleMessageReply(e);
+ assert.equal(element.$.replyDialog.draft,
+ '> quote text\n\n some draft text');
+ assert.equal(element.$.replyDialog.quote, '> quote text\n\n');
+ });
+
+ test('reply from top of page contains previous draft', function() {
+ var div = document.createElement('div');
+ element.$.replyDialog.draft = '> quote text\n\n some draft text';
+ element.$.replyDialog.quote = '> quote text\n\n';
+ var e = {target: div, preventDefault: sinon.spy()};
+ element._handleReplyTap(e);
+ assert.equal(element.$.replyDialog.draft,
+ '> quote text\n\n some draft text');
+ assert.equal(element.$.replyDialog.quote, '> quote text\n\n');
});
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index d462c12..e4ab44b 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-path-list-behavior.html">
+<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
index 979a06a..b668d06 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
@@ -55,6 +55,7 @@
</label>
<iron-autogrow-textarea
id="messageInput"
+ max-rows="15"
class="message"
bind-value="{{message}}"></iron-autogrow-textarea>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
index 0dbefc5..06775d7 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -33,18 +33,15 @@
message: String,
},
- populateRevertMessage: function(message) {
+ populateRevertMessage: function(message, commitHash) {
// Figure out what the revert title should be.
var originalTitle = message.split('\n')[0];
var revertTitle = 'Revert "' + originalTitle + '"';
- // Figure out what the revert commit message should be.
- var commitRegex = /\nChange-Id: (\w+)\n\s*/g;
- var match = commitRegex.exec(message);
- if (!match) {
- alert('Unable to find Change-Id in footer of commit message.');
+ if (!commitHash) {
+ alert('Unable to find the commit hash of this change.');
return;
}
- var revertCommitText = 'This reverts commit ' + match[1] + '.';
+ var revertCommitText = 'This reverts commit ' + commitHash + '.';
// Add '> ' in front of the original commit text.
var originalCommitText = message.replace(/^/gm, '> ');
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index 8ccfc9a..5e6c53fb 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -41,16 +41,18 @@
test('no match', function() {
assert.isNotOk(element.message);
var alertStub = sinon.stub(window, 'alert');
- element.populateRevertMessage('not a change-id in sight');
+ element.populateRevertMessage('not a commitHash in sight', undefined);
assert.isTrue(alertStub.calledOnce);
alertStub.restore();
});
test('single line', function() {
assert.isNotOk(element.message);
- element.populateRevertMessage('one line commit\n\nChange-Id: abcdefg\n');
+ element.populateRevertMessage(
+ 'one line commit\n\nChange-Id: abcdefg\n',
+ 'abcd123');
var expected = 'Revert "one line commit"\n\n' +
- 'This reverts commit abcdefg.\n\n' +
+ 'This reverts commit abcd123.\n\n' +
'Reason for revert: <INSERT REASONING HERE>\n\n' +
'Original change\'s description:\n' +
'> one line commit\n> \n' +
@@ -61,9 +63,10 @@
test('multi line', function() {
assert.isNotOk(element.message);
element.populateRevertMessage(
- 'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n');
+ 'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
+ 'abcd123');
var expected = 'Revert "many lines"\n\n' +
- 'This reverts commit abcdefg.\n\n' +
+ 'This reverts commit abcd123.\n\n' +
'Reason for revert: <INSERT REASONING HERE>\n\n' +
'Original change\'s description:\n' +
'> many lines\n> commit\n> \n> message\n> \n' +
@@ -74,9 +77,10 @@
test('issue above change id', function() {
assert.isNotOk(element.message);
element.populateRevertMessage(
- 'much lines\nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n');
+ 'much lines\nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
+ 'abcd123');
var expected = 'Revert "much lines"\n\n' +
- 'This reverts commit abcdefg.\n\n' +
+ 'This reverts commit abcd123.\n\n' +
'Reason for revert: <INSERT REASONING HERE>\n\n' +
'Original change\'s description:\n' +
'> much lines\n> very\n> \n> commit\n> \n' +
@@ -88,9 +92,10 @@
test('revert a revert', function() {
assert.isNotOk(element.message);
element.populateRevertMessage(
- 'Revert "one line commit"\n\nChange-Id: abcdefg\n');
+ 'Revert "one line commit"\n\nChange-Id: abcdefg\n',
+ 'abcd123');
var expected = 'Revert "Revert "one line commit""\n\n' +
- 'This reverts commit abcdefg.\n\n' +
+ 'This reverts commit abcd123.\n\n' +
'Reason for revert: <INSERT REASONING HERE>\n\n' +
'Original change\'s description:\n' +
'> Revert "one line commit"\n> \n' +
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index b1e5c01..7c888da 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -100,7 +100,9 @@
</template>
</ul>
<span class="closeButtonContainer">
- <gr-button link on-tap="_handleCloseTap">Close</gr-button>
+ <gr-button id="closeButton"
+ link
+ on-tap="_handleCloseTap">Close</gr-button>
</span>
</header>
<main hidden$="[[!_schemes.length]]" hidden>
@@ -121,7 +123,7 @@
<div class="patchFiles">
<label>Patch file</label>
<div>
- <a href$="[[_computeDownloadLink(change, patchNum)]]">
+ <a id="download" href$="[[_computeDownloadLink(change, patchNum)]]">
[[_computeDownloadFilename(change, patchNum)]]
</a>
<a href$="[[_computeZipDownloadLink(change, patchNum)]]">
@@ -131,7 +133,7 @@
</div>
<div class="archivesContainer" hidden$="[[!config.archives.length]]" hidden>
<label>Archive</label>
- <div class="archives">
+ <div id="archives" class="archives">
<template is="dom-repeat" items="[[config.archives]]" as="format">
<a href$="[[_computeArchiveDownloadLink(change, patchNum, format)]]">
[[format]]
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index 2f3e8e1..b331899 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -58,6 +58,18 @@
}.bind(this));
},
+ focus: function() {
+ this.$.download.focus();
+ },
+
+ getFocusStops: function() {
+ var links = this.$$('#archives').querySelectorAll('a');
+ return {
+ start: this.$.closeButton,
+ end: links[links.length - 1],
+ };
+ },
+
_computeDownloadCommands: function(change, patchNum, _selectedScheme) {
var commandObj;
for (var rev in change.revisions) {
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 70e934d..ad5086b 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -115,6 +115,14 @@
};
});
+ test('focuses on first download link', function() {
+ var focusStub = sinon.stub(element.$.download, 'focus');
+ element.focus();
+ flushAsynchronousOperations();
+ assert.isTrue(focusStub.called);
+ focusStub.restore();
+ });
+
test('element visibility', function() {
assert.isFalse(element.$$('ul').hasAttribute('hidden'));
assert.isFalse(element.$$('main').hasAttribute('hidden'));
@@ -154,7 +162,6 @@
assert.isFalse(el.hasAttribute('selected'));
});
});
-
});
suite('gr-download-dialog tests', function() {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index dcb2ca7..78e4634 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -89,7 +89,8 @@
.invisible {
visibility: hidden;
}
- .row:not(.header) .stats {
+ .row:not(.header) .stats,
+ .total-stats {
font-family: var(--monospace-font-family);
}
.added {
@@ -108,6 +109,11 @@
.fileListButton {
margin: .5em;
}
+ .totalChanges {
+ justify-content: flex-end;
+ padding-right: 2.6em;
+ text-align: right;
+ }
input.show-hide {
display: none;
}
@@ -147,9 +153,18 @@
/
<gr-button link on-tap="_collapseAllDiffs">Hide diffs</gr-button>
/
+ <select
+ id="modeSelect"
+ is="gr-select"
+ bind-value="{{diffViewMode}}"
+ on-change="_handleDropdownChange">
+ <option value="SIDE_BY_SIDE">Side By Side</option>
+ <option value="UNIFIED_DIFF">Unified</option>
+ </select>
+ /
<label>
Diff against
- <select on-change="_handlePatchChange">
+ <select id="patchChange" on-change="_handlePatchChange">
<option value="PARENT">Base</option>
<template is="dom-repeat" items="[[_computePatchSets(revisions, patchRange.*)]]" as="patchNum">
<option
@@ -201,7 +216,8 @@
</div>
</div>
<gr-diff
- hidden$="[[_computeHiddenState(file.__expanded)]]"
+ hidden$="[[!file.__expanded]]"
+ expanded="[[file.__expanded]]"
project="[[change.project]]"
commit="[[change.current_revision]]"
change-num="[[changeNum]]"
@@ -209,8 +225,14 @@
path="[[file.__path]]"
prefs="[[_diffPrefs]]"
project-config="[[projectConfig]]"
- view-mode="[[_userPrefs.diff_view]]"></gr-diff>
+ view-mode="[[_diffMode]]"></gr-diff>
</template>
+ <div class="row totalChanges">
+ <div class="total-stats" hidden$="[[_hideChangeTotals]]">
+ <span class="added">+[[_patchChange.inserted]]</span>
+ <span class="removed">-[[_patchChange.deleted]]</span>
+ </div>
+ </div>
<gr-button
class="fileListButton"
id="incrementButton"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index dae798d..478faf5 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -16,6 +16,11 @@
var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+ var DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
Polymer({
is: 'gr-file-list',
@@ -36,7 +41,10 @@
value: function() { return document.body; },
},
change: Object,
-
+ diffViewMode: {
+ type: String,
+ notify: true,
+ },
_files: {
type: Array,
observer: '_filesChanged',
@@ -58,15 +66,27 @@
type: Number,
value: 75,
},
+ _patchChange: {
+ type: Object,
+ computed: '_calculatePatchChange(_files)',
+ },
_fileListIncrement: {
type: Number,
readOnly: true,
value: 75,
},
+ _hideChangeTotals: {
+ type: Boolean,
+ computed: '_shouldHideChangeTotals(_patchChange)',
+ },
_shownFiles: {
type: Array,
computed: '_computeFilesShown(_numFilesShown, _files.*)',
},
+ _diffMode: {
+ type: String,
+ computed: '_getDiffViewMode(diffViewMode, _userPrefs)',
+ },
},
behaviors: [
@@ -100,8 +120,17 @@
this._diffPrefs = prefs;
}.bind(this)));
+ // Initialize with user's diff mode preference. Default to
+ // SIDE_BY_SIDE in the meantime.
+ var setDiffViewMode = this.diffViewMode === null;
+ if (setDiffViewMode) {
+ this.set('diffViewMode', DiffViewMode.SIDE_BY_SIDE);
+ }
promises.push(this._getPreferences().then(function(prefs) {
this._userPrefs = prefs;
+ if (setDiffViewMode) {
+ this.set('diffViewMode', prefs.diff_view);
+ }
}.bind(this)));
},
@@ -109,6 +138,22 @@
return Polymer.dom(this.root).querySelectorAll('gr-diff');
},
+ _calculatePatchChange: function(files) {
+ var filesNoCommitMsg = files.filter(function(files) {
+ return files.__path !== '/COMMIT_MSG';
+ });
+
+ return filesNoCommitMsg.reduce(function(acc, obj) {
+ var inserted = obj.lines_inserted ? obj.lines_inserted : 0;
+ var deleted = obj.lines_deleted ? obj.lines_deleted : 0;
+
+ return {
+ inserted: acc.inserted + inserted,
+ deleted: acc.deleted + deleted,
+ };
+ }, {inserted: 0, deleted: 0});
+ },
+
_getDiffPreferences: function() {
return this.$.restAPI.getDiffPreferences();
},
@@ -402,6 +447,10 @@
window.scrollTo(0, top - document.body.clientHeight / 2);
},
+ _shouldHideChangeTotals: function(_patchChange) {
+ return (_patchChange.inserted === 0 && _patchChange.deleted === 0);
+ },
+
_computeFileSelected: function(index, selectedIndex) {
return index === selectedIndex;
},
@@ -437,10 +486,6 @@
return expanded ? 'â–¼' : 'â—€';
},
- _computeHiddenState: function(expanded) {
- return !expanded;
- },
-
_computeFilesShown: function(numFilesShown, files) {
return files.base.slice(0, numFilesShown);
},
@@ -478,5 +523,32 @@
_showAllFiles: function() {
this._numFilesShown = this._files.length;
},
+
+ /**
+ * _getDiffViewMode: Get the diff view (side-by-side or unified) based on
+ * the current state.
+ *
+ * The expected behavior is to use the mode specified in the user's
+ * preferences unless they have manually chosen the alternative view. If the
+ * user navigates up to the change view, it should clear this choice and
+ * revert to the preference the next time a diff is viewed.
+ *
+ * Use side-by-side if the user is not logged in.
+ *
+ * @return {String}
+ */
+ _getDiffViewMode: function() {
+ if (this.diffViewMode) {
+ return this.diffViewMode;
+ } else if (this._userPrefs && this._userPrefs.diff_view) {
+ return this.diffViewMode = this._userPrefs.diff_view;
+ }
+
+ return DiffViewMode.SIDE_BY_SIDE;
+ },
+
+ _handleDropdownChange: function(e) {
+ e.target.blur();
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 502597c..b298fef 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -32,20 +32,36 @@
</template>
</test-fixture>
+<test-fixture id="blank">
+ <template>
+ <div></div>
+ </template>
+</test-fixture>
+
<script>
suite('gr-file-list tests', function() {
var element;
+ var sandbox;
setup(function() {
+ sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(true); },
getPreferences: function() { return Promise.resolve({}); },
+ fetchJSON: function() { return Promise.resolve({}); },
+ });
+ stub('gr-date-formatter', {
+ _loadTimeFormat: function() { return Promise.resolve(''); }
});
element = fixture('basic');
});
+ teardown(function() {
+ sandbox.restore();
+ });
+
test('get file list', function(done) {
- var getChangeFilesStub = sinon.stub(element.$.restAPI, 'getChangeFiles',
+ var getChangeFilesStub = sandbox.stub(element.$.restAPI, 'getChangeFiles',
function() {
return Promise.resolve({
'/COMMIT_MSG': {lines_inserted: 9},
@@ -81,6 +97,37 @@
});
});
+ test('calculate totals for patch number', function() {
+ element._files = [
+ {__path: '/COMMIT_MSG', lines_inserted: 9},
+ {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
+ {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
+ ];
+ assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+
+ // Test with a commit message that isn't the first file.
+ element._files = [
+ {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
+ {__path: '/COMMIT_MSG', lines_inserted: 9},
+ {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
+ ];
+ assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+
+ // Test with no commit message.
+ element._files = [
+ {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
+ {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
+ ];
+ assert.deepEqual(element._patchChange, {inserted: 2, deleted: 2});
+
+ // Test with files missing either lines_inserted or lines_deleted.
+ element._files = [
+ {__path: 'file_added_in_rev2.txt', lines_inserted: 1},
+ {__path: 'myfile.txt', lines_deleted: 1},
+ ];
+ assert.deepEqual(element._patchChange, {inserted: 1, deleted: 1});
+ });
+
suite('keyboard shortcuts', function() {
setup(function() {
element._files = [
@@ -97,19 +144,22 @@
});
test('toggle left diff via shortcut', function() {
- var toggleLeftDiffStub = sinon.stub();
- sinon.stub(element, 'diffs', {get: function() {
+ var toggleLeftDiffStub = sandbox.stub();
+ // Property getter cannot be stubbed w/ sandbox due to a bug in Sinon.
+ // https://github.com/sinonjs/sinon/issues/781
+ var diffsStub = sinon.stub(element, 'diffs', {get: function() {
return [{toggleLeftDiff: toggleLeftDiffStub}];
}});
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A'
assert.isTrue(toggleLeftDiffStub.calledOnce);
+ diffsStub.restore();
});
test('keyboard shortcuts', function() {
flushAsynchronousOperations();
var elementItems = Polymer.dom(element.root).querySelectorAll(
'.row:not(.header)');
- assert.equal(elementItems.length, 3);
+ assert.equal(elementItems.length, 4);
assert.isTrue(elementItems[0].hasAttribute('selected'));
assert.isFalse(elementItems[1].hasAttribute('selected'));
assert.isFalse(elementItems[2].hasAttribute('selected'));
@@ -117,7 +167,7 @@
assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J'
- var showStub = sinon.stub(page, 'show');
+ var showStub = sandbox.stub(page, 'show');
assert.equal(element.selectedIndex, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13); // 'ENTER'
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
@@ -241,7 +291,7 @@
assert.isFalse(fileAdded.checked);
assert.isTrue(myFile.checked);
- var saveStub = sinon.stub(element, '_saveReviewedState',
+ var saveStub = sandbox.stub(element, '_saveReviewedState',
function() { return Promise.resolve(); });
MockInteractions.tap(commitMsg);
@@ -272,7 +322,7 @@
});
test('diff against dropdown', function(done) {
- var showStub = sinon.stub(page, 'show');
+ var showStub = sandbox.stub(page, 'show');
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
@@ -284,7 +334,7 @@
rev3: {_number: 3},
};
flush(function() {
- var selectEl = element.$$('select');
+ var selectEl = element.$.patchChange;
assert.equal(selectEl.value, 'PARENT');
assert.isTrue(element.$$('option[value="3"]').hasAttribute('disabled'));
selectEl.addEventListener('change', function() {
@@ -313,7 +363,7 @@
var fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
// Prevent diff from making API call.
- var diffStub = sinon.stub(element.diffs[0], 'reload');
+ var diffStub = sandbox.stub(element.diffs[0], 'reload');
var showHideCheck = fileRows[0].querySelector(
'input.show-hide[type="checkbox"]');
assert.isTrue(showHideCheck.checked);
@@ -339,5 +389,50 @@
element.$$('a').getAttribute('href'),
'/c/42/2/foo+bar/my%252Bfile.txt%2525');
});
+
+ test('diff mode correctly toggles the diffs', function() {
+ element._files = [
+ {__path: 'myfile.txt', __expanded: false},
+ ];
+ element.changeNum = '42';
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: '2',
+ };
+ element.selectedIndex = 0;
+ flushAsynchronousOperations();
+ var select = element.$.modeSelect;
+ var diffDisplay = element.diffs[0];
+ element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
+ assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
+ assert.equal(element.diffViewMode, 'SIDE_BY_SIDE');
+ assert.equal(diffDisplay.viewMode, 'SIDE_BY_SIDE');
+ element.set('diffViewMode', 'UNIFIED_DIFF');
+ assert.equal(element._getDiffViewMode(), 'UNIFIED_DIFF');
+ assert.equal(diffDisplay.viewMode, 'UNIFIED_DIFF');
+ });
+
+ test('diff mode selector initializes from preferences', function() {
+ var resolvePrefs;
+ var prefsPromise = new Promise(function(resolve) {
+ resolvePrefs = resolve;
+ });
+ sandbox.stub(element, '_getPreferences').returns(prefsPromise);
+
+ // Attach a new gr-file-list so we can intercept the preferences fetch.
+ var view = document.createElement('gr-file-list');
+ var select = view.$.modeSelect;
+ fixture('blank').appendChild(view);
+ flushAsynchronousOperations();
+
+ // At this point the diff mode doesn't yet have the user's preference.
+ assert.equal(select.value, 'SIDE_BY_SIDE');
+
+ // Receive the overriding preference.
+ resolvePrefs({diff_view: 'UNIFIED'});
+ flushAsynchronousOperations();
+ assert.equal(select.value, 'SIDE_BY_SIDE');
+ document.getElementById('blank').restore();
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 1432d8c..cb6a6f9 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -208,6 +208,12 @@
</iron-autogrow-textarea>
</section>
<section class="labelsContainer">
+ <template is="dom-if" if="[[_isClosed(change)]]" id="labelDisabled">
+ <div class="labelDisabledMessage">
+ Setting labels are disabled for this change because it has been
+ closed.
+ </div>
+ </template>
<template is="dom-repeat"
items="[[_computeLabelArray(permittedLabels)]]" as="label">
<div class="labelContainer">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 00fa05e..a61de4d 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -23,6 +23,8 @@
REVIEWERS: 'reviewers',
};
+ var CLOSED_CHANGE_STATUSES = ['ABANDONED', 'MERGED'];
+
Polymer({
is: 'gr-reply-dialog',
@@ -58,6 +60,10 @@
value: '',
observer: '_draftChanged',
},
+ quote: {
+ type: String,
+ value: ''
+ },
diffDrafts: Object,
filterReviewerSuggestion: {
type: Function,
@@ -267,6 +273,10 @@
}.bind(this));
},
+ _isClosed: function(change) {
+ return CLOSED_CHANGE_STATUSES.indexOf(change.status) !== -1;
+ },
+
_computeHideDraftList: function(drafts) {
return Object.keys(drafts || {}).length == 0;
},
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 01ca076..6c77afb 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -244,6 +244,18 @@
}).then(done);
});
+ test('message disabled dialogue appears for closed change', function() {
+ element.change = {status: 'ABANDONED'};
+ flushAsynchronousOperations();
+ assert.isOk(element.$$('.labelDisabledMessage'));
+ });
+
+ test('message disabled dialogue does not appear for open change',
+ function() {
+ element.change = {status: 'NEW'};
+ assert.isNotOk(element.$$('.labelDisabledMessage'));
+ });
+
test('_getStorageLocation', function() {
var actual = element._getStorageLocation();
assert.equal(actual.changeNum, changeNum);
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 7a9c4f9..a38152a 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -18,6 +18,7 @@
var CHECK_SIGN_IN_INTERVAL_MS = 60000;
var SIGN_IN_WIDTH_PX = 690;
var SIGN_IN_HEIGHT_PX = 500;
+ var TOO_MANY_FILES = 'too many files to find conflicts';
Polymer({
is: 'gr-error-manager',
@@ -38,6 +39,10 @@
this.unlisten(document, 'network-error', '_handleNetworkError');
},
+ _shouldSupressError: function(msg) {
+ return msg.indexOf(TOO_MANY_FILES) > -1;
+ },
+
_handleServerError: function(e) {
if (e.detail.response.status === 403) {
this._getLoggedIn().then(function(loggedIn) {
@@ -49,7 +54,9 @@
}.bind(this));
} else {
e.detail.response.text().then(function(text) {
- this._showAlert('Server error: ' + text);
+ if (!this._shouldSupressError(text)) {
+ this._showAlert('Server error: ' + text);
+ }
}.bind(this));
}
},
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index f633a7e..44cbde0 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -70,6 +70,20 @@
});
});
+ test('suppress TOO_MANY_FILES error', function(done) {
+ var showAlertStub = sandbox.stub(element, '_showAlert');
+ var textSpy = sandbox.spy(function() {
+ return Promise.resolve('too many files to find conflicts');
+ });
+ element.fire('server-error', {response: {status: 500, text: textSpy}});
+
+ assert.isTrue(textSpy.called);
+ textSpy.lastCall.returnValue.then(function() {
+ assert.isFalse(showAlertStub.called);
+ done();
+ });
+ });
+
test('show network error', function(done) {
var consoleErrorStub = sandbox.stub(console, 'error');
var showAlertStub = sandbox.stub(element, '_showAlert');
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
index 7653655..d10567c 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
@@ -15,6 +15,7 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-reporting">
<script src="gr-reporting.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 5236a1c..32dd687 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -33,6 +33,8 @@
var CHANGE_VIEW_REGEX = /^\/c\/\d+\/?\d*$/;
var DIFF_VIEW_REGEX = /^\/c\/\d+\/\d+\/.+$/;
+ var pending = [];
+
Polymer({
is: 'gr-reporting',
@@ -40,7 +42,7 @@
_baselines: {
type: Array,
value: function() { return {}; },
- }
+ },
},
get performanceTiming() {
@@ -51,8 +53,13 @@
return Math.round(10 * window.performance.now()) / 10;
},
- reporter: function(type, category, eventName, eventValue) {
- eventValue = eventValue;
+ reporter: function() {
+ var report = (Gerrit._arePluginsLoaded() && !pending.length) ?
+ this.defaultReporter : this.cachingReporter;
+ report.apply(this, arguments);
+ },
+
+ defaultReporter: function(type, category, eventName, eventValue) {
var detail = {
type: type,
category: category,
@@ -63,6 +70,19 @@
console.log(eventName + ': ' + eventValue);
},
+ cachingReporter: function(type, category, eventName, eventValue) {
+ if (Gerrit._arePluginsLoaded()) {
+ if (pending.length) {
+ pending.splice(0).forEach(function(args) {
+ this.reporter.apply(this, args);
+ }, this);
+ }
+ this.reporter(type, category, eventName, eventValue);
+ } else {
+ pending.push([type, category, eventName, eventValue]);
+ }
+ },
+
/**
* User-perceived app start time, should be reported when the app is ready.
*/
@@ -105,6 +125,10 @@
NAVIGATION.TYPE, NAVIGATION.CATEGORY, NAVIGATION.PAGE, page);
},
+ pluginsLoaded: function() {
+ this.timeEnd('PluginsLoaded');
+ },
+
_getPathname: function() {
return window.location.pathname;
},
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index b9d07fc..082f81b 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -90,6 +90,48 @@
));
});
+ suite('plugins', function() {
+ setup(function() {
+ element.reporter.restore();
+ sandbox.stub(element, 'defaultReporter');
+ sandbox.stub(Gerrit, '_arePluginsLoaded');
+ });
+
+ test('pluginsLoaded reports time', function() {
+ Gerrit._arePluginsLoaded.returns(true);
+ var nowStub = sinon.stub(element, 'now').returns(42);
+ element.pluginsLoaded();
+ assert.isTrue(element.defaultReporter.calledWithExactly(
+ 'timing-report', 'UI Latency', 'PluginsLoaded', 42
+ ));
+ });
+
+ test('caches reports if plugins are not loaded', function() {
+ Gerrit._arePluginsLoaded.returns(false);
+ element.timeEnd('foo');
+ assert.isFalse(element.defaultReporter.called);
+ });
+
+ test('reports if plugins are loaded', function() {
+ Gerrit._arePluginsLoaded.returns(true);
+ element.timeEnd('foo');
+ assert.isTrue(element.defaultReporter.called);
+ });
+
+ test('reports cached events preserving order', function() {
+ Gerrit._arePluginsLoaded.returns(false);
+ element.timeEnd('foo');
+ Gerrit._arePluginsLoaded.returns(true);
+ element.timeEnd('bar');
+ assert.isTrue(element.defaultReporter.firstCall.calledWith(
+ 'timing-report', 'UI Latency', 'foo'
+ ));
+ assert.isTrue(element.defaultReporter.secondCall.calledWith(
+ 'timing-report', 'UI Latency', 'bar'
+ ));
+ });
+ });
+
suite('location changed', function() {
var pathnameStub;
setup(function() {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index f185977..05b191c 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -36,7 +36,10 @@
// Fire asynchronously so that the URL is changed by the time the event
// is processed.
app.async(function() {
- app.fire('location-change');
+ app.fire('location-change', {
+ hash: window.location.hash,
+ pathname: window.location.pathname,
+ });
reporting.locationChanged();
}, 1);
next();
@@ -56,6 +59,11 @@
}
// For backward compatibility with GWT links.
if (data.hash) {
+ // In certain login flows the server may redirect to a hash without
+ // a leading slash, which page.js doesn't handle correctly.
+ if (data.hash[0] !== '/') {
+ data.hash = '/' + data.hash
+ }
page.redirect(data.hash);
return;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index 40a90b7..3ad7c74 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -58,10 +58,20 @@
SYNTAX: 'Diff Syntax Render',
};
+ // If any line of the diff is more than the character limit, then disable
+ // syntax highlighting for the entire file.
+ var SYNTAX_MAX_LINE_LENGTH = 500;
+
Polymer({
is: 'gr-diff-builder',
/**
+ * Fired when the diff begins rendering.
+ *
+ * @event render-start
+ */
+
+ /**
* Fired when the diff is rendered.
*
* @event render
@@ -121,10 +131,16 @@
reporting.time(TimingLabel.TOTAL);
reporting.time(TimingLabel.CONTENT);
+ this.fire('render-start');
return this.$.processor.process(this.diff.content).then(function() {
if (this.isImageDiff) {
this._builder.renderDiffImages();
}
+
+ if (this._anyLineTooLong()) {
+ this.$.syntaxLayer.enabled = false;
+ }
+
reporting.timeEnd(TimingLabel.CONTENT);
reporting.time(TimingLabel.SYNTAX);
this.$.syntaxLayer.process().then(function() {
@@ -376,6 +392,18 @@
Polymer.dom.flush();
parent.removeChild(thread);
},
+
+ /**
+ * @returns {Boolean} whether any of the lines in _groups are longer
+ * than SYNTAX_MAX_LINE_LENGTH.
+ */
+ _anyLineTooLong: function() {
+ return this._groups.reduce(function(acc, group) {
+ return acc || group.lines.reduce(function(acc, line) {
+ return acc || line.text.length >= SYNTAX_MAX_LINE_LENGTH;
+ }, false);
+ }, false);
+ },
});
})();
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index af44629..341e959 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -613,6 +613,31 @@
assert.strictEqual(sections[0], section[0]);
assert.strictEqual(sections[1], section[1]);
});
+
+ test('render-start and render are fired', function() {
+ var fireStub = sinon.stub(element, 'fire');
+ element.render({left: [], right: []}, {});
+ flush(function() {
+ assert.isTrue(fireStub.calledWithExactly('render-start'));
+ assert.isTrue(fireStub.calledWithExactly('render'));
+ done();
+ });
+ });
+
+ test('rendering normal-sized diff does not disable syntax', function() {
+ flush(function() {
+ assert.isTrue(element.$.syntaxLayer.enabled);
+ });
+ });
+
+ test('rendering large diff disables syntax', function() {
+ // Before it renders, set the first diff line to 500 '*' characters.
+ element.diff.content[0].a = new Array(501).join('*');
+
+ flush(function() {
+ assert.isFalse(element.$.syntaxLayer.enabled);
+ });
+ });
});
suite('mock-diff', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index c3b6233..864712d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -44,15 +44,20 @@
padding: .5em .7em;
}
.header {
+ cursor: pointer;
display: flex;
- padding-bottom: 0;
font-family: 'Open Sans', sans-serif;
+ padding-bottom: 0;
}
- .headerLeft {
+ .headerMiddle {
+ color: #666;
flex: 1;
+ overflow: hidden;
}
.authorName,
.draftLabel {
+ display: block;
+ float: left;
font-weight: bold;
}
.draftLabel {
@@ -62,6 +67,7 @@
.date {
justify-content: flex-end;
margin-left: 5px;
+ white-space: nowrap;
}
a.date:link,
a.date:visited {
@@ -113,19 +119,62 @@
background-color: #fff;
display: block;
}
+ .show-hide {
+ margin-left: .4em;
+ }
+ input.show-hide {
+ display: none;
+ }
+ label.show-hide {
+ color: #000;
+ cursor: pointer;
+ display: block;
+ font-size: .8em;
+ height: 1.1em;
+ margin-top: .1em;
+ }
+ #container .collapsedContent {
+ display: none;
+ }
+ #container.collapsed {
+ padding-bottom: 3px;
+ }
+ #container.collapsed .collapsedContent {
+ display: block;
+ overflow: hidden;
+ padding-left: 5px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ #container.collapsed .actions,
+ #container.collapsed gr-linked-text,
+ #container.collapsed iron-autogrow-textarea {
+ display: none;
+ }
</style>
<div id="container"
class="container"
on-mouseenter="_handleMouseEnter"
on-mouseleave="_handleMouseLeave">
- <div class="header" id="header">
+ <div class="header" id="header" on-click="_handleToggleCollapsed">
<div class="headerLeft">
<span class="authorName">[[comment.author.name]]</span>
<span class="draftLabel">DRAFT</span>
</div>
+ <div class="headerMiddle">
+ <span class="collapsedContent">[[comment.message]]</span>
+ </div>
<a class="date" href$="[[_computeLinkToComment(comment)]]" on-tap="_handleLinkTap">
<gr-date-formatter date-str="[[comment.updated]]"></gr-date-formatter>
</a>
+ <div class="show-hide">
+ <label class="show-hide">
+ <input type="checkbox" class="show-hide"
+ checked$="[[_commentCollapsed]]"
+ on-change="_handleToggleCollapsed">
+ [[_computeShowHideText(_commentCollapsed)]]
+ </label>
+ </div>
</div>
<iron-autogrow-textarea
id="editTextarea"
@@ -137,6 +186,7 @@
<gr-linked-text class="message"
pre
content="[[comment.message]]"
+ collapsed="[[_commentCollapsed]]"
config="[[projectConfig.commentlinks]]"></gr-linked-text>
<div class="actions" hidden$="[[!showActions]]">
<gr-button class="action reply" on-tap="_handleReply">Reply</gr-button>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index 07badbf..791f949 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -81,6 +81,11 @@
},
patchNum: String,
showActions: Boolean,
+ _commentCollapsed: {
+ type: Boolean,
+ value: true,
+ observer: '_toggleCollapseClass',
+ },
projectConfig: Object,
_xhrPromise: Object, // Used for testing.
@@ -96,10 +101,20 @@
'_loadLocalDraft(changeNum, patchNum, comment)',
],
+ attached: function() {
+ if (this.editing) {
+ this._commentCollapsed = false;
+ }
+ },
+
detached: function() {
this.cancelDebouncer('fire-update');
},
+ _computeShowHideText: function(collapsed) {
+ return collapsed ? 'â—€' : 'â–¼';
+ },
+
save: function() {
this.comment.message = this._messageText;
this.disabled = true;
@@ -210,6 +225,18 @@
}
},
+ _handleToggleCollapsed: function() {
+ this._commentCollapsed = !this._commentCollapsed;
+ },
+
+ _toggleCollapseClass: function(_commentCollapsed) {
+ if (_commentCollapsed) {
+ this.$.container.classList.add('collapsed');
+ } else {
+ this.$.container.classList.remove('collapsed');
+ }
+ },
+
_commentMessageChanged: function(message) {
this._messageText = message || '';
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index bddc3ab..b05746e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -39,6 +39,12 @@
</test-fixture>
<script>
+
+ function isVisible(el) {
+ assert.ok(el);
+ return getComputedStyle(el).getPropertyValue('display') !== 'none';
+ }
+
suite('gr-diff-comment tests', function() {
var element;
setup(function() {
@@ -58,6 +64,32 @@
};
});
+ test('collapsible comments', function() {
+ // When a comment (not draft) is loaded, it should be collapsed
+ assert.isFalse(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is not visible');
+ assert.isFalse(isVisible(element.$$('.actions')),
+ 'actions are not visible');
+ assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is not visible');
+
+ // The header middle content is only visible when comments are collapsed.
+ // It shows the message in a condensed way, and limits to a single line.
+ assert.isTrue(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is visible');
+
+ // When the header row is clicked, the comment should expand
+ MockInteractions.tap(element.$.header);
+ assert.isTrue(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is visible');
+ assert.isTrue(isVisible(element.$$('.actions')),
+ 'actions are visible');
+ assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is not visible');
+ assert.isFalse(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is not visible');
+ });
+
test('proper event fires on reply', function(done) {
element.addEventListener('reply', function(e) {
assert.ok(e.detail.comment);
@@ -135,11 +167,6 @@
};
});
- function isVisible(el) {
- assert.ok(el);
- return getComputedStyle(el).getPropertyValue('display') != 'none';
- }
-
test('button visibility states', function() {
element.showActions = false;
assert.isTrue(element.$$('.actions').hasAttribute('hidden'));
@@ -181,6 +208,67 @@
assert.isTrue(isVisible(element.$$('.cancel')), 'cancel is visible');
});
+ test('collapsible drafts', function() {
+ element.addEventListener('reply', function(e) {
+ assert.ok(e.detail.comment);
+ done();
+ });
+ assert.isFalse(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is not visible');
+ assert.isFalse(isVisible(element.$$('.actions')),
+ 'actions are not visible');
+ assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is not visible');
+ assert.isTrue(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is visible');
+
+ MockInteractions.tap(element.$.header);
+ assert.isTrue(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is visible');
+ assert.isTrue(isVisible(element.$$('.actions')),
+ 'actions are visible');
+ assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is not visible');
+ assert.isFalse(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is is not visible');
+
+ // When the edit button is pressed, should still see the actions
+ // and also textarea
+ MockInteractions.tap(element.$$('.edit'));
+ assert.isFalse(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is not visible');
+ assert.isTrue(isVisible(element.$$('.actions')),
+ 'actions are visible');
+ assert.isTrue(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is visible');
+ assert.isFalse(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is not visible');
+
+ // When toggle again, everything should be hidden except for textarea
+ // and header middle content should be visible
+ MockInteractions.tap(element.$.header);
+ assert.isFalse(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is not visible');
+ assert.isFalse(isVisible(element.$$('.actions')),
+ 'actions are not visible');
+ assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is not visible');
+ assert.isTrue(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is visible');
+
+ // When toggle again, textarea should remain open in the state it was
+ // before
+ MockInteractions.tap(element.$.header);
+ assert.isFalse(isVisible(element.$$('gr-linked-text')),
+ 'gr-linked-text is not visible');
+ assert.isTrue(isVisible(element.$$('.actions')),
+ 'actions are visible');
+ assert.isTrue(isVisible(element.$$('iron-autogrow-textarea')),
+ 'textarea is visible');
+ assert.isFalse(isVisible(element.$$('.collapsedContent')),
+ 'header middle content is not visible');
+ });
+
test('draft creation/cancelation', function(done) {
assert.isFalse(element.editing);
MockInteractions.tap(element.$$('.edit'));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
index 491eded..c8eea3c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -21,7 +21,7 @@
<template>
<gr-cursor-manager
id="cursorManager"
- scroll="keep-visible"
+ scroll="[[_scrollBehavior]]"
cursor-target-class="target-row"
target="{{diffRow}}"></gr-cursor-manager>
</template>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index dd11f2c..e783658 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -24,6 +24,11 @@
UNIFIED: 'UNIFIED_DIFF',
};
+ var ScrollBehavior = {
+ KEEP_VISIBLE: 'keep-visible',
+ NEVER: 'never',
+ };
+
var LEFT_SIDE_CLASS = 'target-side-left';
var RIGHT_SIDE_CLASS = 'target-side-right';
@@ -63,6 +68,18 @@
type: Number,
value: null,
},
+
+ /**
+ * The scroll behavior for the cursor. Values are 'never' and
+ * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
+ * the viewport.
+ */
+ _scrollBehavior: {
+ type: String,
+ value: ScrollBehavior.KEEP_VISIBLE,
+ },
+
+ _listeningForScroll: Boolean,
},
observers: [
@@ -70,6 +87,15 @@
'_diffsChanged(diffs.splices)',
],
+ attached: function() {
+ // Catch when users are scrolling as the view loads.
+ this.listen(window, 'scroll', '_handleWindowScroll');
+ },
+
+ detached: function() {
+ this.unlisten(window, 'scroll', '_handleWindowScroll');
+ },
+
moveLeft: function() {
this.side = DiffSides.LEFT;
if (this._isTargetBlank()) {
@@ -169,12 +195,25 @@
}
},
+ _handleWindowScroll: function() {
+ if (this._listeningForScroll) {
+ this._scrollBehavior = ScrollBehavior.NEVER;
+ this._listeningForScroll = false;
+ }
+ },
+
handleDiffUpdate: function() {
this._updateStops();
if (!this.diffRow) {
this.reInitCursor();
}
+ this._scrollBehavior = ScrollBehavior.KEEP_VISIBLE;
+ this._listeningForScroll = false;
+ },
+
+ _handleDiffRenderStart: function() {
+ this._listeningForScroll = true;
},
/**
@@ -320,12 +359,15 @@
for (i = splice.index;
i < splice.index + splice.addedCount;
i++) {
+ this.listen(this.diffs[i], 'render-start', '_handleDiffRenderStart');
this.listen(this.diffs[i], 'render', 'handleDiffUpdate');
}
for (i = 0;
i < splice.removed && splice.removed.length;
i++) {
+ this.unlisten(splice.removed[i],
+ 'render-start', '_handleDiffRenderStart');
this.unlisten(splice.removed[i], 'render', 'handleDiffUpdate');
}
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index 5bdd138..a7d98e6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -98,6 +98,17 @@
assert.equal(cursorElement.diffRow, firstDeltaRow);
});
+ test('cursor scroll behavior', function() {
+ cursorElement._handleDiffRenderStart();
+ assert.equal(cursorElement._scrollBehavior, 'keep-visible');
+
+ cursorElement._handleWindowScroll();
+ assert.equal(cursorElement._scrollBehavior, 'never');
+
+ cursorElement.handleDiffUpdate();
+ assert.equal(cursorElement._scrollBehavior, 'keep-visible');
+ });
+
suite('unified diff', function() {
setup(function(done) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
index 2dd4c91..f91c8ef 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -86,6 +86,7 @@
},
detached: function() {
+ this.cancel();
this.unlisten(window, 'scroll', '_handleWindowScroll');
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
index 4f8c532..c89d9b5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -591,5 +591,12 @@
});
});
});
+
+ test('detaching cancels', function() {
+ element = fixture('basic');
+ sandbox.stub(element, 'cancel');
+ element.detached();
+ assert(element.cancel.called);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index f69eddd..e6d3653 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -86,6 +86,10 @@
}
},
+ _getCopyEventTarget: function(e) {
+ return Polymer.dom(e).rootTarget;
+ },
+
/**
* Utility function to determine whether an element is a descendant of
* another element with the particular className.
@@ -107,14 +111,15 @@
_handleCopy: function(e) {
var commentSelected = false;
- if (this._elementDescendedFromClass(e.target, SelectionClass.COMMENT)) {
+ var target = this._getCopyEventTarget(e);
+ if (this.classList.contains(SelectionClass.COMMENT)) {
commentSelected = true;
} else {
- if (!this._elementDescendedFromClass(e.target, 'content')) {
+ if (!this._elementDescendedFromClass(target, 'content')) {
return;
}
}
- var lineEl = this.diffBuilder.getLineElByChild(e.target);
+ var lineEl = this.diffBuilder.getLineElByChild(target);
if (!lineEl) {
return;
}
@@ -214,6 +219,9 @@
var content = [];
// Fall back to default copy behavior if the selection lies within one
// comment body.
+ if (range.startContainer === range.endContainer) {
+ return;
+ }
if (this._elementDescendedFromClass(range.commonAncestorContainer,
'message')) {
return;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
index f44d349..71fa233 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -87,6 +87,7 @@
<script>
suite('gr-diff-selection', function() {
var element;
+ var copyEventStub;
var emulateCopyOn = function(target) {
var fakeEvent = {
@@ -96,12 +97,14 @@
setData: sinon.stub(),
},
};
+ element._getCopyEventTarget.returns(target);
element._handleCopy(fakeEvent);
return fakeEvent;
};
setup(function() {
element = fixture('basic');
+ sinon.stub(element, '_getCopyEventTarget');
element._cachedDiffBuilder = {
getLineElByChild: sinon.stub().returns({}),
getSideByLineEl: sinon.stub(),
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 6c73e08..03609c0 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
@@ -104,7 +104,6 @@
this._setReviewed(true);
}
}.bind(this));
-
if (this.changeViewState.diffMode === null) {
// Initialize with user's diff mode preference. Default to
// SIDE_BY_SIDE in the meantime.
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 1eb3f95..566c6a1 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
@@ -457,7 +457,6 @@
// We will simulate a user change of the selected mode.
var newMode = 'UNIFIED_DIFF';
-
// Set the actual value of the select, and simulate the change event.
select.value = newMode;
element.fire('change', {}, {node: select});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index dc818e3..1a6759b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -34,8 +34,9 @@
properties: {
changeNum: String,
- hidden: {
+ expanded: {
type: Boolean,
+ value: true,
observer: '_handleShowDiff',
},
patchRange: Object,
@@ -92,14 +93,7 @@
},
ready: function() {
- // Reload only if the diff is not hidden and params are supplied.
- if (this.changeNum && this.patchRange && this.path && !this.hidden) {
- this.reload();
- }
- },
-
- _handleShowDiff: function(hidden) {
- if (!hidden) {
+ if (this._canRender()) {
this.reload();
}
},
@@ -126,7 +120,7 @@
},
getCursorStops: function() {
- if (this.hidden) {
+ if (!this.expanded) {
return [];
}
@@ -159,6 +153,16 @@
this.toggleClass('no-left');
},
+ _handleShowDiff: function() {
+ if (this._canRender()) {
+ this.reload();
+ }
+ },
+
+ _canRender: function() {
+ return this.changeNum && this.patchRange && this.path && this.expanded;
+ },
+
_getCommentThreads: function() {
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 2f0b6bd..b9b49c0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -468,15 +468,20 @@
assert.equal(drafts[0].id, id);
});
- test('_handleShowDiff reloads when hidden is made false',
+ test('_handleShowDiff reloads when expanded is made true',
function(done) {
+ element.expanded = false;
+ element.changeNum = element._comments.meta.changeNum;
+ element.patchRange = element._comments.meta.patchRange;
+ element.path = element._comments.meta.path;
+
var stub = sinon.stub(element, 'reload', function() {
assert.isTrue(stub.called);
stub.restore();
done();
});
var spy = sinon.spy(element, '_handleShowDiff');
- element.set('hidden', false);
+ element.set('expanded', true);
});
});
});
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 372fea9..1580609 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -110,10 +110,12 @@
},
_loadPlugins: function(plugins) {
+ Gerrit._setPluginsCount(plugins.length);
for (var i = 0; i < plugins.length; i++) {
var scriptEl = document.createElement('script');
scriptEl.defer = true;
scriptEl.src = '/' + plugins[i];
+ scriptEl.onerror = Gerrit._pluginInstalled;
document.body.appendChild(scriptEl);
}
},
@@ -160,13 +162,13 @@
}
},
- _handleLocationChange: function() {
- var hash = location.hash.substring(1);
- var pathname = location.pathname;
+ _handleLocationChange: function(e) {
+ var hash = e.detail.hash.substring(1);
+ var pathname = e.detail.pathname;
if (pathname.startsWith('/c/') && parseInt(hash, 10) > 0) {
pathname += '@' + hash;
}
- this.set('_path', encodeURIComponent(pathname));
+ this.set('_path', pathname);
},
_handleTitleChange: function(e) {
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index f941d0f..b04cd4d 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -64,5 +64,25 @@
assert.equal(gwtLink.href,
'http://' + location.host + '/?polygerrit=0#/test/path');
});
+
+ test('_handleLocationChange handles hashes', function() {
+ var curLocation = {
+ pathname: '/c/1/1/testfile.txt',
+ hash: '#2',
+ host: location.host,
+ };
+
+ var event = {detail: curLocation};
+ var gwtLink = element.$$('#gwtLink');
+ element._handleLocationChange(event);
+ assert.equal(gwtLink.href,
+ 'http://' + location.host + '/?polygerrit=0#/c/1/1/testfile.txt@2');
+ });
+
+ test('sets plugins count', function() {
+ sandbox.stub(Gerrit, '_setPluginsCount');
+ element._loadPlugins([]);
+ assert.isTrue(Gerrit._setPluginsCount.calledWithExactly(0));
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
index 360c281..dc1de17 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
@@ -53,12 +53,17 @@
padding: 0;
text-decoration: none;
}
+ .transparentBackground,
+ gr-button.transparentBackground {
+ background-color: transparent;
+ }
</style>
- <div class="container">
+ <div class$="container [[_getBackgroundClass(transparentBackground)]]">
<gr-account-link account="[[account]]"></gr-account-link>
<gr-button
hidden$="[[!removable]]" hidden
- class="remove" on-tap="_handleRemoveTap">×</gr-button>
+ class$="remove [[_getBackgroundClass(transparentBackground)]]"
+ on-tap="_handleRemoveTap">×</gr-button>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
index 45bf8fe..e33e1fc 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
@@ -28,6 +28,10 @@
type: Boolean,
reflectToAttribute: true,
},
+ transparentBackground: {
+ type: Boolean,
+ value: false,
+ },
},
ready: function() {
@@ -36,6 +40,10 @@
}.bind(this));
},
+ _getBackgroundClass: function(transparent) {
+ return transparent ? 'transparentBackground' : '';
+ },
+
_handleRemoveTap: function(e) {
e.preventDefault();
this.fire('remove', {account: this.account});
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index f136907..9bbcea5 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -47,5 +47,6 @@
</span>
</span>
</template>
+ <script src="../../../scripts/util.js"></script>
<script src="gr-account-label.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
index d3585ef..8d89692 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
@@ -36,7 +36,8 @@
<span>
<a href$="[[_computeOwnerLink(account)]]">
<gr-account-label account="[[account]]"
- avatar-image-size="[[avatarImageSize]]"></gr-account-label>
+ avatar-image-size="[[avatarImageSize]]"
+ show-email="[[_computeShowEmail(account)]]"></gr-account-label>
</a>
</span>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
index 058b27d..0c2ad0b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
@@ -30,5 +30,9 @@
var accountID = account.email || account._account_id;
return '/q/owner:' + encodeURIComponent(accountID) + '+status:open';
},
+
+ _computeShowEmail: function(account) {
+ return !!(account && !account.name);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index 2b5b831..1a84d15 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -48,6 +48,10 @@
assert.equal(element._computeOwnerLink({_account_id: 42}),
'/q/owner:42+status:open');
+
+ assert.equal(element._computeShowEmail({name: 'asd'}), false);
+
+ assert.equal(element._computeShowEmail({}), true);
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index 1967b80..5c0535b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -14,6 +14,7 @@
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-js-api-interface">
@@ -23,4 +24,3 @@
<script src="gr-js-api-interface.js"></script>
<script src="gr-public-js-api.js"></script>
</dom-module>
-
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
index be33ad7..06b25de 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
@@ -81,11 +81,11 @@
this._eventCallbacks[eventName].push(callback);
},
- canSubmitChange: function() {
+ canSubmitChange: function(change, revision) {
var submitCallbacks = this._getEventCallbacks(EventType.SUBMIT_CHANGE);
var cancelSubmit = submitCallbacks.some(function(callback) {
try {
- return callback() === false;
+ return callback(change, revision) === false;
} catch (err) {
console.error(err);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 97e0a18..2e7c53d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -172,5 +172,49 @@
});
});
+ test('_setPluginsCount', function(done) {
+ stub('gr-reporting', {
+ pluginsLoaded: function() {
+ assert.equal(Gerrit._pluginsPending, 0);
+ done();
+ }
+ });
+ Gerrit._setPluginsCount(0);
+ });
+
+ test('_arePluginsLoaded', function() {
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ Gerrit._setPluginsCount(1);
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ Gerrit._setPluginsCount(0);
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ });
+
+ test('_pluginInstalled', function(done) {
+ stub('gr-reporting', {
+ pluginsLoaded: function() {
+ done();
+ }
+ });
+ Gerrit._setPluginsCount(2);
+ Gerrit._pluginInstalled();
+ assert.equal(Gerrit._pluginsPending, 1);
+ Gerrit._pluginInstalled();
+ });
+
+ test('install calls _pluginInstalled', function() {
+ var stub = sinon.stub(Gerrit, '_pluginInstalled');
+ Gerrit.install(function(p) { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ assert.isTrue(stub.calledOnce);
+ stub.restore();
+ });
+
+ test('install calls _pluginInstalled on error', function() {
+ var stub = sinon.stub(Gerrit, '_pluginInstalled');
+ Gerrit.install(function() {}, '0.0pre-alpha');
+ assert.isTrue(stub.calledOnce);
+ stub.restore();
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 21d76f1..ad8c135 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -64,6 +64,9 @@
var Gerrit = window.Gerrit || {};
+ // Number of plugins to initialize, -1 means 'not yet known'.
+ Gerrit._pluginsPending = -1;
+
Gerrit.getPluginName = function() {
console.warn('Gerrit.getPluginName is not supported in PolyGerrit.',
'Please use self.getPluginName() instead.');
@@ -85,12 +88,14 @@
if (opt_version && opt_version !== API_VERSION) {
console.warn('Only version ' + API_VERSION +
' is supported in PolyGerrit. ' + opt_version + ' was given.');
+ Gerrit._pluginInstalled();
return;
}
// TODO(andybons): Polyfill currentScript for IE10/11 (edge supports it).
var src = opt_src || (document.currentScript && document.currentScript.src);
callback(new Plugin(src));
+ Gerrit._pluginInstalled();
};
Gerrit.getLoggedIn = function() {
@@ -101,5 +106,20 @@
// NOOP since PolyGerrit doesn’t support GWT plugins.
};
+ Gerrit._setPluginsCount = function(count) {
+ Gerrit._pluginsPending = count;
+ if (Gerrit._arePluginsLoaded()) {
+ document.createElement('gr-reporting').pluginsLoaded();
+ }
+ };
+
+ Gerrit._pluginInstalled = function() {
+ Gerrit._setPluginsCount(Gerrit._pluginsPending - 1);
+ };
+
+ Gerrit._arePluginsLoaded = function() {
+ return Gerrit._pluginsPending === 0;
+ };
+
window.Gerrit = Gerrit;
})(window);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
index d823f8c..f34ffcf 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-path-list-behavior.html">
+<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<script src="../../../bower_components/es6-promise/dist/es6-promise.min.js"></script>
<script src="../../../bower_components/fetch/fetch.js"></script>
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 5eb6e35..6195272 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
@@ -62,7 +62,13 @@
COMMIT_FOOTERS: 17,
// Include push certificate information along with any patch sets.
- PUSH_CERTIFICATES: 18
+ PUSH_CERTIFICATES: 18,
+
+ // Include change's reviewer updates.
+ REVIEWER_UPDATES: 19,
+
+ // Set the submittable boolean.
+ SUBMITTABLE: 20
};
Polymer({
@@ -352,7 +358,8 @@
var options = this._listChangesOptionsToHex(
ListChangesOption.ALL_REVISIONS,
ListChangesOption.CHANGE_ACTIONS,
- ListChangesOption.DOWNLOAD_COMMANDS
+ ListChangesOption.DOWNLOAD_COMMANDS,
+ ListChangesOption.SUBMITTABLE
);
return this._getChangeDetail(changeNum, options, opt_errFn,
opt_cancelCondition);
@@ -858,5 +865,10 @@
deleteAccountSSHKey: function(id) {
return this.send('DELETE', '/accounts/self/sshkeys/' + id);
},
+
+ deleteVote: function(changeID, account, label) {
+ return this.send('DELETE', '/changes/' + changeID +
+ '/reviewers/' + account + '/votes/' + encodeURIComponent(label));
+ },
});
})();
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 042a497..c5df0a2 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -22,8 +22,10 @@
<script src="../bower_components/web-component-tester/browser.js"></script>
<script>
var testFiles = [];
- var basePath = '../elements/';
+ var elementsPath = '../elements/';
+ var behaviorsPath = '../behaviors/';
+ // Elements tests.
[
'change/gr-account-entry/gr-account-entry_test.html',
'change/gr-account-list/gr-account-list_test.html',
@@ -94,10 +96,18 @@
'shared/gr-select/gr-select_test.html',
'shared/gr-storage/gr-storage_test.html',
].forEach(function(file) {
- file = basePath + file;
+ file = elementsPath + file;
testFiles.push(file);
testFiles.push(file + '?dom=shadow');
});
+ // Behaviors tests.
+ [
+ 'gr-path-list-behavior/gr-path-list-behavior_test.html',
+ ].forEach(function(file) {
+ file = behaviorsPath + file;
+ testFiles.push(file);
+ });
+
WCT.loadSuites(testFiles);
</script>
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
new file mode 100644
index 0000000..17736cd
--- /dev/null
+++ b/tools/bzl/asciidoc.bzl
@@ -0,0 +1,338 @@
+def documentation_attributes():
+ return [
+ "toc",
+ 'newline="\\n"',
+ 'asterisk="*"',
+ 'plus="+"',
+ 'caret="^"',
+ 'startsb="["',
+ 'endsb="]"',
+ 'tilde="~"',
+ "last-update-label!",
+ "source-highlighter=prettify",
+ "stylesheet=DEFAULT",
+ "linkcss=true",
+ "prettifydir=.",
+ # Just a placeholder, will be filled in asciidoctor java binary:
+ "revnumber=%s",
+ ]
+
+
+def release_notes_attributes():
+ return [
+ 'toc',
+ 'newline="\\n"',
+ 'asterisk="*"',
+ 'plus="+"',
+ 'caret="^"',
+ 'startsb="["',
+ 'endsb="]"',
+ 'tilde="~"',
+ 'last-update-label!',
+ 'stylesheet=DEFAULT',
+ 'linkcss=true',
+ ]
+
+
+def _replace_macros_impl(ctx):
+ cmd = [
+ ctx.file._exe.path,
+ '--suffix', ctx.attr.suffix,
+ "-s", ctx.file.src.path,
+ "-o", ctx.outputs.out.path,
+ ]
+ if ctx.attr.searchbox:
+ cmd.append('--searchbox')
+ else:
+ cmd.append('--no-searchbox')
+ ctx.action(
+ inputs = [ctx.file._exe, ctx.file.src],
+ outputs = [ctx.outputs.out],
+ command = cmd,
+ progress_message = "Replacing macros in %s" % ctx.file.src.short_path,
+ )
+
+_replace_macros = rule(
+ implementation = _replace_macros_impl,
+ attrs = {
+ "_exe": attr.label(
+ default = Label("//Documentation:replace_macros.py"),
+ allow_single_file = True,
+ ),
+ "src": attr.label(
+ mandatory = True,
+ allow_single_file = [".txt"],
+ ),
+ "suffix": attr.string(mandatory = True),
+ "searchbox": attr.bool(default = True),
+ "out": attr.output(mandatory = True),
+ },
+)
+
+
+def _generate_asciidoc_args(ctx):
+ args = []
+ if ctx.attr.backend:
+ args.extend(["-b", ctx.attr.backend])
+ revnumber = False
+ for attribute in ctx.attr.attributes:
+ if attribute.startswith("revnumber="):
+ revnumber = True
+ else:
+ args.extend(["-a", attribute])
+ if revnumber:
+ args.extend([
+ "--revnumber-file", ctx.file.version.path,
+ ])
+ for src in ctx.files.srcs:
+ args.append(src.path)
+ return args
+
+
+def _invoke_replace_macros(name, src, suffix, searchbox):
+ fn = src
+ if fn.startswith(":"):
+ fn = src[1:]
+
+ _replace_macros(
+ name = "macros_%s_%s" % (name, fn),
+ src = src,
+ out = fn + suffix,
+ suffix = suffix,
+ searchbox = searchbox,
+ )
+
+ return ":" + fn + suffix, fn.replace(".txt", ".html")
+
+
+def _asciidoc_impl(ctx):
+ args = [
+ "--bazel",
+ "--in-ext", ".txt" + ctx.attr.suffix,
+ "--out-ext", ".html",
+ ]
+ args.extend(_generate_asciidoc_args(ctx))
+ ctx.action(
+ inputs = ctx.files.srcs + [ctx.executable._exe, ctx.file.version],
+ outputs = ctx.outputs.outs,
+ executable = ctx.executable._exe,
+ arguments = args,
+ progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
+ )
+
+_asciidoc = rule(
+ implementation = _asciidoc_impl,
+ attrs = {
+ "_exe": attr.label(
+ default = Label("//lib/asciidoctor:asciidoc"),
+ allow_files = True,
+ executable = True,
+ ),
+ "srcs": attr.label_list(mandatory = True, allow_files = True),
+ "version": attr.label(
+ default = Label("//:version.txt"),
+ allow_single_file = True,
+ ),
+ "suffix": attr.string(mandatory = True),
+ "backend": attr.string(),
+ "attributes": attr.string_list(),
+ "outs": attr.output_list(mandatory = True),
+ },
+)
+
+
+def _genasciidoc_htmlonly(
+ name,
+ srcs = [],
+ attributes = [],
+ backend = None,
+ searchbox = True,
+ **kwargs):
+ SUFFIX = "." + name + "_macros"
+ new_srcs = []
+ outs = ["asciidoctor.css"]
+
+ for src in srcs:
+ new_src, html_name = _invoke_replace_macros(name, src, SUFFIX, searchbox)
+ new_srcs.append(new_src)
+ outs.append(html_name)
+
+ _asciidoc(
+ name = name + "_gen",
+ srcs = new_srcs,
+ suffix = SUFFIX,
+ backend = backend,
+ attributes = attributes,
+ outs = outs,
+ )
+
+ native.filegroup(
+ name = name,
+ data = outs,
+ **kwargs
+ )
+
+
+def genasciidoc(
+ name,
+ srcs = [],
+ attributes = [],
+ backend = None,
+ searchbox = True,
+ resources = True,
+ **kwargs):
+ SUFFIX = "_htmlonly"
+
+ _genasciidoc_htmlonly(
+ name = name + SUFFIX if resources else name,
+ srcs = srcs,
+ attributes = attributes,
+ backend = backend,
+ searchbox = searchbox,
+ **kwargs
+ )
+
+ if resources:
+ htmlonly = ":" + name + SUFFIX
+ native.filegroup(
+ name = name,
+ srcs = [
+ htmlonly,
+ "//Documentation:resources",
+ ],
+ **kwargs
+ )
+
+
+def _asciidoc_html_zip_impl(ctx):
+ args = [
+ "--mktmp",
+ "-z", ctx.outputs.out.path,
+ "--in-ext", ".txt" + ctx.attr.suffix,
+ "--out-ext", ".html",
+ ]
+ args.extend(_generate_asciidoc_args(ctx))
+ ctx.action(
+ inputs = ctx.files.srcs + [ctx.executable._exe, ctx.file.version],
+ outputs = [ctx.outputs.out],
+ executable = ctx.executable._exe,
+ arguments = args,
+ progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
+ )
+
+_asciidoc_html_zip = rule(
+ implementation = _asciidoc_html_zip_impl,
+ attrs = {
+ "_exe": attr.label(
+ default = Label("//lib/asciidoctor:asciidoc"),
+ allow_files = True,
+ executable = True,
+ ),
+ "srcs": attr.label_list(mandatory = True, allow_files = True),
+ "version": attr.label(
+ default = Label("//:version.txt"),
+ allow_single_file = True,
+ ),
+ "suffix": attr.string(mandatory = True),
+ "backend": attr.string(),
+ "attributes": attr.string_list(),
+ },
+ outputs = {
+ "out": "%{name}.zip",
+ }
+)
+
+
+def _genasciidoc_htmlonly_zip(
+ name,
+ srcs = [],
+ attributes = [],
+ backend = None,
+ searchbox = True,
+ **kwargs):
+ SUFFIX = "." + name + "_expn"
+ new_srcs = []
+
+ for src in srcs:
+ new_src, _ = _invoke_replace_macros(name, src, SUFFIX, searchbox)
+ new_srcs.append(new_src)
+
+ _asciidoc_html_zip(
+ name = name,
+ srcs = new_srcs,
+ suffix = SUFFIX,
+ backend = backend,
+ attributes = attributes,
+ )
+
+
+def _asciidoc_zip_impl(ctx):
+ tmpdir = ctx.outputs.out.path + "_tmpdir"
+ cmd = [
+ "p=$PWD",
+ "mkdir -p %s" % tmpdir,
+ "unzip -q %s -d %s/%s/" % (ctx.file.src.path, tmpdir, ctx.attr.directory),
+ ]
+ for r in ctx.files.resources:
+ if r.path == r.short_path:
+ cmd.append("tar -cf- %s | tar -C %s -xf-" % (r.short_path, tmpdir))
+ else:
+ parent = r.path[:-len(r.short_path)]
+ cmd.append(
+ "tar -C %s -cf- %s | tar -C %s -xf-" % (parent, r.short_path, tmpdir))
+ cmd.extend([
+ "cd %s" % tmpdir,
+ "zip -qr $p/%s *" % ctx.outputs.out.path,
+ ])
+ ctx.action(
+ inputs = [ctx.file.src] + ctx.files.resources,
+ outputs = [ctx.outputs.out],
+ command = " && ".join(cmd),
+ progress_message =
+ "Generating asciidoctor zip file %s" % ctx.outputs.out.short_path,
+ )
+
+_asciidoc_zip = rule(
+ implementation = _asciidoc_zip_impl,
+ attrs = {
+ "src": attr.label(
+ mandatory = True,
+ allow_single_file = [".zip"],
+ ),
+ "resources": attr.label_list(mandatory = True, allow_files = True),
+ "directory": attr.string(mandatory = True),
+ },
+ outputs = {
+ "out": "%{name}.zip",
+ }
+)
+
+
+def genasciidoc_zip(
+ name,
+ srcs = [],
+ attributes = [],
+ directory = None,
+ backend = None,
+ searchbox = True,
+ resources = True,
+ **kwargs):
+ SUFFIX = "_htmlonly"
+
+ _genasciidoc_htmlonly_zip(
+ name = name + SUFFIX if resources else name,
+ srcs = srcs,
+ attributes = attributes,
+ backend = backend,
+ searchbox = searchbox,
+ **kwargs
+ )
+
+ if resources:
+ htmlonly = ":" + name + SUFFIX
+ _asciidoc_zip(
+ name = name,
+ src = htmlonly,
+ resources = ["//Documentation:resources"],
+ directory = directory,
+ )
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index 37cc70c..60ee60b 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -2,7 +2,7 @@
def normalize_target_name(target):
return target.replace("//", "").replace("/", "__").replace(":", "___")
-def license_map(name, targets = [], opts = []):
+def license_map(name, targets = [], opts = [], **kwargs):
"""Generate XML for all targets that depend directly on a LICENSE file"""
xmls = []
tools = [ "//tools/bzl:license-map.py", "//lib:all-licenses" ]
@@ -28,7 +28,8 @@
name = "gen_license_txt_" + name,
cmd = "python $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
outs = [ name + ".txt" ],
- tools = tools
+ tools = tools,
+ **kwargs
)
def license_test(name, target):