Merge changes from topic 'guava-20.0'
* changes:
Remove usage of to-be-deprecated Throwables
Update Guava to 20.0 snapshot version 20160818.201422-323
*submodules:
* Update plugins/replication from branch 'master'
- Remove usage of to-be-deprecated Throwables methods
The propagate and propagateIfPossible methods will be deprecated
in Guava 20. Replace them with the recommended alternatives.
Change-Id: I11d8adcae3000189c16f60179e02a79440ef99d6
diff --git a/Documentation/config-cla.txt b/Documentation/config-cla.txt
index c07a24f..2234808 100644
--- a/Documentation/config-cla.txt
+++ b/Documentation/config-cla.txt
@@ -37,8 +37,13 @@
Each `contributor-agreement` section within the `project.config` file must
have a unique name. The section name will appear in the web UI.
-If not already present, add the UUID of the groups used in the
-`autoVerify` and `accepted` variables in the groups file.
+If not already present, add the group(s) used in the `autoVerify` and
+`accepted` variables in the `groups` file:
+----
+ # UUID Group Name
+ #
+ 3dedb32915ecdbef5fced9f0a2587d164cd614d4 CLA Accepted - Individual
+----
Commit the configuration change, and push it back:
----
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 686491c..7836b09 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3329,7 +3329,9 @@
Full Name and Preferred Email. This may cause messages to be
classified as spam if the user's domain has SPF or DKIM enabled
and <<sendemail.smtpServer,sendemail.smtpServer>> is not a trusted
-relay for that domain.
+relay for that domain. You can specify
+<<sendemail.allowedDomain,sendemail.allowedDomain>> to instruct Gerrit to only
+send as USER if USER is from those domains.
+
* `MIXED`
+
@@ -3355,6 +3357,16 @@
+
By default, MIXED.
+[[sendemail.allowedDomain]]sendemail.allowedDomain::
++
+Only used when `sendemail.from` is set to `USER`.
+List of allowed domains. If user's email matches one of the domains, emails will
+be sent as USER, otherwise as MIXED mode. Wildcards may be specified by
+including `*` to match any number of characters, for example `*.example.com`
+matches any subdomain of `example.com`.
++
+By default, `*`.
+
[[sendemail.smtpServer]]sendemail.smtpServer::
+
Hostname (or IP address) of a SMTP server that will relay
diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py
index bc2d657..15f470c 100755
--- a/Documentation/gen_licenses.py
+++ b/Documentation/gen_licenses.py
@@ -95,16 +95,14 @@
if args.asciidoc:
print("""\
-Gerrit Code Review - Licenses
-=============================
+= Gerrit Code Review - Licenses
Gerrit open source software is licensed under the <<Apache2_0,Apache
License 2.0>>. Executable distributions also include other software
components that are provided under additional licenses.
[[cryptography]]
-Cryptography Notice
--------------------
+== Cryptography Notice
This distribution includes cryptographic software. The country
in which you currently reside may have restrictions on the import,
@@ -139,8 +137,7 @@
link:http://www.bouncycastle.org/java.html[Bouncy Castle Crypto API]
to be installed by the end-user.
-Licenses
---------
+== Licenses
""")
for n in used:
@@ -149,13 +146,13 @@
if args.asciidoc:
print()
print('[[%s]]' % name.replace('.', '_'))
- print(name)
- print('~' * len(name))
+ print("=== " + name)
print()
else:
print()
print(name)
- print('--')
+ print()
+ print('----')
for d in libs:
if d.startswith('//lib:') or d.startswith('//lib/'):
p = d[len('//lib:'):]
@@ -166,12 +163,12 @@
print('* ' + p)
if args.asciidoc:
print()
- print('[[license]]')
- print('[verse]')
- print('--')
+ print('[[%s_license]]' % name.replace('.', '_'))
+ print('----')
with open(n[2:].replace(':', '/')) as fd:
copyfileobj(fd, stdout)
- print('--')
+ print()
+ print('----')
if args.asciidoc:
print("""
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 29b8c5f..ec1335b 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -192,6 +192,7 @@
get::/changes/?q=is:open+owner:self&q=is:open+reviewer:self+-owner:self&q=is:closed+owner:self+limit:5&o=LABELS
****
+[[query-options]]
Additional fields can be obtained by adding `o` parameters, each
option requires more database lookups and slows down the query
response time to the client so they are generally disabled by
diff --git a/gerrit-acceptance-framework/BUCK b/gerrit-acceptance-framework/BUCK
index ba68fa3..7ef71c4 100644
--- a/gerrit-acceptance-framework/BUCK
+++ b/gerrit-acceptance-framework/BUCK
@@ -37,10 +37,20 @@
java_binary(
name = 'acceptance-framework',
+ merge_manifests = False,
+ manifest_file = ':manifest',
deps = [':lib'],
visibility = ['PUBLIC'],
)
+genrule(
+ name = 'manifest',
+ cmd = 'echo "Manifest-Version: 1.0" >$OUT;' +
+ 'echo "Implementation-Title: Gerrit Acceptance Test Framework" >>$OUT;' +
+ 'echo "Implementation-Vendor: Gerrit Code Review Project" >>$OUT',
+ out = 'manifest.txt',
+)
+
java_library(
name = 'lib',
srcs = SRCS,
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
index c71e13f..dde1875 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
@@ -48,9 +48,9 @@
private Path pluginsSitePath;
private Path pluginSubPath;
private Path pluginSource;
- private String pluginName;
private boolean standalone;
+ protected String pluginName;
protected Path testSite;
@Override
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 47d97e3..7bad261 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
@@ -41,6 +41,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
@@ -58,6 +59,7 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
@@ -538,6 +540,149 @@
}
@Test
+ public void pushCommitOfOtherUser() throws Exception {
+ // admin pushes commit of user
+ PushOneCommit push = pushFactory.create(db, user.getIdent(), testRepo);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+
+ ChangeInfo change = gApi.changes().id(result.getChangeId()).get();
+ assertThat(change.owner._accountId).isEqualTo(admin.id.get());
+ CommitInfo commit = change.revisions.get(change.currentRevision).commit;
+ assertThat(commit.author.email).isEqualTo(user.email);
+ assertThat(commit.committer.email).isEqualTo(user.email);
+
+ // check that the author/committer was added as reviewer
+ Collection<AccountInfo> reviewers = change.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId)
+ .isEqualTo(user.getId().get());
+ assertThat(change.reviewers.get(CC)).isNull();
+
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body())
+ .contains(admin.fullName + " has uploaded a new change for review");
+ assertThat(m.body())
+ .contains("Change subject: " + PushOneCommit.SUBJECT + "\n");
+ assertMailFrom(m, admin.email);
+ }
+
+ @Test
+ public void pushCommitOfOtherUserThatCannotSeeChange() throws Exception {
+ // create hidden project that is only visible to administrators
+ Project.NameKey p = createProject("p");
+ ProjectConfig cfg = projectCache.checkedGet(p).getConfig();
+ Util.allow(cfg,
+ Permission.READ,
+ groupCache.get(new AccountGroup.NameKey("Administrators"))
+ .getGroupUUID(),
+ "refs/*");
+ Util.block(cfg, Permission.READ, REGISTERED_USERS, "refs/*");
+ saveProjectConfig(p, cfg);
+
+ // admin pushes commit of user
+ TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
+ PushOneCommit push = pushFactory.create(db, user.getIdent(), repo);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+
+ ChangeInfo change = gApi.changes().id(result.getChangeId()).get();
+ assertThat(change.owner._accountId).isEqualTo(admin.id.get());
+ CommitInfo commit = change.revisions.get(change.currentRevision).commit;
+ assertThat(commit.author.email).isEqualTo(user.email);
+ assertThat(commit.committer.email).isEqualTo(user.email);
+
+ // check the user cannot see the change
+ setApiUser(user);
+ try {
+ gApi.changes().id(result.getChangeId()).get();
+ fail("Expected ResourceNotFoundException");
+ } catch (ResourceNotFoundException e) {
+ // Expected.
+ }
+
+ // check that the author/committer was NOT added as reviewer (he can't see
+ // the change)
+ assertThat(change.reviewers.get(REVIEWER)).isNull();
+ assertThat(change.reviewers.get(CC)).isNull();
+ assertThat(sender.getMessages()).isEmpty();
+ }
+
+ @Test
+ public void pushCommitWithFooterOfOtherUser() throws Exception {
+ // admin pushes commit that references 'user' in a footer
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo,
+ PushOneCommit.SUBJECT + "\n\n"
+ + FooterConstants.REVIEWED_BY.getName() + ": "
+ + user.getIdent().toExternalString(),
+ PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+
+ // check that 'user' was added as reviewer
+ ChangeInfo change = gApi.changes().id(result.getChangeId()).get();
+ Collection<AccountInfo> reviewers = change.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId)
+ .isEqualTo(user.getId().get());
+ assertThat(change.reviewers.get(CC)).isNull();
+
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body()).contains("Hello " + user.fullName + ",\n");
+ assertThat(m.body()).contains("I'd like you to do a code review.");
+ assertThat(m.body())
+ .contains("Change subject: " + PushOneCommit.SUBJECT + "\n");
+ assertMailFrom(m, admin.email);
+ }
+
+ @Test
+ public void pushCommitWithFooterOfOtherUserThatCannotSeeChange()
+ throws Exception {
+ // create hidden project that is only visible to administrators
+ Project.NameKey p = createProject("p");
+ ProjectConfig cfg = projectCache.checkedGet(p).getConfig();
+ Util.allow(cfg,
+ Permission.READ, groupCache
+ .get(new AccountGroup.NameKey("Administrators")).getGroupUUID(),
+ "refs/*");
+ Util.block(cfg, Permission.READ, REGISTERED_USERS, "refs/*");
+ saveProjectConfig(p, cfg);
+
+ // admin pushes commit that references 'user' in a footer
+ TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo,
+ PushOneCommit.SUBJECT + "\n\n" + FooterConstants.REVIEWED_BY.getName()
+ + ": " + user.getIdent().toExternalString(),
+ PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT);
+ PushOneCommit.Result result = push.to("refs/for/master");
+ result.assertOkStatus();
+
+ // check that 'user' cannot see the change
+ setApiUser(user);
+ try {
+ gApi.changes().id(result.getChangeId()).get();
+ fail("Expected ResourceNotFoundException");
+ } catch (ResourceNotFoundException e) {
+ // Expected.
+ }
+
+ // check that 'user' was NOT added as cc ('user' can't see the change)
+ setApiUser(admin);
+ ChangeInfo change = gApi.changes().id(result.getChangeId()).get();
+ assertThat(change.reviewers.get(REVIEWER)).isNull();
+ assertThat(change.reviewers.get(CC)).isNull();
+ assertThat(sender.getMessages()).isEmpty();
+ }
+
+ @Test
public void addReviewerThatCannotSeeChange() throws Exception {
// create hidden project that is only visible to administrators
Project.NameKey p = createProject("p");
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 ee2dbfe..3629e29 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
@@ -52,6 +52,7 @@
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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.change.GetRevisionActions;
@@ -566,6 +567,19 @@
}
@Test
+ public void diffNonExistingFile() throws Exception {
+ PushOneCommit.Result r = createChange();
+
+ exception.expect(ResourceNotFoundException.class);
+ exception.expectMessage("non-existing");
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .file("non-existing")
+ .diff();
+ }
+
+ @Test
public void diffOnMergeCommitChange() throws Exception {
PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index ca669b4..7dee60f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -783,6 +783,23 @@
pushWithReviewerInFooter("Notauser", null);
}
+ @Test
+ public void pushNewPatchsetOverridingStickyLabel() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ LabelType codeReview = Util.codeReview();
+ codeReview.setCopyMaxScore(true);
+ cfg.getLabelSections().put(codeReview.getName(), codeReview);
+ saveProjectConfig(cfg);
+
+ PushOneCommit.Result r = pushTo("refs/for/master%l=Code-Review+2");
+ r.assertOkStatus();
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent", r.getChangeId());
+ r = push.to("refs/for/master%l=Code-Review+1");
+ r.assertOkStatus();
+ }
+
private void pushWithReviewerInFooter(String nameEmail,
TestAccount expectedReviewer) throws Exception {
int n = 5;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index 752f0d2..afd6734 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -16,13 +16,11 @@
import com.google.gerrit.common.audit.Audit;
import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import com.google.gwtjsonrpc.common.VoidResult;
import java.util.List;
import java.util.Set;
@@ -36,14 +34,4 @@
@SignInRequired
void deleteExternalIds(Set<AccountExternalId.Key> keys,
AsyncCallback<Set<AccountExternalId.Key>> callback);
-
- @Audit
- @SignInRequired
- void updateContact(String fullName, String emailAddr,
- AsyncCallback<Account> callback);
-
- @Audit
- @SignInRequired
- void enterAgreement(String agreementName,
- AsyncCallback<VoidResult> callback);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
deleted file mode 100644
index 22482c7..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2008 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.common.data;
-
-import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.RemoteJsonService;
-import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.RpcImpl.Version;
-
-@RpcImpl(version = Version.V2_0)
-public interface AccountService extends RemoteJsonService {
- @SignInRequired
- void myAgreements(AsyncCallback<AgreementInfo> callback);
-}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 2731476..a71ab37 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -47,7 +47,7 @@
Map<String, FileInfo> files() throws RestApiException;
Map<String, FileInfo> files(String base) throws RestApiException;
Map<String, FileInfo> files(int parentNum) throws RestApiException;
- FileApi file(String path);
+ FileApi file(String path) throws RestApiException;
MergeableInfo mergeable() throws RestApiException;
MergeableInfo mergeableOtherBranches() throws RestApiException;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java
index 3cc3fdc..bb4ac9d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java
@@ -17,12 +17,14 @@
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.common.AccountInfo;
-/** Notified whenever a Reviewer is added to a change. */
+import java.util.List;
+
+/** Notified whenever one or more Reviewers are added to a change. */
@ExtensionPoint
public interface ReviewerAddedListener {
interface Event extends ChangeEvent {
- AccountInfo getReviewer();
+ List<AccountInfo> getReviewers();
}
- void onReviewerAdded(Event event);
+ void onReviewersAdded(Event event);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index acd2e78..15668ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -83,6 +83,14 @@
new RestApi("/accounts/").id(account).view("name").get(cb);
}
+ /** Set the account name */
+ public static void setName(String account, String name,
+ AsyncCallback<NativeString> cb) {
+ AccountNameInput input = AccountNameInput.create();
+ input.name(name);
+ new RestApi("/accounts/").id(account).view("name").put(input, cb);
+ }
+
/** Retrieve email addresses */
public static void getEmails(String account,
AsyncCallback<JsArray<EmailInfo>> cb) {
@@ -97,6 +105,13 @@
.ifNoneMatch().put(in, cb);
}
+ /** Set preferred email address */
+ public static void setPreferredEmail(String account, String email,
+ AsyncCallback<NativeString> cb) {
+ new RestApi("/accounts/").id(account).view("emails")
+ .id(email).view("preferred").put(cb);
+ }
+
/** Retrieve SSH keys */
public static void getSshKeys(String account,
AsyncCallback<JsArray<SshKeyInfo>> cb) {
@@ -196,6 +211,14 @@
new RestApi("/accounts/").id(account).view("password.http").delete(cb);
}
+ /** Enter a contributor agreement */
+ public static void enterAgreement(String account, String name,
+ AsyncCallback<NativeString> cb) {
+ AgreementInput in = AgreementInput.create();
+ in.name(name);
+ new RestApi("/accounts/").id(account).view("agreements").put(in, cb);
+ }
+
private static JsArray<ProjectWatchInfo> projectWatchArrayFromSet(
Set<ProjectWatchInfo> set) {
JsArray<ProjectWatchInfo> jsArray = JsArray.createArray().cast();
@@ -205,6 +228,17 @@
return jsArray;
}
+ private static class AgreementInput extends JavaScriptObject {
+ final native void name(String n) /*-{ if(n)this.name=n; }-*/;
+
+ static AgreementInput create() {
+ return createObject().cast();
+ }
+
+ protected AgreementInput() {
+ }
+ }
+
private static class HttpPasswordInput extends JavaScriptObject {
final native void generate(boolean g) /*-{ if(g)this.generate=g; }-*/;
@@ -227,6 +261,17 @@
}
}
+ private static class AccountNameInput extends JavaScriptObject {
+ final native void name(String n) /*-{ if(n)this.name=n; }-*/;
+
+ static AccountNameInput create() {
+ return createObject().cast();
+ }
+
+ protected AccountNameInput() {
+ }
+ }
+
public static void addGpgKey(String account, String armored,
AsyncCallback<NativeMap<GpgKeyInfo>> cb) {
new RestApi("/accounts/")
@@ -243,6 +288,12 @@
.post(GpgKeysInput.delete(fingerprints), cb);
}
+ /** List contributor agreements */
+ public static void getAgreements(String account,
+ AsyncCallback<JsArray<AgreementInfo>> cb) {
+ new RestApi("/accounts/").id(account).view("agreements").get(cb);
+ }
+
private static class GpgKeysInput extends JavaScriptObject {
static GpgKeysInput add(String key) {
return createWithAdd(Natives.arrayOf(key));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index a084612..01b5af3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -141,11 +141,8 @@
String errorDialogTitleRegisterNewEmail();
String newAgreement();
- String agreementStatus();
String agreementName();
String agreementDescription();
- String agreementStatus_EXPIRED();
- String agreementStatus_VERIFIED();
String newAgreementSelectTypeHeading();
String newAgreementNoneAvailable();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index ca2d316..f74d34b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -151,10 +151,7 @@
newAgreement = New Contributor Agreement
-agreementStatus = Status
agreementName = Name
-agreementStatus_EXPIRED = Expired
-agreementStatus_VERIFIED = Verified
agreementDescription = Description
newAgreementSelectTypeHeading = Select an agreement type:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementInfo.java
new file mode 100644
index 0000000..f038818
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementInfo.java
@@ -0,0 +1,26 @@
+// 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.client.account;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class AgreementInfo extends JavaScriptObject {
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native String description() /*-{ return this.description; }-*/;
+ public final native String url() /*-{ return this.url; }-*/;
+
+ protected AgreementInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
index 136de64..e078e60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -15,7 +15,6 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.ErrorDialog;
-import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.rpc.CallbackGroup;
@@ -46,7 +45,6 @@
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtexpui.user.client.AutoCenterDialogBox;
-import com.google.gwtjsonrpc.common.AsyncCallback;
class ContactPanelShort extends Composite {
protected final FlowPanel body;
@@ -61,6 +59,7 @@
NpTextBox nameTxt;
private ListBox emailPick;
private Button registerNewEmail;
+ private OnEditEnabler onEditEnabler;
ContactPanelShort() {
body = new FlowPanel();
@@ -145,7 +144,7 @@
save.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
- doSave(null);
+ doSave();
}
});
@@ -167,6 +166,8 @@
}
}
});
+
+ onEditEnabler = new OnEditEnabler(save, nameTxt);
}
private boolean canEditFullName() {
@@ -230,7 +231,7 @@
updateEmailList();
registerNewEmail.setEnabled(true);
save.setEnabled(false);
- new OnEditEnabler(save, nameTxt);
+ onEditEnabler.updateOriginalValue(nameTxt);
}
display();
}
@@ -249,7 +250,7 @@
currentEmail = account.email();
nameTxt.setText(account.name());
save.setEnabled(false);
- new OnEditEnabler(save, nameTxt);
+ onEditEnabler.updateOriginalValue(nameTxt);
}
private void doRegisterNewEmail() {
@@ -344,10 +345,13 @@
inEmail.setFocus(true);
}
- void doSave(final AsyncCallback<Account> onSave) {
- String newName = canEditFullName() ? nameTxt.getText() : null;
- if (newName != null && newName.trim().isEmpty()) {
+ void doSave() {
+ final String newName;
+ String name = canEditFullName() ? nameTxt.getText() : null;
+ if (name != null && name.trim().isEmpty()) {
newName = null;
+ } else {
+ newName = name;
}
final String newEmail;
@@ -365,24 +369,40 @@
save.setEnabled(false);
registerNewEmail.setEnabled(false);
- Util.ACCOUNT_SEC.updateContact(newName, newEmail,
- new GerritCallback<Account>() {
- @Override
- public void onSuccess(Account result) {
- registerNewEmail.setEnabled(true);
- onSaveSuccess(FormatUtil.asInfo(result));
- if (onSave != null) {
- onSave.onSuccess(result);
- }
- }
+ CallbackGroup group = new CallbackGroup();
+ if (!newEmail.equals(currentEmail)) {
+ AccountApi.setPreferredEmail("self", newEmail,
+ group.add(new GerritCallback<NativeString>() {
+ @Override
+ public void onSuccess(NativeString result) {
+ }
+ }));
+ }
+ AccountApi.setName("self", newName,
+ group.add(new GerritCallback<NativeString>() {
+ @Override
+ public void onSuccess(NativeString result) {
+ }
- @Override
- public void onFailure(final Throwable caught) {
- save.setEnabled(true);
- registerNewEmail.setEnabled(true);
- super.onFailure(caught);
- }
- });
+ @Override
+ public void onFailure(Throwable caught) {
+ save.setEnabled(true);
+ registerNewEmail.setEnabled(true);
+ super.onFailure(caught);
+ }
+ }));
+ group.done();
+ group.addListener(new GerritCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ currentEmail = newEmail;
+ AccountInfo me = Gerrit.getUserAccount();
+ me.email(currentEmail);
+ me.name(newName);
+ onSaveSuccess(me);
+ registerNewEmail.setEnabled(true);
+ }
+ });
}
void onSaveSuccess(AccountInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
index 308cf30..5396b4f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
@@ -16,14 +16,17 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.common.data.ContributorAgreement;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import java.util.List;
+
public class MyAgreementsScreen extends SettingsScreen {
private AgreementTable agreements;
@@ -39,71 +42,54 @@
@Override
protected void onLoad() {
super.onLoad();
- Util.ACCOUNT_SVC.myAgreements(new ScreenLoadCallback<AgreementInfo>(this) {
+ AccountApi.getAgreements(
+ "self", new ScreenLoadCallback<JsArray<AgreementInfo>>(this) {
@Override
- public void preDisplay(final AgreementInfo result) {
- agreements.display(result);
- }
- });
+ public void preDisplay(JsArray<AgreementInfo> result) {
+ agreements.display(Natives.asList(result));
+ }});
}
private static class AgreementTable extends FancyFlexTable<ContributorAgreement> {
AgreementTable() {
table.setWidth("");
- table.setText(0, 1, Util.C.agreementStatus());
- table.setText(0, 2, Util.C.agreementName());
- table.setText(0, 3, Util.C.agreementDescription());
+ table.setText(0, 1, Util.C.agreementName());
+ table.setText(0, 2, Util.C.agreementDescription());
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- for (int c = 1; c < 4; c++) {
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ for (int c = 1; c < 3; c++) {
fmt.addStyleName(0, c, Gerrit.RESOURCES.css().dataHeader());
}
}
- void display(final AgreementInfo result) {
+ void display(List<AgreementInfo> result) {
while (1 < table.getRowCount()) {
table.removeRow(table.getRowCount() - 1);
}
- for (final String k : result.accepted) {
- addOne(result, k);
+ for (AgreementInfo info : result) {
+ addOne(info);
}
}
- void addOne(final AgreementInfo info, final String k) {
- final int row = table.getRowCount();
+ void addOne(AgreementInfo info) {
+ int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
- final ContributorAgreement cla = info.agreements.get(k);
- final String statusName;
- if (cla == null) {
- statusName = Util.C.agreementStatus_EXPIRED();
+ String url = info.url();
+ if (url != null && url.length() > 0) {
+ Anchor a = new Anchor(info.name(), url);
+ a.setTarget("_blank");
+ table.setWidget(row, 1, a);
} else {
- statusName = Util.C.agreementStatus_VERIFIED();
+ table.setText(row, 1, info.name());
}
- table.setText(row, 1, statusName);
-
- if (cla == null) {
- table.setText(row, 2, "");
- table.setText(row, 3, "");
- } else {
- final String url = cla.getAgreementUrl();
- if (url != null && url.length() > 0) {
- final Anchor a = new Anchor(cla.getName(), url);
- a.setTarget("_blank");
- table.setWidget(row, 2, a);
- } else {
- table.setText(row, 2, cla.getName());
- }
- table.setText(row, 3, cla.getDescription());
- }
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- for (int c = 1; c < 4; c++) {
+ table.setText(row, 2, info.description());
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ for (int c = 1; c < 3; c++) {
fmt.addStyleName(row, c, Gerrit.RESOURCES.css().dataCell());
}
-
- setRowItem(row, cla);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
index 14f8e2f..27a6cc7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -17,13 +17,15 @@
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.Request;
@@ -41,7 +43,6 @@
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
-import com.google.gwtjsonrpc.common.VoidResult;
import java.util.HashSet;
import java.util.List;
@@ -73,15 +74,19 @@
@Override
protected void onLoad() {
super.onLoad();
- Util.ACCOUNT_SVC.myAgreements(new GerritCallback<AgreementInfo>() {
+ AccountApi.getAgreements(
+ "self", new GerritCallback<JsArray<AgreementInfo>>() {
@Override
- public void onSuccess(AgreementInfo result) {
+ public void onSuccess(JsArray<AgreementInfo> result) {
if (isAttached()) {
- mySigned = new HashSet<>(result.accepted);
+ mySigned = new HashSet<>();
+ for (AgreementInfo info: Natives.asList(result)) {
+ mySigned.add(info.name());
+ }
postRPC();
}
- }
- });
+ }});
+
Gerrit.SYSTEM_SVC
.contributorAgreements(new GerritCallback<List<ContributorAgreement>>() {
@Override
@@ -199,15 +204,15 @@
}
private void doEnterAgreement() {
- Util.ACCOUNT_SEC.enterAgreement(current.getName(),
- new GerritCallback<VoidResult>() {
+ AccountApi.enterAgreement("self", current.getName(),
+ new GerritCallback<NativeString>() {
@Override
- public void onSuccess(final VoidResult result) {
+ public void onSuccess(NativeString result) {
Gerrit.display(nextToken);
}
@Override
- public void onFailure(final Throwable caught) {
+ public void onFailure(Throwable caught) {
yesIAgreeBox.setText("");
super.onFailure(caught);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
index a0f36b9..b4b4390 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
@@ -15,7 +15,6 @@
package com.google.gerrit.client.account;
import com.google.gerrit.common.data.AccountSecurity;
-import com.google.gerrit.common.data.AccountService;
import com.google.gerrit.common.data.ProjectAdminService;
import com.google.gwt.core.client.GWT;
import com.google.gwtjsonrpc.client.JsonUtil;
@@ -23,14 +22,10 @@
public class Util {
public static final AccountConstants C = GWT.create(AccountConstants.class);
public static final AccountMessages M = GWT.create(AccountMessages.class);
- public static final AccountService ACCOUNT_SVC;
public static final AccountSecurity ACCOUNT_SEC;
public static final ProjectAdminService PROJECT_SVC;
static {
- ACCOUNT_SVC = GWT.create(AccountService.class);
- JsonUtil.bind(ACCOUNT_SVC, "rpc/AccountService");
-
ACCOUNT_SEC = GWT.create(AccountSecurity.class);
JsonUtil.bind(ACCOUNT_SEC, "rpc/AccountSecurity");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
index 819a11f..4391477 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
@@ -73,6 +73,9 @@
widget = w;
}
+ public void updateOriginalValue(final TextBoxBase tb) {
+ originalValue = tb.getValue().trim();
+ }
// Register input widgets to be listened to
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 2c67182..c1f4da0 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
@@ -64,6 +64,7 @@
if (options.enableDefaultUi()) {
filter("/").through(XsrfCookieFilter.class);
+ filter("/accounts/self/detail").through(XsrfCookieFilter.class);
serve("/").with(HostPageServlet.class);
serve("/Gerrit").with(LegacyGerritServlet.class);
serve("/Gerrit/*").with(legacyGerritScreen());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 8594e30..27aff21 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -359,7 +359,6 @@
if (Strings.isNullOrEmpty(entryTitle)) {
entryTitle = rsrc.substring(nameOffset, rsrc.length() - 3).replace('-', ' ');
}
- rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
} else {
entryTitle = rsrc.substring(nameOffset).replace('-', ' ');
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
index 62778eb..d32fdaf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
@@ -28,12 +28,10 @@
install(new FactoryModule() {
@Override
protected void configure() {
- factory(AgreementInfoFactory.Factory.class);
factory(DeleteExternalIds.Factory.class);
factory(ExternalIdDetailFactory.Factory.class);
}
});
rpc(AccountSecurityImpl.class);
- rpc(AccountServiceImpl.class);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index 8fcf9ea..3d05548 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -14,74 +14,31 @@
package com.google.gerrit.httpd.rpc.account;
-import com.google.common.base.Strings;
-import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.AccountSecurity;
-import com.google.gerrit.common.data.ContributorAgreement;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.extensions.events.AgreementSignup;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
class AccountSecurityImpl extends BaseServiceImplementation implements
AccountSecurity {
- private final Realm realm;
- private final ProjectCache projectCache;
- private final Provider<IdentifiedUser> user;
- private final AccountByEmailCache byEmailCache;
- private final AccountCache accountCache;
-
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
- private final GroupCache groupCache;
- private final AuditService auditService;
- private final AgreementSignup agreementSignup;
-
@Inject
AccountSecurityImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
- final Realm r, final Provider<IdentifiedUser> u,
- final ProjectCache pc,
- final AccountByEmailCache abec, final AccountCache uac,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
- final ExternalIdDetailFactory.Factory externalIdDetailFactory,
- final GroupCache groupCache,
- final AuditService auditService,
- AgreementSignup agreementSignup) {
+ final ExternalIdDetailFactory.Factory externalIdDetailFactory) {
super(schema, currentUser);
- realm = r;
- user = u;
- projectCache = pc;
- byEmailCache = abec;
- accountCache = uac;
- this.auditService = auditService;
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
this.externalIdDetailFactory = externalIdDetailFactory;
- this.groupCache = groupCache;
- this.agreementSignup = agreementSignup;
}
@Override
@@ -94,84 +51,4 @@
final AsyncCallback<Set<AccountExternalId.Key>> callback) {
deleteExternalIdsFactory.create(keys).to(callback);
}
-
- @Override
- public void updateContact(final String name, final String emailAddr,
- final AsyncCallback<Account> callback) {
- run(callback, new Action<Account>() {
- @Override
- public Account run(ReviewDb db)
- throws OrmException, Failure, IOException {
- IdentifiedUser self = user.get();
- final Account me = db.accounts().get(self.getAccountId());
- final String oldEmail = me.getPreferredEmail();
- if (realm.allowsEdit(Account.FieldName.FULL_NAME)) {
- me.setFullName(Strings.emptyToNull(name));
- }
- if (!Strings.isNullOrEmpty(emailAddr)
- && !self.hasEmailAddress(emailAddr)) {
- throw new Failure(new PermissionDeniedException("Email address must be verified"));
- }
- me.setPreferredEmail(Strings.emptyToNull(emailAddr));
- db.accounts().update(Collections.singleton(me));
- if (!eq(oldEmail, me.getPreferredEmail())) {
- byEmailCache.evict(oldEmail);
- byEmailCache.evict(me.getPreferredEmail());
- }
- accountCache.evict(me.getId());
- return me;
- }
- });
- }
-
- private static boolean eq(final String a, final String b) {
- if (a == null && b == null) {
- return true;
- }
- return a != null && a.equals(b);
- }
-
- @Override
- public void enterAgreement(final String agreementName,
- final AsyncCallback<VoidResult> callback) {
- run(callback, new Action<VoidResult>() {
- @Override
- public VoidResult run(final ReviewDb db)
- throws OrmException, Failure, IOException {
- ContributorAgreement ca = projectCache.getAllProjects().getConfig()
- .getContributorAgreement(agreementName);
- if (ca == null) {
- throw new Failure(new NoSuchEntityException());
- }
-
- if (ca.getAutoVerify() == null) {
- throw new Failure(new IllegalStateException(
- "cannot enter a non-autoVerify agreement"));
- } else if (ca.getAutoVerify().getUUID() == null) {
- throw new Failure(new NoSuchEntityException());
- }
-
- AccountGroup group = groupCache.get(ca.getAutoVerify().getUUID());
- if (group == null) {
- throw new Failure(new NoSuchEntityException());
- }
-
- Account account = user.get().getAccount();
- agreementSignup.fire(account, ca.getName());
-
- final AccountGroupMember.Key key =
- new AccountGroupMember.Key(account.getId(), group.getId());
- AccountGroupMember m = db.accountGroupMembers().get(key);
- if (m == null) {
- m = new AccountGroupMember(key);
- auditService.dispatchAddAccountsToGroup(account.getId(), Collections
- .singleton(m));
- db.accountGroupMembers().insert(Collections.singleton(m));
- accountCache.evict(m.getAccountId());
- }
-
- return VoidResult.INSTANCE;
- }
- });
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
deleted file mode 100644
index 8fba47d..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import com.google.gerrit.common.data.AccountService;
-import com.google.gerrit.common.data.AgreementInfo;
-import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-class AccountServiceImpl extends BaseServiceImplementation implements
- AccountService {
- private final AgreementInfoFactory.Factory agreementInfoFactory;
-
- @Inject
- AccountServiceImpl(final Provider<ReviewDb> schema,
- final Provider<IdentifiedUser> identifiedUser,
- final AgreementInfoFactory.Factory agreementInfoFactory) {
- super(schema, identifiedUser);
- this.agreementInfoFactory = agreementInfoFactory;
- }
-
- @Override
- public void myAgreements(final AsyncCallback<AgreementInfo> callback) {
- agreementInfoFactory.create().to(callback);
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
deleted file mode 100644
index 91afd97..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import com.google.gerrit.common.data.AgreementInfo;
-import com.google.gerrit.common.data.ContributorAgreement;
-import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-class AgreementInfoFactory extends Handler<AgreementInfo> {
- private static final Logger log = LoggerFactory.getLogger(AgreementInfoFactory.class);
-
- interface Factory {
- AgreementInfoFactory create();
- }
-
- private final IdentifiedUser user;
- private final ProjectCache projectCache;
-
- private AgreementInfo info;
-
- @Inject
- AgreementInfoFactory(final IdentifiedUser user,
- final ProjectCache projectCache) {
- this.user = user;
- this.projectCache = projectCache;
- }
-
- @Override
- public AgreementInfo call() throws Exception {
- List<String> accepted = new ArrayList<>();
- Map<String, ContributorAgreement> agreements = new HashMap<>();
- Collection<ContributorAgreement> cas =
- projectCache.getAllProjects().getConfig().getContributorAgreements();
- for (ContributorAgreement ca : cas) {
- agreements.put(ca.getName(), ca.forUi());
-
- List<AccountGroup.UUID> groupIds = new ArrayList<>();
- for (PermissionRule rule : ca.getAccepted()) {
- if ((rule.getAction() == Action.ALLOW) && (rule.getGroup() != null)) {
- if (rule.getGroup().getUUID() == null) {
- log.warn("group \"" + rule.getGroup().getName() + "\" does not " +
- " exist, referenced in CLA \"" + ca.getName() + "\"");
- } else {
- groupIds.add(new AccountGroup.UUID(rule.getGroup().getUUID().get()));
- }
- }
- }
- if (user.getEffectiveGroups().containsAnyOf(groupIds)) {
- accepted.add(ca.getName());
- }
- }
-
- info = new AgreementInfo();
- info.setAccepted(accepted);
- info.setAgreements(agreements);
- return info;
- }
-}
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index 777f3a8..7e12f232 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -13,10 +13,20 @@
java_binary(
name = 'plugin-api',
+ merge_manifests = False,
+ manifest_file = ':manifest',
deps = [':lib'],
visibility = ['PUBLIC'],
)
+genrule(
+ name = 'manifest',
+ cmd = 'echo "Manifest-Version: 1.0" >$OUT;' +
+ 'echo "Implementation-Title: Gerrit Plugin API" >>$OUT;' +
+ 'echo "Implementation-Vendor: Gerrit Code Review Project" >>$OUT',
+ out = 'manifest.txt',
+)
+
java_library(
name = 'lib',
exported_deps = PLUGIN_API + [
@@ -29,7 +39,7 @@
'//gerrit-reviewdb:server',
'//lib:args4j',
'//lib:blame-cache',
- '//lib/dropwizard:dropwizard-core',
+ '//lib:gson',
'//lib:guava',
'//lib:gwtorm',
'//lib:jsch',
@@ -37,6 +47,7 @@
'//lib:servlet-api-3_1',
'//lib:velocity',
'//lib/commons:lang',
+ '//lib/dropwizard:dropwizard-core',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index d723667..bdceb7b 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -9,7 +9,6 @@
ALTER TABLE patch_set_approvals CLUSTER ON patch_set_approvals_pkey;
ALTER TABLE account_group_members CLUSTER ON account_group_members_pkey;
-ALTER TABLE starred_changes CLUSTER ON starred_changes_pkey;
CLUSTER;
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 bc2ec06..d69ad3f 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
@@ -77,22 +77,54 @@
this.psUtil = psUtil;
}
+ /**
+ * Apply approval copy settings from prior PatchSets to a new PatchSet.
+ *
+ * @param db review database.
+ * @param ctl change control for user uploading PatchSet
+ * @param ps new PatchSet
+ * @throws OrmException
+ */
public void copy(ReviewDb db, ChangeControl ctl, PatchSet ps)
throws OrmException {
- db.patchSetApprovals().insert(getForPatchSet(db, ctl, ps));
+ copy(db, ctl, ps, Collections.<PatchSetApproval>emptyList());
+ }
+
+ /**
+ * Apply approval copy settings from prior PatchSets to a new PatchSet.
+ *
+ * @param db review database.
+ * @param ctl change control for user uploading PatchSet
+ * @param ps new PatchSet
+ * @param dontCopy PatchSetApprovals indicating which (account, label) pairs
+ * should not be copied
+ * @throws OrmException
+ */
+ public void copy(ReviewDb db, ChangeControl ctl, PatchSet ps,
+ Iterable<PatchSetApproval> dontCopy) throws OrmException {
+ db.patchSetApprovals().insert(
+ getForPatchSet(db, ctl, ps, dontCopy));
}
Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
ChangeControl ctl, PatchSet.Id psId) throws OrmException {
+ return getForPatchSet(db, ctl, psId,
+ Collections.<PatchSetApproval>emptyList());
+ }
+
+ Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
+ ChangeControl ctl, PatchSet.Id psId,
+ Iterable<PatchSetApproval> dontCopy) throws OrmException {
PatchSet ps = psUtil.get(db, ctl.getNotes(), psId);
if (ps == null) {
return Collections.emptyList();
}
- return getForPatchSet(db, ctl, ps);
+ return getForPatchSet(db, ctl, ps, dontCopy);
}
private Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
- ChangeControl ctl, PatchSet ps) throws OrmException {
+ ChangeControl ctl, PatchSet ps,
+ Iterable<PatchSetApproval> dontCopy) throws OrmException {
checkNotNull(ps, "ps should not be null");
ChangeData cd = changeDataFactory.create(db, ctl);
try {
@@ -103,10 +135,16 @@
Table<String, Account.Id, PatchSetApproval> wontCopy =
HashBasedTable.create();
+ for (PatchSetApproval psa : dontCopy) {
+ wontCopy.put(psa.getLabel(), psa.getAccountId(), psa);
+ }
+
Table<String, Account.Id, PatchSetApproval> byUser =
HashBasedTable.create();
for (PatchSetApproval psa : all.get(ps.getId())) {
- byUser.put(psa.getLabel(), psa.getAccountId(), psa);
+ if (!wontCopy.contains(psa.getLabel(), psa.getAccountId())) {
+ byUser.put(psa.getLabel(), psa.getAccountId(), psa);
+ }
}
TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
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 e0526e4..5a5d16c 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
@@ -43,11 +43,15 @@
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -73,6 +77,9 @@
*/
@Singleton
public class ApprovalsUtil {
+ private static final Logger log =
+ LoggerFactory.getLogger(ApprovalsUtil.class);
+
private static final Ordering<PatchSetApproval> SORT_APPROVALS =
Ordering.natural()
.onResultOf(
@@ -99,13 +106,19 @@
}
private final NotesMigration migration;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final ChangeControl.GenericFactory changeControlFactory;
private final ApprovalCopier copier;
@VisibleForTesting
@Inject
public ApprovalsUtil(NotesMigration migration,
+ IdentifiedUser.GenericFactory userFactory,
+ ChangeControl.GenericFactory changeControlFactory,
ApprovalCopier copier) {
this.migration = migration;
+ this.userFactory = userFactory;
+ this.changeControlFactory = changeControlFactory;
this.copier = copier;
}
@@ -164,8 +177,8 @@
PatchSetInfo info, Iterable<Account.Id> wantReviewers,
Collection<Account.Id> existingReviewers) throws OrmException {
return addReviewers(db, update, labelTypes, change, ps.getId(),
- ps.isDraft(), info.getAuthor().getAccount(),
- info.getCommitter().getAccount(), wantReviewers, existingReviewers);
+ info.getAuthor().getAccount(), info.getCommitter().getAccount(),
+ wantReviewers, existingReviewers);
}
public List<PatchSetApproval> addReviewers(ReviewDb db, ChangeNotes notes,
@@ -189,12 +202,12 @@
existingReviewers.add(entry.getKey());
}
}
- return addReviewers(db, update, labelTypes, change, psId, false, null, null,
+ return addReviewers(db, update, labelTypes, change, psId, null, null,
wantReviewers, existingReviewers);
}
private List<PatchSetApproval> addReviewers(ReviewDb db, ChangeUpdate update,
- LabelTypes labelTypes, Change change, PatchSet.Id psId, boolean isDraft,
+ LabelTypes labelTypes, Change change, PatchSet.Id psId,
Account.Id authorId, Account.Id committerId,
Iterable<Account.Id> wantReviewers,
Collection<Account.Id> existingReviewers) throws OrmException {
@@ -204,11 +217,11 @@
}
Set<Account.Id> need = Sets.newLinkedHashSet(wantReviewers);
- if (authorId != null && !isDraft) {
+ if (authorId != null && canSee(db, update.getNotes(), authorId)) {
need.add(authorId);
}
- if (committerId != null && !isDraft) {
+ if (committerId != null && canSee(db, update.getNotes(), authorId)) {
need.add(committerId);
}
need.remove(change.getOwner());
@@ -229,6 +242,17 @@
return Collections.unmodifiableList(cells);
}
+ private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) {
+ try {
+ IdentifiedUser user = userFactory.create(accountId);
+ return changeControlFactory.controlFor(notes, user).isVisible(db);
+ } catch (OrmException | NoSuchChangeException e) {
+ log.warn(String.format("Failed to check if account %d can see change %d",
+ accountId.get(), notes.getChangeId().get()), e);
+ return false;
+ }
+ }
+
/**
* Adds accounts to a change as reviewers in the CC state.
*
@@ -254,25 +278,40 @@
return need;
}
- public void addApprovals(ReviewDb db, ChangeUpdate update,
+ /**
+ * Adds approvals to ChangeUpdate and writes to ReviewDb.
+ *
+ * @param db review database.
+ * @param update change update.
+ * @param labelTypes label types for the containing project.
+ * @param ps patch set being approved.
+ * @param changeCtl change control for user adding approvals.
+ * @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 {
- if (!approvals.isEmpty()) {
- checkApprovals(approvals, changeCtl);
- List<PatchSetApproval> cells = new ArrayList<>(approvals.size());
- 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));
- update.putApproval(vote.getKey(), vote.getValue());
- }
- db.patchSetApprovals().insert(cells);
+ if (approvals.isEmpty()) {
+ return Collections.emptyList();
}
+ checkApprovals(approvals, changeCtl);
+ List<PatchSetApproval> cells = new ArrayList<>(approvals.size());
+ 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));
+ }
+ for (PatchSetApproval psa : cells) {
+ update.putApproval(psa.getLabel(), psa.getValue());
+ }
+ db.patchSetApprovals().insert(cells);
+ return cells;
}
public static void checkLabel(LabelTypes labelTypes, String name, Short value) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
index 14cc74e..99e6bd8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
@@ -15,6 +15,8 @@
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
@@ -24,11 +26,20 @@
@Singleton
public class GetEmails implements RestReadView<AccountResource> {
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
+
+ @Inject
+ GetEmails(IdentifiedUser.GenericFactory identifiedUserFactory) {
+ this.identifiedUserFactory = identifiedUserFactory;
+ }
@Override
public List<EmailInfo> apply(AccountResource rsrc) {
+ IdentifiedUser user =
+ identifiedUserFactory.create(rsrc.getUser().getAccountId());
+
List<EmailInfo> emails = new ArrayList<>();
- for (String email : rsrc.getUser().getEmailAddresses()) {
+ for (String email : user.getEmailAddresses()) {
if (email != null) {
EmailInfo e = new EmailInfo();
e.email = email;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 6b5e83c..213f90d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -321,9 +321,13 @@
}
@Override
- public FileApi file(String path) {
- return fileApi.create(files.parse(revision,
- IdString.fromDecoded(path)));
+ public FileApi file(String path) throws RestApiException {
+ try {
+ return fileApi.create(files.parse(revision,
+ IdString.fromDecoded(path)));
+ } catch (IOException e) {
+ throw new RestApiException("Cannot retrieve file", e);
+ }
}
@Override
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 6333809..84781f5 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
@@ -19,6 +19,8 @@
import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
import com.google.common.base.MoreObjects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Sets;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
@@ -34,6 +36,7 @@
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;
import com.google.gerrit.server.extensions.events.CommentAdded;
@@ -48,6 +51,7 @@
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
@@ -86,6 +90,7 @@
LoggerFactory.getLogger(ChangeInserter.class);
private final ProjectControl.GenericFactory projectControlFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
@@ -128,6 +133,7 @@
@Inject
ChangeInserter(ProjectControl.GenericFactory projectControlFactory,
+ IdentifiedUser.GenericFactory userFactory,
ChangeControl.GenericFactory changeControlFactory,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
@@ -142,6 +148,7 @@
@Assisted RevCommit commit,
@Assisted String refName) {
this.projectControlFactory = projectControlFactory;
+ this.userFactory = userFactory;
this.changeControlFactory = changeControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.psUtil = psUtil;
@@ -353,8 +360,10 @@
update.fixStatus(change.getStatus());
LabelTypes labelTypes = ctl.getProjectControl().getLabelTypes();
- approvalsUtil.addReviewers(db, update, labelTypes, change,
- patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet());
+ approvalsUtil.addReviewers(db, update, labelTypes, change, patchSet,
+ patchSetInfo,
+ filterOnChangeVisibility(db, ctx.getNotes(), reviewers),
+ Collections.<Account.Id> emptySet());
approvalsUtil.addApprovals(db, update, labelTypes, patchSet,
ctx.getControl(), approvals);
if (message != null) {
@@ -368,6 +377,25 @@
return true;
}
+ private Set<Account.Id> filterOnChangeVisibility(final ReviewDb db,
+ final ChangeNotes notes, Set<Account.Id> accounts) {
+ return Sets.filter(accounts, new Predicate<Account.Id>() {
+ @Override
+ public boolean apply(Account.Id accountId) {
+ try {
+ IdentifiedUser user = userFactory.create(accountId);
+ return changeControlFactory.controlFor(notes, user).isVisible(db);
+ } catch (OrmException | NoSuchChangeException e) {
+ log.warn(
+ String.format("Failed to check if account %d can see change %d",
+ accountId.get(), notes.getChangeId().get()),
+ e);
+ return false;
+ }
+ }
+ });
+ }
+
@Override
public void postUpdate(Context ctx) throws OrmException, NoSuchChangeException {
if (sendMail) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 48f00c3..ab92a97 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -926,12 +926,19 @@
.toSortedList(AccountInfoComparator.ORDER_NULLS_FIRST);
}
+ @Nullable
+ private Repository openRepoIfNecessary(ChangeControl ctl) throws IOException {
+ if (has(ALL_COMMITS) || has(CURRENT_COMMIT) || has(COMMIT_FOOTERS)) {
+ return repoManager.openRepository(ctl.getProject().getNameKey());
+ }
+ return null;
+ }
+
private Map<String, RevisionInfo> revisions(ChangeControl ctl, ChangeData cd,
Map<PatchSet.Id, PatchSet> map) throws PatchListNotAvailableException,
GpgException, OrmException, IOException {
Map<String, RevisionInfo> res = new LinkedHashMap<>();
- try (Repository repo =
- repoManager.openRepository(ctl.getProject().getNameKey())) {
+ try (Repository repo = openRepoIfNecessary(ctl)) {
for (PatchSet in : map.values()) {
if ((has(ALL_REVISIONS)
|| in.getId().equals(ctl.getChange().currentPatchSetId()))
@@ -975,8 +982,7 @@
throws PatchListNotAvailableException, GpgException, OrmException,
IOException {
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
- try (Repository repo =
- repoManager.openRepository(ctl.getProject().getNameKey())) {
+ try (Repository repo = openRepoIfNecessary(ctl)) {
RevisionInfo rev = toRevisionInfo(
ctl, changeDataFactory.create(db.get(), ctl), in, repo, true);
accountLoader.fill();
@@ -985,7 +991,7 @@
}
private RevisionInfo toRevisionInfo(ChangeControl ctl, ChangeData cd,
- PatchSet in, Repository repo, boolean fillCommit)
+ PatchSet in, @Nullable Repository repo, boolean fillCommit)
throws PatchListNotAvailableException, GpgException, OrmException,
IOException {
Change c = ctl.getChange();
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 2302b70..f0075ee 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
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -31,10 +32,11 @@
* implementation changes, which might invalidate old entries).
*/
public interface ChangeKindCache {
- ChangeKind getChangeKind(ProjectState project, Repository repo,
+ ChangeKind getChangeKind(ProjectState project, @Nullable Repository repo,
ObjectId prior, ObjectId next);
ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch);
- ChangeKind getChangeKind(Repository repo, ChangeData cd, PatchSet patch);
+ ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd,
+ 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 edc1b12..b23bcf8 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
@@ -22,9 +22,11 @@
import com.google.common.cache.Cache;
import com.google.common.cache.Weigher;
import com.google.common.collect.FluentIterable;
+import com.google.gerrit.common.Nullable;
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.cache.CacheModule;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -100,11 +102,13 @@
}
@Override
- public ChangeKind getChangeKind(ProjectState project, Repository repo,
- ObjectId prior, ObjectId next) {
+ public ChangeKind getChangeKind(ProjectState project,
+ @Nullable Repository repo, ObjectId prior, ObjectId next) {
try {
Key key = new Key(prior, next, useRecursiveMerge);
- return new Loader(key, repo).call();
+ return new Loader(
+ key, repoManager, project.getProject().getNameKey(), repo)
+ .call();
} catch (IOException e) {
log.warn("Cannot check trivial rebase of new patch set " + next.name()
+ " in " + project.getProject().getName(), e);
@@ -120,7 +124,7 @@
}
@Override
- public ChangeKind getChangeKind(Repository repo, ChangeData cd,
+ public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd,
PatchSet patch) {
return getChangeKindInternal(this, repo, cd, patch, projectCache);
}
@@ -191,11 +195,16 @@
private static class Loader implements Callable<ChangeKind> {
private final Key key;
- private final Repository repo;
+ private final GitRepositoryManager repoManager;
+ private final Project.NameKey projectName;
+ private final Repository alreadyOpenRepo;
- private Loader(Key key, Repository repo) {
+ private Loader(Key key, GitRepositoryManager repoManager,
+ Project.NameKey projectName, @Nullable Repository alreadyOpenRepo) {
this.key = key;
- this.repo = repo;
+ this.repoManager = repoManager;
+ this.projectName = projectName;
+ this.alreadyOpenRepo = alreadyOpenRepo;
}
@Override
@@ -204,6 +213,12 @@
return ChangeKind.NO_CODE_CHANGE;
}
+ Repository repo = alreadyOpenRepo;
+ boolean close = false;
+ if (repo == null) {
+ repo = repoManager.openRepository(projectName);
+ close = true;
+ }
try (RevWalk walk = new RevWalk(repo)) {
RevCommit prior = walk.parseCommit(key.prior);
walk.parseBody(prior);
@@ -246,6 +261,10 @@
// it was a rework.
}
return ChangeKind.REWORK;
+ } finally {
+ if (close) {
+ repo.close();
+ }
}
}
@@ -321,11 +340,14 @@
}
@Override
- public ChangeKind getChangeKind(ProjectState project, Repository repo,
- ObjectId prior, ObjectId next) {
+ public ChangeKind getChangeKind(ProjectState project,
+ @Nullable Repository repo, ObjectId prior, ObjectId next) {
try {
Key key = new Key(prior, next, useRecursiveMerge);
- return cache.get(key, new Loader(key, repo));
+ return cache.get(
+ key,
+ new Loader(
+ key, repoManager, project.getProject().getNameKey(), repo));
} catch (ExecutionException e) {
log.warn("Cannot check trivial rebase of new patch set " + next.name()
+ " in " + project.getProject().getName(), e);
@@ -340,14 +362,14 @@
}
@Override
- public ChangeKind getChangeKind(Repository repo, ChangeData cd,
+ public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd,
PatchSet patch) {
return getChangeKindInternal(this, repo, cd, patch, projectCache);
}
private static ChangeKind getChangeKindInternal(
ChangeKindCache cache,
- Repository repo,
+ @Nullable Repository repo,
ChangeData change,
PatchSet patch,
ProjectCache projectCache) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 35dbec1..1034c99c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -48,6 +48,7 @@
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -67,11 +68,15 @@
public class Files implements ChildCollection<RevisionResource, FileResource> {
private final DynamicMap<RestView<FileResource>> views;
private final Provider<ListFiles> list;
+ private final GitRepositoryManager repoManager;
@Inject
- Files(DynamicMap<RestView<FileResource>> views, Provider<ListFiles> list) {
+ Files(DynamicMap<RestView<FileResource>> views,
+ Provider<ListFiles> list,
+ GitRepositoryManager repoManager) {
this.views = views;
this.list = list;
+ this.repoManager = repoManager;
}
@Override
@@ -85,8 +90,17 @@
}
@Override
- public FileResource parse(RevisionResource rev, IdString id) {
- return new FileResource(rev, id.get());
+ public FileResource parse(RevisionResource rev, IdString id)
+ throws ResourceNotFoundException, IOException {
+ try (Repository repo = repoManager.openRepository(rev.getProject());
+ RevWalk rw = new RevWalk(repo)) {
+ RevTree tree = rw.parseTree(
+ ObjectId.fromString(rev.getPatchSet().getRevision().get()));
+ if (TreeWalk.forPath(repo, id.get(), tree) != null) {
+ return new FileResource(rev, id.get());
+ }
+ }
+ throw new ResourceNotFoundException(id);
}
public static final class ListFiles implements RestReadView<RevisionResource> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index fb37d9d..4166ca3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
+import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
@@ -40,7 +41,6 @@
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupMembers;
@@ -96,7 +96,6 @@
private final Provider<IdentifiedUser> user;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final Config cfg;
- private final AccountCache accountCache;
private final ReviewerJson json;
private final ReviewerAdded reviewerAdded;
private final NotesMigration migration;
@@ -115,7 +114,6 @@
Provider<IdentifiedUser> user,
IdentifiedUser.GenericFactory identifiedUserFactory,
@GerritServerConfig Config cfg,
- AccountCache accountCache,
ReviewerJson json,
ReviewerAdded reviewerAdded,
NotesMigration migration) {
@@ -132,7 +130,6 @@
this.user = user;
this.identifiedUserFactory = identifiedUserFactory;
this.cfg = cfg;
- this.accountCache = accountCache;
this.json = json;
this.reviewerAdded = reviewerAdded;
this.migration = migration;
@@ -355,11 +352,15 @@
}
emailReviewers(rsrc.getChange(), addedReviewers, addedCCs);
if (!addedReviewers.isEmpty()) {
- for (PatchSetApproval psa : addedReviewers) {
- Account account = accountCache.get(psa.getAccountId()).getAccount();
- reviewerAdded.fire(rsrc.getChange(), patchSet, account,
+ List<Account.Id> reviewers = Lists.transform(addedReviewers,
+ new Function<PatchSetApproval, Account.Id>() {
+ @Override
+ public Account.Id apply(PatchSetApproval psa) {
+ return psa.getAccountId();
+ }
+ });
+ reviewerAdded.fire(rsrc.getChange(), patchSet, reviewers,
ctx.getAccount(), ctx.getWhen());
- }
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 2b621c2..1dc910c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -20,7 +20,6 @@
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.gerrit.common.data.GitwebType;
import com.google.gerrit.extensions.config.CloneCommand;
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
@@ -385,11 +384,6 @@
public String reportBugText;
}
- public static class GitwebInfo {
- public String url;
- public GitwebType type;
- }
-
public static class PluginConfigInfo {
public Boolean hasAvatars;
public List<String> jsResourcePaths;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 1368bf3..8d093f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -321,7 +321,7 @@
}
@Override
- public void onReviewerAdded(ReviewerAddedListener.Event ev) {
+ public void onReviewersAdded(ReviewerAddedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
@@ -330,9 +330,10 @@
event.change = changeAttributeSupplier(change);
event.patchSet = patchSetAttributeSupplier(change,
psUtil.current(db.get(), notes));
- event.reviewer = accountAttributeSupplier(ev.getReviewer());
-
- dispatcher.get().postEvent(change, event);
+ for (AccountInfo reviewer : ev.getReviewers()) {
+ event.reviewer = accountAttributeSupplier(reviewer);
+ dispatcher.get().postEvent(change, event);
+ }
} catch (OrmException e) {
log.error("Failed to dispatch event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
index 3ae8135..35b14dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.extensions.events;
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -33,6 +35,7 @@
import java.io.IOException;
import java.sql.Timestamp;
+import java.util.List;
public class ReviewerAdded {
private static final Logger log =
@@ -49,29 +52,38 @@
}
public void fire(ChangeInfo change, RevisionInfo revision,
- AccountInfo reviewer, AccountInfo adder, Timestamp when) {
+ List<AccountInfo> reviewers, AccountInfo adder, Timestamp when) {
if (!listeners.iterator().hasNext()) {
return;
}
- Event event = new Event(change, revision, reviewer, adder, when);
+ Event event = new Event(change, revision, reviewers, adder, when);
for (ReviewerAddedListener l : listeners) {
try {
- l.onReviewerAdded(event);
+ l.onReviewersAdded(event);
} catch (Exception e) {
log.warn("Error in event listener, e");
}
}
}
- public void fire(Change change, PatchSet patchSet, Account account,
+ public void fire(Change change, PatchSet patchSet, List<Account.Id> reviewers,
Account adder, Timestamp when) {
- if (!listeners.iterator().hasNext()) {
+ if (!listeners.iterator().hasNext() || reviewers.isEmpty()) {
return;
}
+
+ List<AccountInfo> transformed = Lists.transform(reviewers,
+ new Function<Account.Id, AccountInfo>() {
+ @Override
+ public AccountInfo apply(Account.Id account) {
+ return util.accountInfo(account);
+ }
+ });
+
try {
fire(util.changeInfo(change),
util.revisionInfo(change.getProject(), patchSet),
- util.accountInfo(account),
+ transformed,
util.accountInfo(adder),
when);
} catch (PatchListNotAvailableException | GpgException | IOException
@@ -82,17 +94,17 @@
private static class Event extends AbstractRevisionEvent
implements ReviewerAddedListener.Event {
- private final AccountInfo reviewer;
+ private final List<AccountInfo> reviewers;
- Event(ChangeInfo change, RevisionInfo revision, AccountInfo reviewer,
+ Event(ChangeInfo change, RevisionInfo revision, List<AccountInfo> reviewers,
AccountInfo adder, Timestamp when) {
super(change, revision, adder, when, NotifyHandling.ALL);
- this.reviewer = reviewer;
+ this.reviewers = reviewers;
}
@Override
- public AccountInfo getReviewer() {
- return reviewer;
+ public List<AccountInfo> getReviewers() {
+ return reviewers;
}
}
}
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 fe568c8..a70fa7a 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
@@ -255,13 +255,15 @@
ChangeData cd = changeDataFactory.create(ctx.getDb(), ctx.getControl());
MailRecipients oldRecipients =
getRecipientsFromReviewers(cd.reviewers());
- approvalCopier.copy(ctx.getDb(), ctx.getControl(), newPatchSet);
+ Iterable<PatchSetApproval> newApprovals =
+ approvalsUtil.addApprovals(ctx.getDb(), update,
+ projectControl.getLabelTypes(), newPatchSet, ctx.getControl(),
+ approvals);
+ approvalCopier.copy(ctx.getDb(), ctx.getControl(), newPatchSet,
+ newApprovals);
approvalsUtil.addReviewers(ctx.getDb(), update,
projectControl.getLabelTypes(), change, newPatchSet, info,
recipients.getReviewers(), oldRecipients.getAll());
- approvalsUtil.addApprovals(ctx.getDb(), update,
- projectControl.getLabelTypes(), newPatchSet, ctx.getControl(),
- approvals);
recipients.add(oldRecipients);
String approvalMessage = ApprovalsUtil.renderMessageWithApprovals(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
index 95b3ff4..eedfe70 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
@@ -64,12 +64,6 @@
if (failAfterRefUpdates) {
throw new ResourceConflictException("Failing after ref updates");
}
- for (SubmitStrategy strategy : strategies) {
- SubmitStrategy.Arguments args = strategy.args;
- if (args.mergeTip.getCurrentTip().equals(args.mergeTip.getInitialTip())) {
- continue;
- }
- }
}
private void findUnmergedChanges(List<Change.Id> alreadyMerged)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index 51f7ad1..0bc65bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -32,6 +32,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.regex.Pattern;
/** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */
@Singleton
@@ -53,13 +54,15 @@
generator =
new PatternGen(srvAddr, accountCache, anonymousCowardName, name,
srvAddr.email);
-
} else if ("USER".equalsIgnoreCase(from)) {
- generator = new UserGen(accountCache, srvAddr);
-
+ String[] domains = cfg.getStringList("sendemail", null, "allowedDomain");
+ Pattern domainPattern = MailUtil.glob(domains);
+ ParameterizedString namePattern =
+ new ParameterizedString("${user} (Code Review)");
+ generator = new UserGen(accountCache, domainPattern, anonymousCowardName,
+ namePattern, srvAddr);
} else if ("SERVER".equalsIgnoreCase(from)) {
generator = new ServerGen(srvAddr);
-
} else {
final Address a = Address.parse(from);
final ParameterizedString name = a.name != null ? new ParameterizedString(a.name) : null;
@@ -84,11 +87,31 @@
static final class UserGen implements FromAddressGenerator {
private final AccountCache accountCache;
- private final Address srvAddr;
+ private final Pattern domainPattern;
+ private final String anonymousCowardName;
+ private final ParameterizedString nameRewriteTmpl;
+ private final Address serverAddress;
- UserGen(AccountCache accountCache, Address srvAddr) {
+ /**
+ * From address generator for USER mode
+ *
+ * @param accountCache get user account from id
+ * @param domainPattern allowed user domain pattern that Gerrit can send as
+ * the user
+ * @param anonymousCowardName name used when user's full name is missing
+ * @param nameRewriteTmpl name template used for rewriting the sender's name
+ * when Gerrit can not send as the user
+ * @param serverAddress serverAddress.name is used when fromId is null and
+ * serverAddress.email is used when Gerrit can not send as the user
+ */
+ UserGen(AccountCache accountCache, Pattern domainPattern,
+ String anonymousCowardName, ParameterizedString nameRewriteTmpl,
+ Address serverAddress) {
this.accountCache = accountCache;
- this.srvAddr = srvAddr;
+ this.domainPattern = domainPattern;
+ this.anonymousCowardName = anonymousCowardName;
+ this.nameRewriteTmpl = nameRewriteTmpl;
+ this.serverAddress = serverAddress;
}
@Override
@@ -98,14 +121,44 @@
@Override
public Address from(final Account.Id fromId) {
+ String senderName;
if (fromId != null) {
Account a = accountCache.get(fromId).getAccount();
+ String fullName = a.getFullName();
String userEmail = a.getPreferredEmail();
- return new Address(
- a.getFullName(),
- userEmail != null ? userEmail : srvAddr.getEmail());
+ if (canRelay(userEmail)) {
+ return new Address(fullName, userEmail);
+ }
+
+ if (fullName == null || "".equals(fullName.trim())) {
+ fullName = anonymousCowardName;
+ }
+ senderName = nameRewriteTmpl.replace("user", fullName).toString();
+ } else {
+ senderName = serverAddress.name;
}
- return srvAddr;
+
+ String senderEmail;
+ ParameterizedString senderEmailPattern =
+ new ParameterizedString(serverAddress.email);
+ if (senderEmailPattern.getParameterNames().isEmpty()) {
+ senderEmail = senderEmailPattern.getRawPattern();
+ } else {
+ senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName))
+ .toString();
+ }
+ return new Address(senderName, senderEmail);
+ }
+
+ /** check if Gerrit is allowed to send from {@code userEmail}. */
+ private boolean canRelay(String userEmail) {
+ if (userEmail != null) {
+ int index = userEmail.indexOf('@');
+ if (index > 0 && index < userEmail.length() - 1) {
+ return domainPattern.matcher(userEmail.substring(index + 1)).matches();
+ }
+ }
+ return false;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
index 048a4a4..8a132cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -32,6 +32,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.regex.Pattern;
public class MailUtil {
public static MailRecipients getRecipientsFromFooters(
@@ -124,4 +125,19 @@
return Collections.unmodifiableSet(all);
}
}
+
+ /** allow wildcard matching for {@code domains} */
+ public static Pattern glob(String[] domains) {
+ // if domains is not set, match anything
+ if (domains == null || domains.length == 0) {
+ return Pattern.compile(".*");
+ }
+
+ StringBuilder sb = new StringBuilder("");
+ for (String domain : domains) {
+ String quoted = "\\Q" + domain.replace("\\E", "\\E\\\\E\\Q") + "\\E|";
+ sb.append(quoted.replace("*", "\\E.*\\Q"));
+ }
+ return Pattern.compile(sb.substring(0, sb.length() - 1));
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
index 47942be..82ea155 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
@@ -14,16 +14,39 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
public class FileResource implements RestResource {
public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
new TypeLiteral<RestView<FileResource>>() {};
+ public static FileResource create(GitRepositoryManager repoManager,
+ ProjectControl project, ObjectId rev, String path)
+ throws ResourceNotFoundException, IOException {
+ try (Repository repo =
+ repoManager.openRepository(project.getProject().getNameKey());
+ RevWalk rw = new RevWalk(repo)) {
+ RevTree tree = rw.parseTree(rev);
+ if (TreeWalk.forPath(repo, path, tree) != null) {
+ return new FileResource(project, rev, path);
+ }
+ }
+ throw new ResourceNotFoundException(IdString.fromDecoded(path));
+ }
+
private final ProjectControl project;
private final ObjectId rev;
private final String path;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
index d0460d5..dcb8747 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
@@ -19,19 +19,25 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.ObjectId;
+import java.io.IOException;
+
@Singleton
public class FilesCollection implements
ChildCollection<BranchResource, FileResource> {
private final DynamicMap<RestView<FileResource>> views;
+ private final GitRepositoryManager repoManager;
@Inject
- FilesCollection(DynamicMap<RestView<FileResource>> views) {
+ FilesCollection(DynamicMap<RestView<FileResource>> views,
+ GitRepositoryManager repoManager) {
this.views = views;
+ this.repoManager = repoManager;
}
@Override
@@ -40,11 +46,10 @@
}
@Override
- public FileResource parse(BranchResource parent, IdString id) {
- return new FileResource(
- parent.getControl(),
- ObjectId.fromString(parent.getRevision()),
- id.get());
+ public FileResource parse(BranchResource parent, IdString id)
+ throws ResourceNotFoundException, IOException {
+ return FileResource.create(repoManager, parent.getControl(),
+ ObjectId.fromString(parent.getRevision()), id.get());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
index 8e0aab8..736fe5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -19,17 +19,23 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.io.IOException;
+
@Singleton
public class FilesInCommitCollection implements
ChildCollection<CommitResource, FileResource> {
private final DynamicMap<RestView<FileResource>> views;
+ private final GitRepositoryManager repoManager;
@Inject
- FilesInCommitCollection(DynamicMap<RestView<FileResource>> views) {
+ FilesInCommitCollection(DynamicMap<RestView<FileResource>> views,
+ GitRepositoryManager repoManager) {
this.views = views;
+ this.repoManager = repoManager;
}
@Override
@@ -39,8 +45,9 @@
@Override
public FileResource parse(CommitResource parent, IdString id)
- throws ResourceNotFoundException {
- return new FileResource(parent.getProject(), parent.getCommit(), id.get());
+ throws ResourceNotFoundException, IOException {
+ return FileResource.create(repoManager, parent.getProject(),
+ parent.getCommit(), id.get());
}
@Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index 11f1d54..87dd3d9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -35,8 +35,10 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Set;
public class FromAddressGeneratorProviderTest {
@@ -60,6 +62,10 @@
config.setString("sendemail", null, "from", newFrom);
}
+ private void setDomains(List<String> domains) {
+ config.setStringList("sendemail", null, "allowedDomain", domains);
+ }
+
@Test
public void testDefaultIsMIXED() {
assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class);
@@ -118,7 +124,7 @@
replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
- assertThat(r.name).isEqualTo(name);
+ assertThat(r.name).isEqualTo(name + " (Code Review)");
assertThat(r.email).isEqualTo(ident.getEmailAddress());
verify(accountCache);
}
@@ -135,6 +141,88 @@
}
@Test
+ public void testUSERAllowDomain() {
+ setFrom("USER");
+ setDomains(Arrays.asList("*.example.com"));
+ final String name = "A U. Thor";
+ final String email = "a.u.thor@test.example.com";
+ final Account.Id user = user(name, email);
+
+ replay(accountCache);
+ final Address r = create().from(user);
+ assertThat(r).isNotNull();
+ assertThat(r.name).isEqualTo(name);
+ assertThat(r.email).isEqualTo(email);
+ verify(accountCache);
+ }
+
+ @Test
+ public void testUSERNoAllowDomain() {
+ setFrom("USER");
+ setDomains(Arrays.asList("example.com"));
+ final String name = "A U. Thor";
+ final String email = "a.u.thor@test.com";
+ final Account.Id user = user(name, email);
+
+ replay(accountCache);
+ final Address r = create().from(user);
+ assertThat(r).isNotNull();
+ assertThat(r.name).isEqualTo(name + " (Code Review)");
+ assertThat(r.email).isEqualTo(ident.getEmailAddress());
+ verify(accountCache);
+ }
+
+ @Test
+ public void testUSERAllowDomainTwice() {
+ setFrom("USER");
+ setDomains(Arrays.asList("example.com"));
+ setDomains(Arrays.asList("test.com"));
+ final String name = "A U. Thor";
+ final String email = "a.u.thor@test.com";
+ final Account.Id user = user(name, email);
+
+ replay(accountCache);
+ final Address r = create().from(user);
+ assertThat(r).isNotNull();
+ assertThat(r.name).isEqualTo(name);
+ assertThat(r.email).isEqualTo(email);
+ verify(accountCache);
+ }
+
+ @Test
+ public void testUSERAllowDomainTwiceReverse() {
+ setFrom("USER");
+ setDomains(Arrays.asList("test.com"));
+ setDomains(Arrays.asList("example.com"));
+ final String name = "A U. Thor";
+ final String email = "a.u.thor@test.com";
+ final Account.Id user = user(name, email);
+
+ replay(accountCache);
+ final Address r = create().from(user);
+ assertThat(r).isNotNull();
+ assertThat(r.name).isEqualTo(name + " (Code Review)");
+ assertThat(r.email).isEqualTo(ident.getEmailAddress());
+ verify(accountCache);
+ }
+
+ @Test
+ public void testUSERAllowTwoDomains() {
+ setFrom("USER");
+ setDomains(Arrays.asList("example.com", "test.com"));
+ final String name = "A U. Thor";
+ final String email = "a.u.thor@test.com";
+ final Account.Id user = user(name, email);
+
+ replay(accountCache);
+ final Address r = create().from(user);
+ assertThat(r).isNotNull();
+ assertThat(r.name).isEqualTo(name);
+ assertThat(r.email).isEqualTo(email);
+ verify(accountCache);
+ }
+
+ @Test
public void testSelectSERVER() {
setFrom("SERVER");
assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.ServerGen.class);
diff --git a/lib/BUCK b/lib/BUCK
index 20ac74c..2496df7 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -59,8 +59,8 @@
maven_jar(
name = 'gson',
- id = 'com.google.code.gson:gson:2.6.2',
- sha1 = 'f1bc476cc167b18e66c297df599b2377131a8947',
+ id = 'com.google.code.gson:gson:2.7',
+ sha1 = '751f548c85fa49f330cecbb1875893f971b33c4e',
license = 'Apache2.0',
)
diff --git a/lib/JGIT_VERSION b/lib/JGIT_VERSION
index 58b677a..6457323 100644
--- a/lib/JGIT_VERSION
+++ b/lib/JGIT_VERSION
@@ -1,4 +1,4 @@
include_defs('//lib/maven.defs')
REPO = GERRIT # Leave here even if set to MAVEN_CENTRAL.
-VERS = '4.4.1.201607150455-r.109-g0b4751e'
+VERS = '4.4.1.201607150455-r.118-g1096652'
diff --git a/lib/guice/BUCK b/lib/guice/BUCK
index 3893b80..867b521 100644
--- a/lib/guice/BUCK
+++ b/lib/guice/BUCK
@@ -1,6 +1,6 @@
include_defs('//lib/maven.defs')
-VERSION = '4.0'
+VERSION = '4.1.0'
EXCLUDE = [
'META-INF/DEPENDENCIES',
'META-INF/LICENSE',
@@ -19,7 +19,7 @@
maven_jar(
name = 'guice_library',
id = 'com.google.inject:guice:' + VERSION,
- sha1 = '0f990a43d3725781b6db7cd0acf0a8b62dfd1649',
+ sha1 = 'eeb69005da379a10071aa4948c48d89250febb07',
license = 'Apache2.0',
deps = [':aopalliance'],
exclude_java_sources = True,
@@ -33,7 +33,7 @@
maven_jar(
name = 'guice-assistedinject',
id = 'com.google.inject.extensions:guice-assistedinject:' + VERSION,
- sha1 = '8fa6431da1a2187817e3e52e967535899e2e46ca',
+ sha1 = 'af799dd7e23e6fe8c988da12314582072b07edcb',
license = 'Apache2.0',
deps = [':guice'],
exclude = EXCLUDE,
@@ -42,7 +42,7 @@
maven_jar(
name = 'guice-servlet',
id = 'com.google.inject.extensions:guice-servlet:' + VERSION,
- sha1 = '4503da866f4c402b5090579b40c1c4aaefabb164',
+ sha1 = '90ac2db772d9b85e2b05417b74f7464bcc061dcb',
license = 'Apache2.0',
deps = [':guice'],
exclude = EXCLUDE,
diff --git a/lib/jgit/org.eclipse.jgit.archive/BUCK b/lib/jgit/org.eclipse.jgit.archive/BUCK
index fab0634..384a5e0 100644
--- a/lib/jgit/org.eclipse.jgit.archive/BUCK
+++ b/lib/jgit/org.eclipse.jgit.archive/BUCK
@@ -4,7 +4,7 @@
maven_jar(
name = 'jgit-archive',
id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
- sha1 = '57ebfba05a6023e3d9ed48083ff0dcbac09e8047',
+ sha1 = '3f45cd199e40a7c68ee07a1743c06d1c3d07308a',
license = 'jgit',
repository = REPO,
deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.http.server/BUCK b/lib/jgit/org.eclipse.jgit.http.server/BUCK
index 3371b87..2ade9ff 100644
--- a/lib/jgit/org.eclipse.jgit.http.server/BUCK
+++ b/lib/jgit/org.eclipse.jgit.http.server/BUCK
@@ -4,7 +4,7 @@
maven_jar(
name = 'jgit-servlet',
id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
- sha1 = '44b864259407c0fe2c7ccdeabcef79db3de5a9ea',
+ sha1 = 'fa67bf925001cfc663bf98772f37d5c5c1abd756',
license = 'jgit',
repository = REPO,
deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUCK b/lib/jgit/org.eclipse.jgit.junit/BUCK
index 68062e7..a31ee6f 100644
--- a/lib/jgit/org.eclipse.jgit.junit/BUCK
+++ b/lib/jgit/org.eclipse.jgit.junit/BUCK
@@ -4,7 +4,7 @@
maven_jar(
name = 'junit',
id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
- sha1 = '9637d7ec4775a4e0e2467c8e951a735ad06a8c57',
+ sha1 = 'dc7edb9c3060655c7fb93ab9b9349e815bab266f',
license = 'DO_NOT_DISTRIBUTE',
repository = REPO,
unsign = True,
diff --git a/lib/jgit/org.eclipse.jgit/BUCK b/lib/jgit/org.eclipse.jgit/BUCK
index 17e3bbe..7c06726 100644
--- a/lib/jgit/org.eclipse.jgit/BUCK
+++ b/lib/jgit/org.eclipse.jgit/BUCK
@@ -4,8 +4,8 @@
maven_jar(
name = 'jgit',
id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
- bin_sha1 = 'de7fb6fa397b2388bfafe17b212d4fe4ad5df72a',
- src_sha1 = '30665754ae7703018251b2880e0cfba6edcbe189',
+ bin_sha1 = 'cd142b9030910babd119702f1c4eeae13ee90018',
+ src_sha1 = '3e65e476bfb4a529e18752ffcd27b566e7ee7241',
license = 'jgit',
repository = REPO,
unsign = True,
diff --git a/plugins/hooks b/plugins/hooks
index 3acc14d..ac87f18 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 3acc14d10d26678eae6489038fe0d4dad644a9b4
+Subproject commit ac87f187034838e6cf8d5824b2e541cc9b3a0aa4
diff --git a/plugins/replication b/plugins/replication
index c5123d6..9a83958 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit c5123d6a5604cc740d6f42485235c0d3ec141c4e
+Subproject commit 9a8395846516d65158b1b2bb59deb9aa6537e820
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index b741784..1fe09e5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -32,19 +32,14 @@
<template>
<style>
:host {
- display: block;
+ display: inline-block;
+ font-family: var(--font-family);
}
section {
- margin-top: 1em;
- }
- .groupLabel {
- color: #666;
- margin-bottom: .15em;
- text-align: center;
+ display: inline-block;
}
gr-button {
- display: block;
- margin-bottom: .5em;
+ margin-left: .5em;
}
gr-button:before {
content: attr(data-label);
@@ -53,6 +48,15 @@
content: attr(data-loading-label);
}
@media screen and (max-width: 50em) {
+ :host,
+ section,
+ gr-button {
+ display: block;
+ }
+ gr-button {
+ margin-bottom: .5em;
+ margin-left: 0;
+ }
.confirmDialog {
width: 90vw;
}
@@ -60,7 +64,6 @@
</style>
<div>
<section hidden$="[[!_actionCount(actions.*, _additionalActions.*)]]">
- <div class="groupLabel">Change</div>
<template is="dom-repeat" items="[[_changeActionValues]]" as="action">
<gr-button title$="[[action.title]]"
primary$="[[action.__primary]]"
@@ -73,7 +76,6 @@
</template>
</section>
<section hidden$="[[!_actionCount(_revisionActions.*, _additionalActions.*)]]">
- <div class="groupLabel">Revision</div>
<template is="dom-repeat" items="[[_revisionActionValues]]" as="action">
<gr-button title$="[[action.title]]"
primary$="[[action.__primary]]"
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 e3f7fd2..1807ac9 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
@@ -45,18 +45,16 @@
color: #666;
padding: 1em var(--default-horizontal-margin);
}
- .headerContainer {
- height: 4.1em;
- margin-bottom: .5em;
- }
.header {
align-items: center;
background-color: var(--view-background-color);
- border-bottom: 1px solid #ddd;
display: flex;
- padding: 1em var(--default-horizontal-margin);
+ padding: .65em var(--default-horizontal-margin);
z-index: 99; /* Less than gr-overlay's backdrop */
}
+ .header .download {
+ margin-right: 1em;
+ }
.header.pinned {
border-bottom-color: transparent;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
@@ -69,24 +67,11 @@
flex: 1;
font-size: 1.2em;
font-weight: bold;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
}
gr-change-star {
margin-right: .25em;
vertical-align: -.425em;
}
- .download,
- .patchSelectLabel {
- margin-left: 1em;
- }
- .header select {
- margin-left: .5em;
- }
- .header .reply {
- margin-left: var(--default-horizontal-margin);
- }
gr-reply-dialog {
width: 50em;
}
@@ -111,12 +96,8 @@
padding-right: 1em;
}
.changeMetadata {
- border-right: 1px solid #ddd;
font-size: .9em;
}
- gr-change-actions {
- margin-top: 1em;
- }
.commitMessage {
font-family: var(--monospace-font-family);
flex: 0 0 72ch;
@@ -124,15 +105,28 @@
margin-bottom: 1em;
overflow-x: hidden;
}
- .commitMessage h4 {
- font-family: var(--font-family);
- font-weight: bold;
- margin-bottom: .25em;
- }
.commitMessage gr-linked-text {
--linked-text-white-space: pre;
overflow: auto;
}
+ .editCommitMessage {
+ margin-top: 1em;
+ }
+ .commitActions {
+ border-bottom: 1px solid #ddd;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: .5em;
+ padding-bottom: .5em;
+ }
+ .reply {
+ margin-right: .5em;
+ }
+ .mainChangeInfo {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ }
.commitAndRelated {
align-content: flex-start;
display: flex;
@@ -149,9 +143,6 @@
padding: 0 var(--default-horizontal-margin);
}
@media screen and (max-width: 50em) {
- .headerContainer {
- height: 5.15em;
- }
.header {
align-items: flex-start;
flex-direction: column;
@@ -163,12 +154,6 @@
.header-title {
font-size: 1.1em;
}
- .header-actions {
- align-items: center;
- display: flex;
- justify-content: space-between;
- margin-top: .5em;
- }
gr-reply-dialog {
min-width: initial;
width: 90vw;
@@ -176,17 +161,10 @@
.download {
display: none;
}
- .patchSelectLabel {
- margin-left: 0;
- margin-right: .5em;
- }
- .header select {
- margin-left: 0;
- margin-right: .5em;
- }
- .header .reply {
- margin-left: 0;
- margin-right: .5em;
+ .reply {
+ display: block;
+ margin-right: 0;
+ margin-bottom: .5em;
}
.changeInfo-column:not(:last-of-type) {
margin-right: 0;
@@ -207,6 +185,9 @@
margin-top: .25em;
max-width: none;
}
+ .commitActions {
+ flex-direction: column;
+ }
.commitMessage {
flex: initial;
margin-right: 0;
@@ -215,35 +196,28 @@
</style>
<div class="container loading" hidden$="{{!_loading}}">Loading...</div>
<div class="container" hidden$="{{_loading}}">
- <div class="headerContainer">
- <div class="header">
- <span class="header-title">
- <gr-change-star change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star>
- <a href$="[[_computeChangePermalink(_change._number)]]">[[_change._number]]</a><span>:</span>
- <span>[[_change.subject]]</span>
- <span class="changeStatus">[[_computeChangeStatus(_change, _patchRange.patchNum)]]</span>
+ <div class="header">
+ <span class="header-title">
+ <gr-change-star change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star>
+ <a href$="[[_computeChangePermalink(_change._number)]]">[[_change._number]]</a><span>:</span>
+ <span>[[_change.subject]]</span>
+ <span class="changeStatus">[[_computeChangeStatus(_change, _patchRange.patchNum)]]</span>
+ </span>
+ <span class="header-actions">
+ <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
+ <span>
+ <label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
+ <select id="patchSetSelect" on-change="_handlePatchChange">
+ <template is="dom-repeat" items="{{_allPatchSets}}" as="patchNumber">
+ <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchRange.patchNum)]]">
+ <span>[[patchNumber]]</span>
+ /
+ <span>[[_computeLatestPatchNum(_allPatchSets)]]</span>
+ </option>
+ </template>
+ </select>
</span>
- <span class="header-actions">
- <gr-button hidden
- class="reply"
- primary$="[[_computeReplyButtonHighlighted(_diffDrafts.*)]]"
- hidden$="[[!_loggedIn]]"
- on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
- <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
- <span>
- <label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
- <select id="patchSetSelect" on-change="_handlePatchChange">
- <template is="dom-repeat" items="{{_allPatchSets}}" as="patchNumber">
- <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchRange.patchNum)]]">
- <span>[[patchNumber]]</span>
- /
- <span>[[_computeLatestPatchNum(_allPatchSets)]]</span>
- </option>
- </template>
- </select>
- </span>
- </span>
- </div>
+ </span>
</div>
<section class="changeInfo">
<div class="changeInfo-column changeMetadata">
@@ -254,34 +228,40 @@
mutable="[[_loggedIn]]"
on-show-reply-dialog="_handleShowReplyDialog">
</gr-change-metadata>
- <gr-change-actions id="actions"
- change="[[_change]]"
- actions="[[_change.actions]]"
- change-num="[[_changeNum]]"
- patch-num="[[_patchRange.patchNum]]"
- commit-info="[[_commitInfo]]"
- on-reload-change="_handleReloadChange"></gr-change-actions>
</div>
- <div class="changeInfo-column commitAndRelated">
- <div class="commitMessage">
- <h4>
- Commit message
+ <div class="changeInfo-column mainChangeInfo">
+ <div class="commitActions" hidden$="[[!_loggedIn]]"">
+ <gr-button
+ class="reply"
+ secondary
+ on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
+ <gr-change-actions id="actions"
+ change="[[_change]]"
+ actions="[[_change.actions]]"
+ change-num="[[_changeNum]]"
+ patch-num="[[_patchRange.patchNum]]"
+ commit-info="[[_commitInfo]]"
+ on-reload-change="_handleReloadChange"></gr-change-actions>
+ </div>
+ <div class="commitAndRelated">
+ <div class="commitMessage">
+ <gr-editable-content id="commitMessageEditor"
+ editing="[[_editingCommitMessage]]"
+ content="{{_commitInfo.message}}">
+ <gr-linked-text pre
+ content="[[_commitInfo.message]]"
+ config="[[_projectConfig.commentlinks]]"></gr-linked-text>
+ </gr-editable-content>
<gr-button link
+ class="editCommitMessage"
on-tap="_handleEditCommitMessage"
hidden$="[[_hideEditCommitMessage]]">Edit</gr-button>
- </h4>
- <gr-editable-content id="commitMessageEditor"
- editing="[[_editingCommitMessage]]"
- content="{{_commitInfo.message}}">
- <gr-linked-text pre
- content="[[_commitInfo.message]]"
- config="[[_projectConfig.commentlinks]]"></gr-linked-text>
- </gr-editable-content>
- </div>
- <div class="relatedChanges">
- <gr-related-changes-list id="relatedChanges"
- change="[[_change]]"
- patch-num="[[_patchRange.patchNum]]"></gr-related-changes-list>
+ </div>
+ <div class="relatedChanges">
+ <gr-related-changes-list id="relatedChanges"
+ change="[[_change]]"
+ patch-num="[[_patchRange.patchNum]]"></gr-related-changes-list>
+ </div>
</div>
</div>
</section>
@@ -312,6 +292,7 @@
on-close="_handleDownloadDialogClose"></gr-download-dialog>
</gr-overlay>
<gr-overlay id="replyOverlay"
+ no-cancel-on-outside-click
on-iron-overlay-opened="_handleReplyOverlayOpen"
with-backdrop>
<gr-reply-dialog id="replyDialog"
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 14ac4d1..c7cb978 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
@@ -78,8 +78,6 @@
value: false,
},
_loading: Boolean,
- _headerContainerEl: Object,
- _headerEl: Object,
_projectConfig: Object,
_replyButtonLabel: {
type: String,
@@ -98,10 +96,6 @@
'_paramsAndChangeChanged(params, _change)',
],
- ready: function() {
- this._headerEl = this.$$('.header');
- },
-
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn;
@@ -114,34 +108,6 @@
this._handleCommitMessageSave.bind(this));
this.addEventListener('editable-content-cancel',
this._handleCommitMessageCancel.bind(this));
- this.listen(window, 'scroll', '_handleBodyScroll');
- },
-
- detached: function() {
- this.unlisten(window, 'scroll', '_handleBodyScroll');
- },
-
- _handleBodyScroll: function(e) {
- var containerEl = this._headerContainerEl ||
- this.$$('.headerContainer');
-
- // Calculate where the header is relative to the window.
- var top = containerEl.offsetTop;
- for (var offsetParent = containerEl.offsetParent;
- offsetParent;
- offsetParent = offsetParent.offsetParent) {
- top += offsetParent.offsetTop;
- }
- // The element may not be displayed yet, in which case do nothing.
- if (top == 0) { return; }
-
- this._headerEl.classList.toggle('pinned', window.scrollY >= top);
- },
-
- _resetHeaderEl: function() {
- var el = this._headerEl || this.$$('.header');
- this._headerEl = el;
- el.classList.remove('pinned');
},
_handleEditCommitMessage: function(e) {
@@ -267,19 +233,7 @@
},
_handlePatchChange: function(e) {
- var patchNum = e.target.value;
- var currentPatchNum;
- if (this._change.current_revision) {
- currentPatchNum =
- this._change.revisions[this._change.current_revision]._number;
- } else {
- currentPatchNum = this._computeLatestPatchNum(this._allPatchSets);
- }
- if (patchNum == currentPatchNum) {
- page.show(this.changePath(this._changeNum));
- return;
- }
- page.show(this.changePath(this._changeNum) + '/' + patchNum);
+ this._changePatchNum(parseInt(e.target.value, 10));
},
_handleReplyTap: function(e) {
@@ -339,9 +293,6 @@
};
this._reload().then(function() {
- this.$.messageList.topMargin = this._headerEl.offsetHeight;
- this.$.fileList.topMargin = this._headerEl.offsetHeight;
-
// Allow the message list to render before scrolling.
this.async(function() {
this._maybeScrollToMessage();
@@ -405,6 +356,25 @@
this.fire('title-change', {title: title});
},
+ /**
+ * Change active patch to the provided patch num.
+ * @param {int} patchNum the patchn number to be viewed.
+ */
+ _changePatchNum: function(patchNum) {
+ var currentPatchNum;
+ if (this._change.current_revision) {
+ currentPatchNum =
+ this._change.revisions[this._change.current_revision]._number;
+ } else {
+ currentPatchNum = this._computeLatestPatchNum(this._allPatchSets);
+ }
+ if (patchNum === currentPatchNum) {
+ page.show(this.changePath(this._changeNum));
+ return;
+ }
+ page.show(this.changePath(this._changeNum) + '/' + patchNum);
+ },
+
_computeChangePermalink: function(changeNum) {
return '/' + changeNum;
},
@@ -477,11 +447,6 @@
return result;
},
- _computeReplyButtonHighlighted: function(changeRecord) {
- var drafts = (changeRecord && changeRecord.base) || {};
- return Object.keys(drafts).length > 0;
- },
-
_computeReplyButtonLabel: function(changeRecord) {
var drafts = (changeRecord && changeRecord.base) || {};
var draftCount = Object.keys(drafts).reduce(function(count, file) {
@@ -495,9 +460,17 @@
return label;
},
+ _switchToMostRecentPatchNum: function() {
+ this._getChangeDetail().then(function() {
+ var patchNum = this._allPatchSets[this._allPatchSets.length - 1];
+ if (patchNum !== this._patchRange.patchNum) {
+ this._changePatchNum(patchNum);
+ }
+ }.bind(this));
+ },
+
_handleKey: function(e) {
if (this.shouldSupressKeyboardShortcut(e)) { return; }
-
switch (e.keyCode) {
case 65: // 'a'
if (this._loggedIn && !e.shiftKey) {
@@ -505,6 +478,12 @@
this._openReplyDialog();
}
break;
+ case 82: // 'r'
+ if (e.shiftKey) {
+ e.preventDefault();
+ this._switchToMostRecentPatchNum();
+ }
+ break;
case 85: // 'u'
e.preventDefault();
page.show('/');
@@ -619,8 +598,6 @@
]);
}.bind(this);
- this._resetHeaderEl();
-
if (this._patchRange.patchNum) {
return reloadPatchNumDependentResources().then(function() {
return detailCompletes;
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 c9a687b..2200a63 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
@@ -43,44 +43,89 @@
element = fixture('basic');
});
- test('keyboard shortcuts', function() {
- var showStub = sinon.stub(page, 'show');
+ suite('keyboard shortcuts', function() {
+ test('U should navigate to /', function() {
+ var showStub = sinon.stub(page, 'show');
+ MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U'
+ assert(showStub.lastCall.calledWithExactly('/'));
+ showStub.restore();
+ });
- MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U'
- assert(showStub.lastCall.calledWithExactly('/'),
- 'Should navigate to /');
- showStub.restore();
+ test('A should toggle overlay', function() {
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
+ var overlayEl = element.$.replyOverlay;
+ assert.isFalse(overlayEl.opened);
+ element._loggedIn = true;
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
- var overlayEl = element.$.replyOverlay;
- assert.isFalse(overlayEl.opened);
- element._loggedIn = true;
+ MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A'
+ assert.isFalse(overlayEl.opened);
- MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A'
- assert.isFalse(overlayEl.opened);
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
+ assert.isTrue(overlayEl.opened);
+ overlayEl.close();
+ assert.isFalse(overlayEl.opened);
+ });
- MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A'
- assert.isTrue(overlayEl.opened);
- overlayEl.close();
- assert.isFalse(overlayEl.opened);
+ test('shift + R should fetch and navigate to the latest patch set',
+ function(done) {
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 1,
+ };
+ element._change = {
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev1: {_number: 1},
+ },
+ current_revision: 'rev1',
+ status: 'NEW',
+ labels: {},
+ actions: {},
+ };
+
+ sinon.stub(element.$.restAPI, '_getChangeDetail', function() {
+ // Mock change obj.
+ return Promise.resolve({
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev1: {_number: 1},
+ rev13: {_number: 13},
+ },
+ current_revision: 'rev1',
+ status: 'NEW',
+ labels: {},
+ actions: {},
+ });
+ });
+
+ var showStub = sinon.stub(page, 'show', function(arg) {
+ assert.equal(arg, '/c/42/13');
+ showStub.restore();
+ element.$.restAPI._getChangeDetail.restore();
+ done();
+ });
+
+ // 'shift + R'
+ MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift');
+ });
});
- test('reply button is highlighted when there are drafts', function() {
+ test('reply button has updated count when there are drafts', function() {
var replyButton = element.$$('gr-button.reply');
assert.ok(replyButton);
- assert.isFalse(replyButton.hasAttribute('primary'));
+ assert.equal(replyButton.textContent, 'Reply');
element._diffDrafts = null;
- assert.isFalse(replyButton.hasAttribute('primary'));
+ assert.equal(replyButton.textContent, 'Reply');
element._diffDrafts = {};
- assert.isFalse(replyButton.hasAttribute('primary'));
+ assert.equal(replyButton.textContent, 'Reply');
element._diffDrafts = {
'file1.txt': [{}],
'file2.txt': [{}, {}],
};
- assert.isTrue(replyButton.hasAttribute('primary'));
assert.equal(replyButton.textContent, 'Reply (3)');
});
@@ -306,14 +351,15 @@
true, false, changeRecord, '2'));
});
- test('topic is coalesced to null', function() {
+ test('topic is coalesced to null', function(done) {
sinon.stub(element, '_changeChanged');
- sinon.stub(element.$.restAPI, 'getChangeDetail', function(num) {
+ sinon.stub(element.$.restAPI, 'getChangeDetail', function() {
return Promise.resolve({id: '123456789', labels: {}});
});
element._getChangeDetail().then(function() {
assert.isNull(element._change.topic);
+ done();
});
});
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 ef5ceed..201671d 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
@@ -181,9 +181,7 @@
</template>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
- <gr-diff-cursor
- id="cursor"
- fold-offset-top="[[topMargin]]"></gr-diff-cursor>
+ <gr-diff-cursor id="cursor"></gr-diff-cursor>
</template>
<script src="gr-file-list.js"></script>
</dom-module>
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 225d8b3..7bbeab1 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
@@ -27,7 +27,6 @@
drafts: Object,
revisions: Object,
projectConfig: Object,
- topMargin: Number,
selectedIndex: {
type: Number,
notify: true,
@@ -352,7 +351,7 @@
}
// Don't scroll if it's already in view.
- if (top > window.pageYOffset + this.topMargin &&
+ if (top > window.pageYOffset &&
top < window.pageYOffset + window.innerHeight - el.clientHeight) {
return;
}
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index e7a0573..875bf2b 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -29,7 +29,6 @@
},
comments: Object,
projectConfig: Object,
- topMargin: Number,
showReplyButtons: {
type: Boolean,
value: false,
@@ -52,7 +51,7 @@
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
- window.scrollTo(0, top - this.topMargin);
+ window.scrollTo(0, top);
this._highlightEl(el);
},
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 930c8cf..916726fd 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -45,34 +45,38 @@
.links {
margin-left: 1em;
}
- .links ul {
+ .links .menuContainer {
display: none;
}
.links > li {
cursor: default;
display: inline-block;
margin-left: 1em;
- padding: .4em 0;
+ padding: .5em 0;
position: relative;
}
- .links li:hover ul {
+ .links li:hover .menuContainer,
+ .links li:active .menuContainer {
background-color: #fff;
+ border-radius: 3px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .3);
display: block;
- left: -.75em;
+ left: -.5em;
+ padding: .5em 0;
position: absolute;
- top: 2em;
+ top: 2.45em;
z-index: 1000;
}
.links li ul li a:link,
.links li ul li a:visited {
color: #00e;
display: block;
- padding: .5em .75em;
+ padding: .3em 1em;
text-decoration: none;
white-space: nowrap;
}
- .links li ul li:hover a {
+ .links li ul li:hover a,
+ .links li ul li:active a {
background-color: var(--selection-background-color);
}
.linksTitle {
@@ -87,7 +91,8 @@
height: 0;
position: absolute;
right: 0;
- top: calc(50% - .1em);
+ top: calc(50% - .05em);
+ transition: border-top-color 200ms;
width: 0;
}
.links li:hover .downArrow {
@@ -137,11 +142,13 @@
<span class="linksTitle">
[[linkGroup.title]] <i class="downArrow"></i>
</span>
- <ul>
- <template is="dom-repeat" items="[[linkGroup.links]]" as="link">
- <li><a href$="[[link.url]]">[[link.name]]</a></li>
- </template>
- </ul>
+ <div class="menuContainer">
+ <ul>
+ <template is="dom-repeat" items="[[linkGroup.links]]" as="link">
+ <li><a href$="[[link.url]]">[[link.name]]</a></li>
+ </template>
+ </ul>
+ </div>
</li>
</template>
</ul>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
index fecb376..2b05d99 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -18,6 +18,8 @@
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
<dom-module id="gr-search-bar">
<template>
@@ -53,6 +55,7 @@
multi
borderless></gr-autocomplete>
<gr-button id="searchButton">Search</gr-button>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</form>
</template>
<script src="gr-search-bar.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 8e52f8f..e132f44 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -78,6 +78,10 @@
'tr',
];
+ var MAX_AUTOCOMPLETE_RESULTS = 10;
+
+ var TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g;
+
Polymer({
is: 'gr-search-bar',
@@ -124,34 +128,144 @@
page.show('/q/' + encodeURIComponent(encodeURIComponent(this._inputVal)));
},
- // TODO(kaspern): Flesh this out better.
- _makeSuggestion: function(str) {
- return {
- name: str,
- value: str,
- };
+ /**
+ * Fetch from the API the predicted accounts.
+ * @param {string} predicate - The first part of the search term, e.g.
+ * 'owner'
+ * @param {string} expression - The second part of the search term, e.g.
+ * 'kasp'
+ * @return {!Promise} This returns a promise that resolves to an array of
+ * strings.
+ */
+ _fetchAccounts: function(predicate, expression) {
+ if (expression.length === 0) { return Promise.resolve([]); }
+ return this.$.restAPI.getSuggestedAccounts(
+ expression,
+ MAX_AUTOCOMPLETE_RESULTS)
+ .then(function(accounts) {
+ if (!accounts) { return []; }
+ return accounts.map(function(acct) {
+ return predicate + ':"' + acct.name + ' <' + acct.email + '>"';
+ });
+ });
},
- // TODO(kaspern): Expand support for more complicated autocomplete features.
+ /**
+ * Fetch from the API the predicted groups.
+ * @param {string} predicate - The first part of the search term, e.g.
+ * 'ownerin'
+ * @param {string} expression - The second part of the search term, e.g.
+ * 'polyger'
+ * @return {!Promise} This returns a promise that resolves to an array of
+ * strings.
+ */
+ _fetchGroups: function(predicate, expression) {
+ if (expression.length === 0) { return Promise.resolve([]); }
+ return this.$.restAPI.getSuggestedGroups(
+ expression,
+ MAX_AUTOCOMPLETE_RESULTS)
+ .then(function(groups) {
+ if (!groups) { return []; }
+ var keys = Object.keys(groups);
+ return keys.map(function(key) { return predicate + ':' + key; });
+ });
+ },
+
+ /**
+ * Fetch from the API the predicted projects.
+ * @param {string} predicate - The first part of the search term, e.g.
+ * 'project'
+ * @param {string} expression - The second part of the search term, e.g.
+ * 'gerr'
+ * @return {!Promise} This returns a promise that resolves to an array of
+ * strings.
+ */
+ _fetchProjects: function(predicate, expression) {
+ return this.$.restAPI.getSuggestedProjects(
+ expression,
+ MAX_AUTOCOMPLETE_RESULTS)
+ .then(function(projects) {
+ if (!projects) { return []; }
+ var keys = Object.keys(projects);
+ return keys.map(function(key) { return predicate + ':' + key; });
+ });
+ },
+
+ /**
+ * Determine what array of possible suggestions should be provided
+ * to _getSearchSuggestions.
+ * @param {string} input - The full search term, in lowercase.
+ * @return {!Promise} This returns a promise that resolves to an array of
+ * strings.
+ */
+ _fetchSuggestions: function(input) {
+ // Split the input on colon to get a two part predicate/expression.
+ var splitInput = input.split(':');
+ var predicate = splitInput[0];
+ var expression = splitInput[1] || '';
+ // Switch on the predicate to determine what to autocomplete.
+ switch (predicate) {
+ case 'ownerin':
+ case 'reviewerin':
+ // Fetch groups.
+ return this._fetchGroups(predicate, expression);
+
+ case 'parentproject':
+ case 'project':
+ // Fetch projects.
+ return this._fetchProjects(predicate, expression);
+
+ case 'author':
+ case 'commentby':
+ case 'committer':
+ case 'from':
+ case 'owner':
+ case 'reviewedby':
+ case 'reviewer':
+ // Fetch accounts.
+ return this._fetchAccounts(predicate, expression);
+
+ default:
+ return Promise.resolve(SEARCH_OPERATORS
+ .filter(function(operator) {
+ return operator.indexOf(input) !== -1;
+ }));
+ }
+ },
+
+ /**
+ * Get the sorted, pruned list of suggestions for the current search query.
+ * @param {string} input - The complete search query.
+ * @return {!Promise} This returns a promise that resolves to an array of
+ * strings.
+ */
_getSearchSuggestions: function(input) {
- return Promise.resolve(SEARCH_OPERATORS).then(function(operators) {
- if (!operators) { return []; }
- var lowerCaseInput = input
- .substring(input.lastIndexOf(' ') + 1)
- .toLowerCase();
- return operators
- .filter(function(operator) {
- // Disallow autocomplete values that exactly match the whole str.
- var opContainsInput = operator.indexOf(lowerCaseInput) !== -1;
- var inputContainsOp = lowerCaseInput.indexOf(operator) !== -1;
- return opContainsInput && !inputContainsOp;
- })
- // Prioritize results that start with the input.
- .sort(function(operator) {
- return operator.indexOf(lowerCaseInput);
- })
- .map(this._makeSuggestion);
- }.bind(this));
+ // Allow spaces within quoted terms.
+ var tokens = input.match(TOKENIZE_REGEX);
+ var trimmedInput = tokens[tokens.length - 1].toLowerCase();
+
+ return this._fetchSuggestions(trimmedInput)
+ .then(function(operators) {
+ if (!operators) { return []; }
+ return operators
+ // Disallow autocomplete values that exactly match the str.
+ .filter(function(operator) {
+ return input.indexOf(operator.toLowerCase()) == -1;
+ })
+ // Prioritize results that start with the input.
+ .sort(function(operator) {
+ return operator.indexOf(trimmedInput);
+ })
+ // Return only the first {MAX_AUTOCOMPLETE_RESULTS} results.
+ .slice(0, MAX_AUTOCOMPLETE_RESULTS - 1)
+ // Map to an object to play nice with gr-autocomplete.
+ .map(function(operator) {
+ return {
+ name: operator,
+ value: operator,
+ };
+ });
+ });
},
_handleKey: function(e) {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index 0c16774..60e29b2 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -79,22 +79,82 @@
showStub.restore();
});
- test('_getSearchSuggestions returns proper set of suggestions',
- function(done) {
- element._getSearchSuggestions('is:o')
- .then(function(suggestions) {
- assert.equal(suggestions[0].name, 'is:open');
- assert.equal(suggestions[0].value, 'is:open');
- assert.equal(suggestions[1].name, 'is:owner');
- assert.equal(suggestions[1].value, 'is:owner');
- })
- .then(function() {
- element._getSearchSuggestions('asdasdasdasd')
- .then(function(suggestions) {
- assert.equal(suggestions.length, 0);
- done();
- });
+ suite('_getSearchSuggestions',
+ function() {
+ setup(function() {
+ sinon.stub(element.$.restAPI, 'getSuggestedAccounts', function() {
+ return Promise.resolve([
+ {
+ name: 'fred',
+ email: 'fred@goog.co',
+ },
+ ]);
+ });
+ sinon.stub(element.$.restAPI, 'getSuggestedGroups', function() {
+ return Promise.resolve({
+ Polygerrit: 0,
});
+ });
+ sinon.stub(element.$.restAPI, 'getSuggestedProjects', function() {
+ return Promise.resolve({
+ Polygerrit: 0,
+ });
+ });
+ });
+
+ teardown(function() {
+ element.$.restAPI.getSuggestedAccounts.restore();
+ element.$.restAPI.getSuggestedGroups.restore();
+ element.$.restAPI.getSuggestedProjects.restore();
+ });
+
+ test('Autocompletes accounts',
+ function(done) {
+ return element._getSearchSuggestions('owner:fr')
+ .then(function(suggestions) {
+ assert.equal(suggestions[0].value, 'owner:"fred <fred@goog.co>"');
+ done();
+ });
+ });
+
+ test('Autocompletes groups',
+ function(done) {
+ return element._getSearchSuggestions('ownerin:pol')
+ .then(function(suggestions) {
+ assert.equal(suggestions[0].value, 'ownerin:Polygerrit');
+ done();
+ });
+ });
+
+ test('Autocompletes projects',
+ function(done) {
+ return element._getSearchSuggestions('project:pol')
+ .then(function(suggestions) {
+ assert.equal(suggestions[0].value, 'project:Polygerrit');
+ done();
+ });
+ });
+
+ test('Autocompletes simple searches',
+ function(done) {
+ return element._getSearchSuggestions('is:o')
+ .then(function(suggestions) {
+ assert.equal(suggestions[0].name, 'is:open');
+ assert.equal(suggestions[0].value, 'is:open');
+ assert.equal(suggestions[1].name, 'is:owner');
+ assert.equal(suggestions[1].value, 'is:owner');
+ done();
+ });
+ });
+
+ test('Does not autocomplete with no match',
+ function(done) {
+ return element._getSearchSuggestions('asdasdasdasd')
+ .then(function(suggestions) {
+ assert.equal(suggestions.length, 0);
+ done();
+ });
+ });
});
});
</script>
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 5a41709..491eded 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
@@ -23,7 +23,6 @@
id="cursorManager"
scroll="keep-visible"
cursor-target-class="target-row"
- fold-offset-top="[[foldOffsetTop]]"
target="{{diffRow}}"></gr-cursor-manager>
</template>
<script src="gr-diff-cursor.js"></script>
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 99a0b5c..dd11f2c 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
@@ -54,11 +54,6 @@
},
},
- foldOffsetTop: {
- type: Number,
- value: 0,
- },
-
/**
* If set, the cursor will attempt to move to the line number (instead of
* the first chunk) the next time the diff renders. It is set back to null
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 0fc6b07..6fc09ca0 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -14,6 +14,8 @@
(function() {
'use strict';
+ var TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g;
+
Polymer({
is: 'gr-autocomplete',
@@ -199,8 +201,10 @@
var completed = suggestions[index].value;
if (this.multi) {
// Append the completed text to the end of the string.
- var shortStr = this.text.substring(0, this.text.lastIndexOf(' ') + 1);
- this.value = shortStr + completed;
+ // Allow spaces within quoted terms.
+ var tokens = this.text.match(TOKENIZE_REGEX);
+ tokens[tokens.length - 1] = completed;
+ this.value = tokens.join(' ');
} else {
this.value = completed;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index c815ffd..cc0da66 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -22,7 +22,7 @@
<template strip-whitespace>
<style>
:host {
- background-color: #fff;
+ background-color: #f5f5f5;
border: 1px solid #d1d2d3;
border-radius: 2px;
box-sizing: border-box;
@@ -30,10 +30,10 @@
cursor: pointer;
display: inline-block;
font-family: var(--font-family);
- font-size: 13px;
+ font-size: 12px;
font-weight: bold;
outline-width: 0;
- padding: .3em .65em;
+ padding: .4em .85em;
position: relative;
text-align: center;
-moz-user-select: none;
@@ -44,10 +44,17 @@
:host([hidden]) {
display: none;
}
+ :host([primary]),
+ :host([secondary]) {
+ color: #fff;
+ }
:host([primary]) {
background-color: #4d90fe;
border-color: #3079ed;
- color: #fff;
+ }
+ :host([secondary]) {
+ background-color: #d14836;
+ border-color: transparent;
}
:host([small]) {
font-size: 12px;
@@ -74,25 +81,44 @@
:host([loading][disabled]) {
cursor: wait;
}
- :host(:focus),
- :host(:hover) {
- border-color: #666;
+ :host(:focus:not([primary]:not[secondary])),
+ :host(:hover:not([primary]:not[secondary])) {
+ background-color: #f8f8f8;
+ border-color: #aaa;
}
:host(:active) {
border-color: #d1d2d3;
color: #aaa;
}
+ :host([primary]:focus),
+ :host([secondary]:focus),
+ :host([primary]:active),
+ :host([secondary]:active) {
+ color: #fff;
+ }
:host([primary]:focus) {
- border-color: #fff;
box-shadow: 0 0 1px #00f;
}
:host([primary]:hover) {
+ background-color: #4d90fe;
border-color: #00F;
}
+ :host([primary]:active),
+ :host([secondary]:active) {
+ box-shadow: none;
+ }
:host([primary]:active) {
border-color: #0c2188;
- box-shadow: none;
- color: #fff;
+ }
+ :host([secondary]:focus) {
+ box-shadow: 0 0 1px #f00;
+ }
+ :host([secondary]:hover) {
+ background-color: #c53727;
+ border: 1px solid #b0281a;
+ }
+ :host([secondary]:active) {
+ border-color: #941c0c;
}
:host([primary][loading]),
:host([primary][disabled]) {
@@ -100,6 +126,7 @@
border-color: transparent;
color: #fff;
}
+
</style>
<content></content>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 0d3ea3d..7b3bc23 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -63,15 +63,6 @@
type: String,
value: ScrollBehavior.NEVER,
},
-
- /**
- * When using the 'keep-visible' scroll behavior, set an offset to the top
- * of the window for what is considered above the upper fold.
- */
- foldOffsetTop: {
- type: Number,
- value: 0,
- },
},
detached: function() {
@@ -214,7 +205,7 @@
}
if (this.scroll === ScrollBehavior.KEEP_VISIBLE &&
- top > window.pageYOffset + this.foldOffsetTop &&
+ top > window.pageYOffset &&
top < window.pageYOffset + window.innerHeight) { return; }
// Scroll the element to the middle of the window. Dividing by a third
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 2f109c9..0b07217 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
@@ -467,8 +467,26 @@
});
},
- getSuggestedProjects: function(inputVal, opt_errFn, opt_ctx) {
- return this.fetchJSON('/projects/', opt_errFn, opt_ctx, {p: inputVal});
+ getSuggestedGroups: function(inputVal, opt_n, opt_errFn, opt_ctx) {
+ return this.fetchJSON('/groups/', opt_errFn, opt_ctx, {
+ s: inputVal,
+ n: opt_n,
+ });
+ },
+
+ getSuggestedProjects: function(inputVal, opt_n, opt_errFn, opt_ctx) {
+ return this.fetchJSON('/projects/', opt_errFn, opt_ctx, {
+ p: inputVal,
+ n: opt_n,
+ });
+ },
+
+ getSuggestedAccounts: function(inputVal, opt_n, opt_errFn, opt_ctx) {
+ return this.fetchJSON('/accounts/', opt_errFn, opt_ctx, {
+ q: inputVal,
+ n: opt_n,
+ suggest: null,
+ });
},
addChangeReviewer: function(changeNum, reviewerID) {