Merge changes from topic 'arbitrary-download-schemes'
* changes:
Sort download schemes and command names
SetPreferences: Enforce download scheme is registered
Describe allowed values for download_scheme field
DownloadUrlLink: Kill KnownScheme enum
Store preferred download scheme as arbitrary strings
DownloadConfig: Make set methods return ImmutableSet
Remove DEFAULT_SCHEMES and DEFAULT_COMMANDS
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 62d0219..da213a8 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -28,6 +28,12 @@
to a change being abandoned. It is a `ChangeEmail`: see `ChangeSubject.vm` and
`ChangeFooter.vm`.
+=== AddKey.vm
+
+The `AddKey.vm` template will determine the contents of the email related to
+SSH and GPG keys being added to a user account. This notification is not sent
+when the key is administratively added to another user account.
+
=== ChangeFooter.vm
The `ChangeFooter.vm` template will determine the contents of the footer
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index adf77cf..eda4b5d 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -367,6 +367,19 @@
Changes where 'USER' has commented on the change more recently than the
last update (comment or patch set) from the change owner.
+[[author]]
+author:'AUTHOR'::
++
+Changes where 'AUTHOR' is the author of the current patch set. 'AUTHOR' may be
+the author's exact email address, or part of the name or email address.
+
+[[committer]]
+committer:'COMMITTER'::
++
+Changes where 'COMMITTER' is the committer of the current patch set.
+'COMMITTER' may be the committer's exact email address, or part of the name or
+email address.
+
== Argument Quoting
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 833070b..3adab73 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -44,6 +44,7 @@
'//lib/mina:sshd',
],
visibility = [
+ '//gerrit-plugin-api/...',
'//tools/eclipse:classpath',
'//gerrit-acceptance-tests/...',
],
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 18ee2ef..8d97136 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -76,6 +76,7 @@
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
@@ -193,6 +194,9 @@
}
};
+ @Rule
+ public TemporaryFolder tempSiteDir = new TemporaryFolder();
+
@AfterClass
public static void stopCommonServer() throws Exception {
if (commonServer != null) {
@@ -222,12 +226,14 @@
return cfg.getBoolean("change", null, "submitWholeTopic", false);
}
- private void beforeTest(Description description) throws Exception {
+ protected void beforeTest(Description description) throws Exception {
GerritServer.Description classDesc =
GerritServer.Description.forTestClass(description, configName);
GerritServer.Description methodDesc =
GerritServer.Description.forTestMethod(description, configName);
+ baseConfig.setString("gerrit", null, "tempSiteDir",
+ tempSiteDir.getRoot().getPath());
if (classDesc.equals(methodDesc)) {
if (commonServer == null) {
commonServer = GerritServer.start(classDesc, baseConfig);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 8f4c2d4..634db7c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -62,7 +62,7 @@
// TODO(dborowitz): Use jimfs.
bind(Path.class)
.annotatedWith(SitePath.class)
- .toInstance(Paths.get("UNIT_TEST_GERRIT_SITE"));
+ .toInstance(Paths.get(cfg.getString("gerrit", null, "tempSiteDir")));
bind(GitRepositoryManager.class)
.toInstance(new InMemoryRepositoryManager());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
new file mode 100644
index 0000000..4913488
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
@@ -0,0 +1,218 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.gerrit.server.config.SitePaths;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ProcessBuilder.Redirect;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class PluginDaemonTest extends AbstractDaemonTest {
+
+ private static final String BUCKLC = "buck";
+ private static final String BUCKOUT = "buck-out";
+
+ private Path gen;
+ private Path testSite;
+ private Path pluginRoot;
+ private Path pluginsSitePath;
+ private Path pluginSubPath;
+ private Path pluginSource;
+ private String pluginName;
+ private boolean standalone;
+
+ @Override
+ protected void beforeTest(Description description) throws Exception {
+ locatePaths();
+ retrievePluginName();
+ buildPluginJar();
+ createTestSiteDirs();
+ copyJarToTestSite();
+ super.beforeTest(description);
+ }
+
+ protected void setPluginConfigString(String name, String value)
+ throws IOException, ConfigInvalidException {
+ SitePaths sitePath = new SitePaths(testSite);
+ FileBasedConfig cfg = getGerritConfigFile(sitePath);
+ cfg.load();
+ cfg.setString("plugin", pluginName, name, value);
+ cfg.save();
+ }
+
+ private FileBasedConfig getGerritConfigFile(SitePaths sitePath)
+ throws IOException {
+ FileBasedConfig cfg =
+ new FileBasedConfig(sitePath.gerrit_config.toFile(), FS.DETECTED);
+ if (!cfg.getFile().exists()) {
+ Path etc_path = Files.createDirectories(sitePath.etc_dir);
+ Files.createFile(etc_path.resolve("gerrit.config"));
+ }
+ return cfg;
+ }
+
+ private void locatePaths() {
+ URL pluginClassesUrl =
+ getClass().getProtectionDomain().getCodeSource().getLocation();
+ Path basePath = Paths.get(pluginClassesUrl.getPath()).getParent();
+
+ int idx = 0;
+ int buckOutIdx = 0;
+ int pluginsIdx = 0;
+ for (Path subPath : basePath) {
+ if (subPath.endsWith("plugins")) {
+ pluginsIdx = idx;
+ }
+ if (subPath.endsWith(BUCKOUT)) {
+ buckOutIdx = idx;
+ }
+ idx++;
+ }
+ standalone = checkStandalone(basePath);
+ pluginRoot = basePath.getRoot().resolve(basePath.subpath(0, buckOutIdx));
+ gen = pluginRoot.resolve(BUCKOUT).resolve("gen");
+
+ if (standalone) {
+ pluginSource = pluginRoot;
+ } else {
+ pluginSubPath = basePath.subpath(pluginsIdx, pluginsIdx + 2);
+ pluginSource = pluginRoot.resolve(pluginSubPath);
+ }
+ }
+
+ private boolean checkStandalone(Path basePath) {
+ String pathCharStringOrNone = "[a-zA-Z0-9._-]*?";
+ Pattern pattern = Pattern.compile(pathCharStringOrNone + "gerrit" +
+ pathCharStringOrNone);
+ Path partialPath = basePath;
+ for (int i = basePath.getNameCount(); i > 0; i--) {
+ int count = partialPath.getNameCount();
+ if (count > 1) {
+ String gerritDirCandidate =
+ partialPath.subpath(count - 2, count - 1).toString();
+ if (pattern.matcher(gerritDirCandidate).matches()) {
+ if (partialPath.endsWith(gerritDirCandidate + "/" + BUCKOUT)) {
+ return false;
+ }
+ }
+ }
+ partialPath = partialPath.getParent();
+ }
+ return true;
+ }
+
+ private void retrievePluginName() throws IOException {
+ Path buckFile = pluginSource.resolve("BUCK");
+ byte[] bytes = Files.readAllBytes(buckFile);
+ String buckContent =
+ new String(bytes, StandardCharsets.UTF_8).replaceAll("\\s+", "");
+ Matcher matcher =
+ Pattern.compile("gerrit_plugin\\(name='(.*?)'").matcher(buckContent);
+ if (matcher.find()) {
+ pluginName = matcher.group(1);
+ }
+ if (Strings.isNullOrEmpty(pluginName)) {
+ if (standalone) {
+ pluginName = pluginRoot.getFileName().toString();
+ } else {
+ pluginName = pluginSubPath.getFileName().toString();
+ }
+ }
+ }
+
+ private void buildPluginJar() throws IOException, InterruptedException {
+ Properties properties = loadBuckProperties();
+ String buck =
+ MoreObjects.firstNonNull(properties.getProperty(BUCKLC), BUCKLC);
+ String target;
+ if (standalone) {
+ target = "//:" + pluginName;
+ } else {
+ target = pluginSubPath.toString();
+ }
+
+ ProcessBuilder processBuilder =
+ new ProcessBuilder(buck, "build", target).directory(pluginRoot.toFile())
+ .redirectErrorStream(true);
+ // otherwise plugin jar creation fails:
+ processBuilder.environment().put("NO_BUCKD", "1");
+
+ Path forceJar = pluginSource.resolve("src/main/java/ForceJarIfMissing.java");
+ // if exists after cancelled test:
+ Files.deleteIfExists(forceJar);
+
+ Files.createFile(forceJar);
+ testSite = tempSiteDir.getRoot().toPath();
+
+ // otherwise process often hangs:
+ Path log = testSite.resolve("log");
+ processBuilder.redirectErrorStream(true);
+ processBuilder.redirectOutput(Redirect.appendTo(log.toFile()));
+
+ try {
+ processBuilder.start().waitFor();
+ } finally {
+ Files.delete(forceJar);
+ // otherwise jar not made next time if missing again:
+ processBuilder.start().waitFor();
+ }
+ }
+
+ private Properties loadBuckProperties() throws IOException {
+ Properties properties = new Properties();
+ Path propertiesPath = gen.resolve("tools").resolve("buck.properties");
+ if (Files.exists(propertiesPath)) {
+ try (InputStream in = Files.newInputStream(propertiesPath)) {
+ properties.load(in);
+ }
+ }
+ return properties;
+ }
+
+ private void createTestSiteDirs() throws IOException {
+ SitePaths sitePath = new SitePaths(testSite);
+ pluginsSitePath = Files.createDirectories(sitePath.plugins_dir);
+ Files.createDirectories(sitePath.tmp_dir);
+ }
+
+ private void copyJarToTestSite() throws IOException {
+ Path pluginOut;
+ if (standalone) {
+ pluginOut = gen;
+ } else {
+ pluginOut = gen.resolve(pluginSubPath);
+ }
+ Path jar = pluginOut.resolve(pluginName + ".jar");
+ Path dest = pluginsSitePath.resolve(jar.getFileName());
+ Files.copy(jar, dest, StandardCopyOption.REPLACE_EXISTING);
+ }
+}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 670adba..80e3500 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -27,6 +27,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -41,6 +42,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -54,6 +56,8 @@
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -71,20 +75,24 @@
public List<String> delete;
}
+ private final Logger log = LoggerFactory.getLogger(getClass());
private final Provider<PersonIdent> serverIdent;
private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
private final PublicKeyChecker checker;
+ private final AddKeySender.Factory addKeyFactory;
@Inject
PostGpgKeys(@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<ReviewDb> db,
Provider<PublicKeyStore> storeProvider,
- PublicKeyChecker checker) {
+ PublicKeyChecker checker,
+ AddKeySender.Factory addKeyFactory) {
this.serverIdent = serverIdent;
this.db = db;
this.storeProvider = storeProvider;
this.checker = checker;
+ this.addKeyFactory = addKeyFactory;
}
@Override
@@ -180,6 +188,7 @@
Set<Fingerprint> toRemove) throws BadRequestException,
ResourceConflictException, PGPException, IOException {
try (PublicKeyStore store = storeProvider.get()) {
+ List<String> addedKeys = new ArrayList<>();
for (PGPPublicKeyRing keyRing : keyRings) {
PGPPublicKey key = keyRing.getPublicKey();
CheckResult result = checker.check(key);
@@ -188,6 +197,7 @@
"Problems with public key %s:\n%s",
keyToString(key), Joiner.on('\n').join(result.getProblems())));
}
+ addedKeys.add(PublicKeyStore.keyToString(key));
store.add(keyRing);
}
for (Fingerprint fp : toRemove) {
@@ -204,6 +214,13 @@
case NEW:
case FAST_FORWARD:
case FORCED:
+ try {
+ addKeyFactory.create(rsrc.getUser(), addedKeys).send();
+ } catch (EmailException e) {
+ log.error("Cannot send GPG key added message to "
+ + rsrc.getUser().getAccount().getPreferredEmail(), e);
+ }
+ break;
case NO_CHANGE:
break;
default:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 20afa19..6c2fd04 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -72,6 +72,8 @@
suggestions.add("owner:");
suggestions.add("owner:self");
suggestions.add("ownerin:");
+ suggestions.add("author:");
+ suggestions.add("committer:");
suggestions.add("reviewer:");
suggestions.add("reviewer:self");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 8a227ac..6270a15 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -100,6 +100,7 @@
chmod(0700, site.tmp_dir);
extractMailExample("Abandoned.vm");
+ extractMailExample("AddKey.vm");
extractMailExample("ChangeFooter.vm");
extractMailExample("ChangeSubject.vm");
extractMailExample("Comment.vm");
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index 6a4e4c0..ed11e0f 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -20,6 +20,7 @@
java_library(
name = 'lib',
exported_deps = PLUGIN_API + [
+ '//gerrit-acceptance-tests:lib',
'//gerrit-antlr:query_exception',
'//gerrit-antlr:query_parser',
'//gerrit-common:annotations',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 3c21d17..7ec659e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -18,6 +18,7 @@
import com.google.common.collect.Iterables;
import com.google.common.io.ByteSource;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -30,6 +31,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AddSshKey.Input;
import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -37,12 +39,17 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@Singleton
public class AddSshKey implements RestModifyView<AccountResource, Input> {
+ private static final Logger log = LoggerFactory.getLogger(AddSshKey.class);
+
public static class Input {
public RawInput raw;
}
@@ -50,13 +57,15 @@
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final SshKeyCache sshKeyCache;
+ private final AddKeySender.Factory addKeyFactory;
@Inject
AddSshKey(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
- SshKeyCache sshKeyCache) {
+ SshKeyCache sshKeyCache, AddKeySender.Factory addKeyFactory) {
this.self = self;
this.dbProvider = dbProvider;
this.sshKeyCache = sshKeyCache;
+ this.addKeyFactory = addKeyFactory;
}
@Override
@@ -96,6 +105,12 @@
sshKeyCache.create(new AccountSshKey.Id(
user.getAccountId(), max + 1), sshPublicKey);
dbProvider.get().accountSshKeys().insert(Collections.singleton(sshKey));
+ try {
+ addKeyFactory.create(user, sshKey).send();
+ } catch (EmailException e) {
+ log.error("Cannot send SSH key added message to "
+ + user.getAccount().getPreferredEmail(), e);
+ }
sshKeyCache.evict(user.getUserName());
return Response.<SshKeyInfo>created(new SshKeyInfo(sshKey));
} catch (InvalidSshKeyException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index cf1053d..3964115 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -95,6 +95,7 @@
import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.ReindexAfterUpdate;
import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.mail.AddKeySender;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -186,6 +187,7 @@
factory(AccountInfoCacheFactory.Factory.class);
factory(AddReviewerSender.Factory.class);
+ factory(AddKeySender.Factory.class);
factory(CapabilityControl.Factory.class);
factory(ChangeData.Factory.class);
factory(ChangeJson.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index 5d7229a..e711306 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -16,6 +16,7 @@
import static com.google.common.base.MoreObjects.firstNonNull;
+import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -38,12 +39,14 @@
import com.google.gwtorm.server.OrmException;
import com.google.protobuf.CodedOutputStream;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterLine;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -404,6 +407,64 @@
}
};
+ private static Set<String> getPersonParts(PersonIdent person) {
+ if (person == null) {
+ return ImmutableSet.of();
+ }
+ HashSet<String> parts = Sets.newHashSet();
+ String email = person.getEmailAddress().toLowerCase();
+ parts.add(email);
+ parts.addAll(Arrays.asList(email.split("@")));
+ Splitter s = Splitter.on(CharMatcher.anyOf("@.- ")).omitEmptyStrings();
+ Iterables.addAll(parts, s.split(email));
+ Iterables.addAll(parts, s.split(person.getName().toLowerCase()));
+ return parts;
+ }
+
+ public static Set<String> getAuthorParts(ChangeData cd) throws OrmException {
+ try {
+ return getPersonParts(cd.getAuthor());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ public static Set<String> getCommitterParts(ChangeData cd) throws OrmException {
+ try {
+ return getPersonParts(cd.getCommitter());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ /**
+ * The exact email address, or any part of the author name or email address,
+ * in the current patch set.
+ */
+ public static final FieldDef<ChangeData, Iterable<String>> AUTHOR =
+ new FieldDef.Repeatable<ChangeData, String>(
+ ChangeQueryBuilder.FIELD_AUTHOR, FieldType.FULL_TEXT, false) {
+ @Override
+ public Iterable<String> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return getAuthorParts(input);
+ }
+ };
+
+ /**
+ * The exact email address, or any part of the committer name or email address,
+ * in the current patch set.
+ */
+ public static final FieldDef<ChangeData, Iterable<String>> COMMITTER =
+ new FieldDef.Repeatable<ChangeData, String>(
+ ChangeQueryBuilder.FIELD_COMMITTER, FieldType.FULL_TEXT, false) {
+ @Override
+ public Iterable<String> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return getCommitterParts(input);
+ }
+ };
+
public static class ChangeProtoField extends FieldDef.Single<ChangeData, byte[]> {
public static final ProtobufCodec<Change> CODEC =
CodecFactory.encoder(Change.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 4915a663..a8a97a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -308,6 +308,7 @@
ChangeField.REVIEWEDBY,
ChangeField.EXACT_COMMIT);
+ @Deprecated
static final Schema<ChangeData> V23 = schema(
ChangeField.LEGACY_ID2,
ChangeField.ID,
@@ -341,6 +342,40 @@
ChangeField.REVIEWEDBY,
ChangeField.EXACT_COMMIT);
+ static final Schema<ChangeData> V24 = schema(
+ ChangeField.LEGACY_ID2,
+ ChangeField.ID,
+ ChangeField.STATUS,
+ ChangeField.PROJECT,
+ ChangeField.PROJECTS,
+ ChangeField.REF,
+ ChangeField.EXACT_TOPIC,
+ ChangeField.FUZZY_TOPIC,
+ ChangeField.UPDATED,
+ ChangeField.FILE_PART,
+ ChangeField.PATH,
+ ChangeField.OWNER,
+ ChangeField.REVIEWER,
+ ChangeField.COMMIT,
+ ChangeField.TR,
+ ChangeField.LABEL,
+ ChangeField.COMMIT_MESSAGE,
+ ChangeField.COMMENT,
+ ChangeField.CHANGE,
+ ChangeField.APPROVAL,
+ ChangeField.MERGEABLE,
+ ChangeField.ADDED,
+ ChangeField.DELETED,
+ ChangeField.DELTA,
+ ChangeField.HASHTAG,
+ ChangeField.COMMENTBY,
+ ChangeField.PATCH_SET,
+ ChangeField.GROUP,
+ ChangeField.EDITBY,
+ ChangeField.REVIEWEDBY,
+ ChangeField.EXACT_COMMIT,
+ ChangeField.AUTHOR,
+ ChangeField.COMMITTER);
private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
return new Schema<>(ImmutableList.copyOf(fields));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
new file mode 100644
index 0000000..0f1e86e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
@@ -0,0 +1,113 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail;
+
+import com.google.common.base.Joiner;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import java.util.List;
+
+public class AddKeySender extends OutgoingEmail {
+ public interface Factory {
+ public AddKeySender create(IdentifiedUser user, AccountSshKey sshKey);
+
+ public AddKeySender create(IdentifiedUser user, List<String> gpgKey);
+ }
+
+ private final IdentifiedUser callingUser;
+ private final IdentifiedUser user;
+ private final AccountSshKey sshKey;
+ private final List<String> gpgKeys;
+
+ @AssistedInject
+ public AddKeySender(EmailArguments ea,
+ IdentifiedUser callingUser,
+ @Assisted IdentifiedUser user,
+ @Assisted AccountSshKey sshKey) {
+ super(ea, "addkey");
+ this.callingUser = callingUser;
+ this.user = user;
+ this.sshKey = sshKey;
+ this.gpgKeys = null;
+ }
+
+ @AssistedInject
+ public AddKeySender(EmailArguments ea,
+ IdentifiedUser callingUser,
+ @Assisted IdentifiedUser user,
+ @Assisted List<String> gpgKeys) {
+ super(ea, "addkey");
+ this.callingUser = callingUser;
+ this.user = user;
+ this.sshKey = null;
+ this.gpgKeys = gpgKeys;
+ }
+
+ @Override
+ protected void init() throws EmailException {
+ super.init();
+ setHeader("Subject",
+ String.format("[Gerrit Code Review] New %s Keys Added", getKeyType()));
+ add(RecipientType.TO, new Address(getEmail()));
+ }
+
+ @Override
+ protected boolean shouldSendMessage() {
+ /*
+ * Don't send an email if no keys are added, or an admin is adding a key to
+ * a user.
+ */
+ return (sshKey != null || gpgKeys.size() > 0) &&
+ (user.equals(callingUser) ||
+ !callingUser.getCapabilities().canAdministrateServer());
+ }
+
+ @Override
+ protected void format() throws EmailException {
+ appendText(velocifyFile("AddKey.vm"));
+ }
+
+ public String getEmail() {
+ return user.getAccount().getPreferredEmail();
+ }
+
+ public String getUserNameEmail() {
+ return getUserNameEmailFor(user.getAccountId());
+ }
+
+ public String getKeyType() {
+ if (sshKey != null) {
+ return "SSH";
+ } else if (gpgKeys != null) {
+ return "GPG";
+ }
+ return "Unknown";
+ }
+
+ public String getSshKey() {
+ return (sshKey != null) ? sshKey.getSshPublicKey() + "\n" : null;
+ }
+
+ public String getGpgKeys() {
+ if (gpgKeys != null) {
+ return Joiner.on("\n").join(gpgKeys);
+ }
+ return null;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index 1e4fec7..a2f369b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -268,6 +268,13 @@
return name;
}
+ /**
+ * Gets the human readable name and email for an account;
+ * if neither are available, returns the Anonymous Coward name.
+ *
+ * @param accountId user to fetch.
+ * @return name/email of account, or Anonymous Coward if unset.
+ */
public String getNameEmailFor(Account.Id accountId) {
AccountState who = args.accountCache.get(accountId);
String name = who.getAccount().getFullName();
@@ -286,6 +293,33 @@
}
}
+ /**
+ * Gets the human readable name and email for an account;
+ * if both are unavailable, returns the username. If no
+ * username is set, this function returns null.
+ *
+ * @param accountId user to fetch.
+ * @return name/email of account, username, or null if unset.
+ */
+ public String getUserNameEmailFor(Account.Id accountId) {
+ AccountState who = args.accountCache.get(accountId);
+ String name = who.getAccount().getFullName();
+ String email = who.getAccount().getPreferredEmail();
+
+ if (name != null && email != null) {
+ return name + " <" + email + ">";
+ } else if (email != null) {
+ return email;
+ } else if (name != null) {
+ return name;
+ }
+ String username = who.getUserName();
+ if (username != null) {
+ return username;
+ }
+ return null;
+ }
+
protected boolean shouldSendMessage() {
if (body.length() == 0) {
// If we have no message body, don't send.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index eb32700..beada69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -58,22 +58,7 @@
}
public String getUserNameEmail() {
- String name = user.getAccount().getFullName();
- String email = user.getAccount().getPreferredEmail();
-
- if (name != null && email != null) {
- return name + " <" + email + ">";
- } else if (email != null) {
- return email;
- } else if (name != null) {
- return name;
- } else {
- String username = user.getUserName();
- if (username != null) {
- return username;
- }
- }
- return null;
+ return getUserNameEmailFor(user.getAccountId());
}
public String getEmailRegistrationToken() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java
new file mode 100644
index 0000000..193a061
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.gerrit.server.index.ChangeField.AUTHOR;
+import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_AUTHOR;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+public class AuthorPredicate extends IndexPredicate<ChangeData> {
+ AuthorPredicate(String value) {
+ super(AUTHOR, FIELD_AUTHOR, value);
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ return ChangeField.getAuthorParts(object).contains(
+ getValue().toLowerCase());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 0523d73..8061a26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -62,6 +62,7 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.FooterLine;
@@ -317,6 +318,8 @@
private Boolean mergeable;
private Set<Account.Id> editsByUser;
private Set<Account.Id> reviewedBy;
+ private PersonIdent author;
+ private PersonIdent committer;
@AssistedInject
private ChangeData(
@@ -620,6 +623,24 @@
return commitFooters;
}
+ public PersonIdent getAuthor() throws IOException, OrmException {
+ if (author == null) {
+ if (!loadCommitData()) {
+ return null;
+ }
+ }
+ return author;
+ }
+
+ public PersonIdent getCommitter() throws IOException, OrmException {
+ if (committer == null) {
+ if (!loadCommitData()) {
+ return null;
+ }
+ }
+ return committer;
+ }
+
private boolean loadCommitData() throws OrmException,
RepositoryNotFoundException, IOException, MissingObjectException,
IncorrectObjectTypeException {
@@ -633,6 +654,8 @@
RevCommit c = walk.parseCommit(ObjectId.fromString(sha1));
commitMessage = c.getFullMessage();
commitFooters = c.getFooterLines();
+ author = c.getAuthorIdent();
+ committer = c.getCommitterIdent();
}
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index c6ffc27..776a7f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -91,12 +91,14 @@
public static final String FIELD_ADDED = "added";
public static final String FIELD_AFTER = "after";
public static final String FIELD_AGE = "age";
+ public static final String FIELD_AUTHOR = "author";
public static final String FIELD_BEFORE = "before";
public static final String FIELD_BRANCH = "branch";
public static final String FIELD_CHANGE = "change";
public static final String FIELD_COMMENT = "comment";
public static final String FIELD_COMMENTBY = "commentby";
public static final String FIELD_COMMIT = "commit";
+ public static final String FIELD_COMMITTER = "committer";
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
@@ -843,6 +845,16 @@
throw new QueryParseException("Unknown named destination: " + name);
}
+ @Operator
+ public Predicate<ChangeData> author(String who) {
+ return new AuthorPredicate(who);
+ }
+
+ @Operator
+ public Predicate<ChangeData> committer(String who) {
+ return new CommitterPredicate(who);
+ }
+
@Override
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java
new file mode 100644
index 0000000..e5d9529
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.gerrit.server.index.ChangeField.COMMITTER;
+import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_COMMITTER;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+public class CommitterPredicate extends IndexPredicate<ChangeData> {
+ CommitterPredicate(String value) {
+ super(COMMITTER, FIELD_COMMITTER, value);
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ return ChangeField.getCommitterParts(object).contains(
+ getValue().toLowerCase());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm
new file mode 100644
index 0000000..c60ce8b
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm
@@ -0,0 +1,61 @@
+## Copyright (C) 2015 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The AddKey.vm template will determine the contents of the email
+## related to adding a new SSH or GPG key to an account.
+##
+One or more new ${email.keyType} keys have been added to Gerrit Code Review at ${email.gerritHost}:
+
+#if($email.sshKey)
+$email.sshKey
+#elseif($email.gpgKeys)
+$email.gpgKeys
+#end
+
+If this is not expected, please contact your Gerrit Administrators
+immediately.
+
+You can also manage your ${email.keyType} keys by visiting
+#if($email.sshKey)
+$email.gerritUrl#/settings/ssh-keys
+#elseif($email.gpgKeys)
+$email.gerritUrl#/settings/gpg-keys
+#end
+#if($email.userNameEmail)
+(while signed in as $email.userNameEmail)
+#else
+(while signed in as $email.email)
+#end
+
+If clicking the link above does not work, copy and paste the URL in a
+new browser window instead.
+
+This is a send-only email address. Replies to this message will not
+be read or answered.
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 27c3443..499caa2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -380,6 +380,52 @@
}
@Test
+ public void byAuthor() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+
+ // By exact email address
+ assertQuery("author:jauthor@example.com", change1);
+
+ // By email address part
+ assertQuery("author:jauthor", change1);
+ assertQuery("author:example", change1);
+ assertQuery("author:example.com", change1);
+
+ // By name part
+ assertQuery("author:Author", change1);
+
+ // By non-existing email address / name / part
+ assertQuery("author:jcommitter@example.com");
+ assertQuery("author:somewhere.com");
+ assertQuery("author:jcommitter");
+ assertQuery("author:Committer");
+ }
+
+ @Test
+ public void byCommitter() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+
+ // By exact email address
+ assertQuery("committer:jcommitter@example.com", change1);
+
+ // By email address part
+ assertQuery("committer:jcommitter", change1);
+ assertQuery("committer:example", change1);
+ assertQuery("committer:example.com", change1);
+
+ // By name part
+ assertQuery("committer:Committer", change1);
+
+ // By non-existing email address / name / part
+ assertQuery("committer:jauthor@example.com");
+ assertQuery("committer:somewhere.com");
+ assertQuery("committer:jauthor");
+ assertQuery("committer:Author");
+ }
+
+ @Test
public void byOwnerIn() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Change change1 = newChange(repo, null, null, userId.get(), null).insert();
diff --git a/lib/bouncycastle/BUCK b/lib/bouncycastle/BUCK
index 0ce5817..ff6e6c5 100644
--- a/lib/bouncycastle/BUCK
+++ b/lib/bouncycastle/BUCK
@@ -9,6 +9,7 @@
id = 'org.bouncycastle:bcprov-jdk15on:' + VERSION,
sha1 = '88a941faf9819d371e3174b5ed56a3f3f7d73269',
license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
+ exclude = ['META-INF/BCKEY.*'],
)
maven_jar(
@@ -16,6 +17,7 @@
id = 'org.bouncycastle:bcpg-jdk15on:' + VERSION,
sha1 = 'ff4665a4b5633ff6894209d5dd10b7e612291858',
license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
+ exclude = ['META-INF/BCKEY.*'],
deps = [':bcprov'],
)
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index b9d3ca8..ec6ed89 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit b9d3ca8a65030071e28be19296ba867ab424fbbf
+Subproject commit ec6ed89c47ba7223f82d9cb512926a6c5081343e