Merge "Show "File X of Y" next to file navigation links in diff view"
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index baf08e7..2996a98 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python2
# coding=utf-8
# Copyright (C) 2013 The Android Open Source Project
#
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index 4b928f3..3c922ed 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -244,6 +244,17 @@
been posted in that notification using "Yes" or "No", for
example `Gerrit-HasLabels: No`.
+[[Gerrit-Comment-In-Reply-To]]Gerrit-Comment-In-Reply-To::
+
+In comment emails, a comment-in-reply-to footer is present for each
+account who has a comment that is replied-to in that set of comments.
+For example, to apply a filter to Gerrit messages in which your own diff
+comments are responded to, you might search for the following:
+
+----
+ Gerrit-Comment-In-Reply-To: User Name <user@example.com>
+----
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 31b601f..db673dd 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assert_;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
-import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
@@ -129,6 +128,7 @@
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.jcraft.jsch.JSchException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -201,8 +201,10 @@
}
beforeTest(description);
try (ProjectResetter resetter = resetProjects(projectResetter.builder())) {
+ AbstractDaemonTest.this.resetter = resetter;
base.evaluate();
} finally {
+ AbstractDaemonTest.this.resetter = null;
afterTest();
}
}
@@ -267,6 +269,7 @@
@Inject private SchemaFactory<ReviewDb> reviewDbProvider;
@Inject private Groups groups;
+ private ProjectResetter resetter;
private List<Repository> toClose;
@Before
@@ -330,6 +333,16 @@
.build();
}
+ protected void restartAsSlave() throws Exception {
+ closeSsh();
+ server = GerritServer.restartAsSlave(server);
+ server.getTestInjector().injectMembers(this);
+ if (resetter != null) {
+ server.getTestInjector().injectMembers(resetter);
+ }
+ initSsh();
+ }
+
protected static Config submitWholeTopicEnabledConfig() {
Config cfg = new Config();
cfg.setBoolean("change", null, "submitWholeTopic", true);
@@ -396,20 +409,7 @@
adminRestSession = new RestSession(server, admin);
userRestSession = new RestSession(server, user);
- if (testRequiresSsh
- && SshMode.useSsh()
- && (adminSshSession == null || userSshSession == null)) {
- // Create Ssh sessions
- initSsh(admin);
- Context ctx = newRequestContext(user);
- atrScope.set(ctx);
- userSshSession = ctx.getSession();
- userSshSession.open();
- ctx = newRequestContext(admin);
- atrScope.set(ctx);
- adminSshSession = ctx.getSession();
- adminSshSession.open();
- }
+ initSsh();
resourcePrefix =
UNSAFE_PROJECT_NAME
@@ -422,6 +422,23 @@
testRepo = cloneProject(project, getCloneAsAccount(description));
}
+ protected void initSsh() throws JSchException {
+ if (testRequiresSsh
+ && SshMode.useSsh()
+ && (adminSshSession == null || userSshSession == null)) {
+ // Create Ssh sessions
+ GitUtil.initSsh(admin);
+ Context ctx = newRequestContext(user);
+ atrScope.set(ctx);
+ userSshSession = ctx.getSession();
+ userSshSession.open();
+ ctx = newRequestContext(admin);
+ atrScope.set(ctx);
+ adminSshSession = ctx.getSession();
+ adminSshSession.open();
+ }
+ }
+
private TestAccount getCloneAsAccount(Description description) {
TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
return accountCreator.get(ann != null ? ann.cloneAs() : "admin");
@@ -557,12 +574,7 @@
repo.close();
}
db.close();
- if (adminSshSession != null) {
- adminSshSession.close();
- }
- if (userSshSession != null) {
- userSshSession.close();
- }
+ closeSsh();
if (server != commonServer) {
server.close();
server = null;
@@ -570,6 +582,17 @@
NoteDbMode.resetFromEnv(notesMigration);
}
+ protected void closeSsh() {
+ if (adminSshSession != null) {
+ adminSshSession.close();
+ adminSshSession = null;
+ }
+ if (userSshSession != null) {
+ userSshSession.close();
+ userSshSession = null;
+ }
+ }
+
protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
return testRepo.branch("HEAD").commit().insertChangeId();
}
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 1b9e8aa..5262b74 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
@@ -27,6 +28,7 @@
import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.util.ManualRequestContext;
@@ -35,6 +37,8 @@
import com.google.gerrit.server.util.SystemLog;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.GroupNoteDbMode;
+import com.google.gerrit.testing.InMemoryDatabase;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.NoteDbChecker;
import com.google.gerrit.testing.NoteDbMode;
import com.google.gerrit.testing.SshMode;
@@ -261,7 +265,7 @@
if (!desc.memory()) {
init(desc, baseConfig, site);
}
- return start(desc, baseConfig, site, null);
+ return start(desc, baseConfig, site, null, null, null);
} catch (Exception e) {
TempFileUtil.recursivelyDelete(site.toFile());
throw e;
@@ -278,6 +282,10 @@
* initialize this directory. Can be retrieved from the returned instance via {@link
* #getSitePath()}.
* @param testSysModule optional additional module to add to the system injector.
+ * @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
+ * started in memory
+ * @param inMemoryDatabaseInstance {@link com.google.gerrit.testing.InMemoryDatabase.Instance}
+ * that should be used if the site is started in memory
* @param additionalArgs additional command-line arguments for the daemon program; only allowed if
* the test is not in-memory.
* @return started server.
@@ -288,6 +296,8 @@
Config baseConfig,
Path site,
@Nullable Module testSysModule,
+ @Nullable InMemoryRepositoryManager inMemoryRepoManager,
+ @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance,
String... additionalArgs)
throws Exception {
checkArgument(site != null, "site is required (even for in-memory server");
@@ -307,16 +317,24 @@
daemon.setEmailModuleForTesting(new FakeEmailSender.Module());
daemon.setAdditionalSysModuleForTesting(testSysModule);
daemon.setEnableSshd(desc.useSsh());
+ daemon.setSlave(baseConfig.getBoolean("container", "slave", false));
if (desc.memory()) {
checkArgument(additionalArgs.length == 0, "cannot pass args to in-memory server");
- return startInMemory(desc, site, baseConfig, daemon);
+ return startInMemory(
+ desc, site, baseConfig, daemon, inMemoryRepoManager, inMemoryDatabaseInstance);
}
return startOnDisk(desc, site, daemon, serverStarted, additionalArgs);
}
private static GerritServer startInMemory(
- Description desc, Path site, Config baseConfig, Daemon daemon) throws Exception {
+ Description desc,
+ Path site,
+ Config baseConfig,
+ Daemon daemon,
+ @Nullable InMemoryRepositoryManager inMemoryRepoManager,
+ @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance)
+ throws Exception {
Config cfg = desc.buildConfig(baseConfig);
mergeTestConfig(cfg);
// Set the log4j configuration to an invalid one to prevent system logs
@@ -329,7 +347,9 @@
daemon.setEnableHttpd(desc.httpd());
daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0));
daemon.setDatabaseForTesting(
- ImmutableList.<Module>of(new InMemoryTestingDatabaseModule(cfg, site)));
+ ImmutableList.<Module>of(
+ new InMemoryTestingDatabaseModule(
+ cfg, site, inMemoryRepoManager, inMemoryDatabaseInstance)));
daemon.start();
return new GerritServer(desc, null, createTestInjector(daemon), daemon, null);
}
@@ -483,6 +503,40 @@
return desc;
}
+ public static GerritServer restartAsSlave(GerritServer server) throws Exception {
+ checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
+
+ Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
+
+ Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+ cfg.setBoolean("container", null, "slave", true);
+
+ InMemoryRepositoryManager inMemoryRepoManager = null;
+ if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
+ inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
+ }
+
+ InMemoryDatabase.Instance dbInstance = null;
+ if (hasBinding(server.testInjector, InMemoryDatabase.class)) {
+ InMemoryDatabase inMemoryDatabase = server.testInjector.getInstance(InMemoryDatabase.class);
+ dbInstance = inMemoryDatabase.getDbInstance();
+ dbInstance.setKeepOpen(true);
+ }
+ try {
+ server.close();
+ server.daemon.stop();
+ return start(server.desc, cfg, site, null, inMemoryRepoManager, dbInstance);
+ } finally {
+ if (dbInstance != null) {
+ dbInstance.setKeepOpen(false);
+ }
+ }
+ }
+
+ private static boolean hasBinding(Injector injector, Class<?> clazz) {
+ return injector.getExistingBinding(Key.get(clazz)) != null;
+ }
+
@Override
public void close() throws Exception {
try {
diff --git a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 4b1211b..58dfa94 100644
--- a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -16,6 +16,7 @@
import static com.google.inject.Scopes.SINGLETON;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.metrics.DisabledMetricMaker;
@@ -48,6 +49,7 @@
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
+import com.google.inject.util.Providers;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -58,10 +60,18 @@
class InMemoryTestingDatabaseModule extends LifecycleModule {
private final Config cfg;
private final Path sitePath;
+ @Nullable private final InMemoryRepositoryManager repoManager;
+ @Nullable private final InMemoryDatabase.Instance inMemoryDatabaseInstance;
- InMemoryTestingDatabaseModule(Config cfg, Path sitePath) {
+ InMemoryTestingDatabaseModule(
+ Config cfg,
+ Path sitePath,
+ @Nullable InMemoryRepositoryManager repoManager,
+ @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance) {
this.cfg = cfg;
this.sitePath = sitePath;
+ this.repoManager = repoManager;
+ this.inMemoryDatabaseInstance = inMemoryDatabaseInstance;
makeSiteDirs(sitePath);
}
@@ -72,8 +82,12 @@
// TODO(dborowitz): Use jimfs.
bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
- bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
- bind(InMemoryRepositoryManager.class).in(SINGLETON);
+ if (repoManager != null) {
+ bind(GitRepositoryManager.class).toInstance(repoManager);
+ } else {
+ bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
+ bind(InMemoryRepositoryManager.class).in(SINGLETON);
+ }
bind(MetricMaker.class).to(DisabledMetricMaker.class);
bind(DataSourceType.class).to(InMemoryH2Type.class);
@@ -83,6 +97,7 @@
TypeLiteral<SchemaFactory<ReviewDb>> schemaFactory =
new TypeLiteral<SchemaFactory<ReviewDb>>() {};
bind(schemaFactory).to(NotesMigrationSchemaFactory.class);
+ bind(InMemoryDatabase.Instance.class).toProvider(Providers.of(inMemoryDatabaseInstance));
bind(Key.get(schemaFactory, ReviewDbFactory.class)).to(InMemoryDatabase.class);
bind(InMemoryDatabase.class).in(SINGLETON);
bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class);
@@ -132,7 +147,7 @@
@Override
public void stop() {
- mem.drop();
+ mem.getDbInstance().drop();
}
}
diff --git a/java/com/google/gerrit/acceptance/ProjectResetter.java b/java/com/google/gerrit/acceptance/ProjectResetter.java
index 637fb2a..569a0c1 100644
--- a/java/com/google/gerrit/acceptance/ProjectResetter.java
+++ b/java/com/google/gerrit/acceptance/ProjectResetter.java
@@ -120,11 +120,12 @@
}
}
- private final GitRepositoryManager repoManager;
- private final AllUsersName allUsersName;
- @Nullable private final AccountCreator accountCreator;
- @Nullable private final AccountCache accountCache;
- @Nullable private final ProjectCache projectCache;
+ @Inject private GitRepositoryManager repoManager;
+ @Inject private AllUsersName allUsersName;
+ @Inject @Nullable private AccountCreator accountCreator;
+ @Inject @Nullable private AccountCache accountCache;
+ @Inject @Nullable private ProjectCache projectCache;
+
private final Multimap<Project.NameKey, String> refsPatternByProject;
private final Multimap<Project.NameKey, RefState> savedRefStatesByProject;
diff --git a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
index 8790e78..09ffe9d 100644
--- a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
+++ b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -149,7 +149,7 @@
private GerritServer startImpl(@Nullable Module testSysModule, String... additionalArgs)
throws Exception {
return GerritServer.start(
- serverDesc, baseConfig, sitePaths.site_path, testSysModule, additionalArgs);
+ serverDesc, baseConfig, sitePaths.site_path, testSysModule, null, null, additionalArgs);
}
protected static void runGerrit(String... args) throws Exception {
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 1c0ce9c..3e7ea1b 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -222,6 +222,10 @@
httpd = enable;
}
+ public void setSlave(boolean slave) {
+ this.slave = slave;
+ }
+
@Override
public int run() throws Exception {
if (stopOnly) {
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index 0247fc3..5df0d62 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -22,6 +22,7 @@
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.Patch;
@@ -243,6 +244,34 @@
return groups;
}
+ /** Get the set of accounts whose comments have been replied to in this email. */
+ private HashSet<Account.Id> getReplyAccounts() {
+ HashSet<Account.Id> replyAccounts = new HashSet<>();
+
+ // Track visited parent UUIDs to avoid cycles.
+ HashSet<String> visitedUuids = new HashSet<>();
+
+ for (Comment comment : inlineComments) {
+ visitedUuids.add(comment.key.uuid);
+
+ // Traverse the parent relation to the top of the comment thread.
+ Comment current = comment;
+ while (current.parentUuid != null && !visitedUuids.contains(current.parentUuid)) {
+ Optional<Comment> optParent = getParent(current);
+ if (!optParent.isPresent()) {
+ // There is a parent UUID, but it cannot be loaded, break from the comment thread.
+ break;
+ }
+
+ Comment parent = optParent.get();
+ replyAccounts.add(parent.author.getId());
+ visitedUuids.add(current.parentUuid);
+ current = parent;
+ }
+ }
+ return replyAccounts;
+ }
+
private String getCommentLinePrefix(Comment comment) {
int lineNbr = comment.range == null ? comment.lineNbr : comment.range.startLine;
StringBuilder sb = new StringBuilder();
@@ -497,6 +526,10 @@
footers.add("Gerrit-Comment-Date: " + getCommentTimestamp());
footers.add("Gerrit-HasComments: " + (hasComments ? "Yes" : "No"));
footers.add("Gerrit-HasLabels: " + (labels.isEmpty() ? "No" : "Yes"));
+
+ for (Account.Id account : getReplyAccounts()) {
+ footers.add("Gerrit-Comment-In-Reply-To: " + getNameEmailFor(account));
+ }
}
private String getLine(PatchFile fileInfo, short side, int lineNbr) {
diff --git a/java/com/google/gerrit/testing/InMemoryDatabase.java b/java/com/google/gerrit/testing/InMemoryDatabase.java
index ec98e16..a3d7c17 100644
--- a/java/com/google/gerrit/testing/InMemoryDatabase.java
+++ b/java/com/google/gerrit/testing/InMemoryDatabase.java
@@ -55,26 +55,16 @@
return injector.getInstance(InMemoryDatabase.class);
}
- private static int dbCnt;
-
- private static synchronized DataSource newDataSource() throws SQLException {
- final Properties p = new Properties();
- p.setProperty("driver", org.h2.Driver.class.getName());
- p.setProperty("url", "jdbc:h2:mem:Test_" + (++dbCnt));
- return new SimpleDataSource(p);
- }
-
/** Drop the database from memory; does nothing if the instance was null. */
public static void drop(InMemoryDatabase db) {
if (db != null) {
- db.drop();
+ db.dbInstance.drop();
}
}
private final SchemaCreator schemaCreator;
+ private final Instance dbInstance;
- private Connection openHandle;
- private Database<ReviewDb> database;
private boolean created;
@Inject
@@ -97,35 +87,26 @@
}
});
this.schemaCreator = childInjector.getInstance(SchemaCreator.class);
- initDatabase();
+ Instance dbInstanceFromInjector = childInjector.getInstance(Instance.class);
+ if (dbInstanceFromInjector != null) {
+ this.dbInstance = dbInstanceFromInjector;
+ this.created = true;
+ } else {
+ this.dbInstance = new Instance();
+ }
}
InMemoryDatabase(SchemaCreator schemaCreator) throws OrmException {
this.schemaCreator = schemaCreator;
- initDatabase();
+ this.dbInstance = new Instance();
}
- private void initDatabase() throws OrmException {
- try {
- DataSource dataSource = newDataSource();
-
- // Open one connection. This will peg the database into memory
- // until someone calls drop on us, allowing subsequent connections
- // opened against the same URL to go to the same set of tables.
- //
- openHandle = dataSource.getConnection();
-
- // Build the access layer around the connection factory.
- //
- database = new Database<>(dataSource, ReviewDb.class);
-
- } catch (SQLException e) {
- throw new OrmException(e);
- }
+ public Instance getDbInstance() {
+ return dbInstance;
}
public Database<ReviewDb> getDatabase() {
- return database;
+ return dbInstance.database;
}
@Override
@@ -146,20 +127,6 @@
return this;
}
- /** Drop this database from memory so it no longer exists. */
- public void drop() {
- if (openHandle != null) {
- try {
- openHandle.close();
- } catch (SQLException e) {
- System.err.println("WARNING: Cannot close database connection");
- e.printStackTrace(System.err);
- }
- openHandle = null;
- database = null;
- }
- }
-
public SystemConfig getSystemConfig() throws OrmException {
try (ReviewDb c = open()) {
return c.systemConfig().get(new SystemConfig.Key());
@@ -175,4 +142,60 @@
public void assertSchemaVersion() throws OrmException {
assertThat(getSchemaVersion().versionNbr).isEqualTo(SchemaVersion.getBinaryVersion());
}
+
+ public static class Instance {
+ private static int dbCnt;
+
+ private Connection openHandle;
+ private Database<ReviewDb> database;
+ private boolean keepOpen;
+
+ private static synchronized DataSource newDataSource() throws SQLException {
+ final Properties p = new Properties();
+ p.setProperty("driver", org.h2.Driver.class.getName());
+ p.setProperty("url", "jdbc:h2:mem:Test_" + (++dbCnt));
+ return new SimpleDataSource(p);
+ }
+
+ private Instance() throws OrmException {
+ try {
+ DataSource dataSource = newDataSource();
+
+ // Open one connection. This will peg the database into memory
+ // until someone calls drop on us, allowing subsequent connections
+ // opened against the same URL to go to the same set of tables.
+ //
+ openHandle = dataSource.getConnection();
+
+ // Build the access layer around the connection factory.
+ //
+ database = new Database<>(dataSource, ReviewDb.class);
+
+ } catch (SQLException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ public void setKeepOpen(boolean keepOpen) {
+ this.keepOpen = keepOpen;
+ }
+
+ /** Drop this database from memory so it no longer exists. */
+ public void drop() {
+ if (keepOpen) {
+ return;
+ }
+
+ if (openHandle != null) {
+ try {
+ openHandle.close();
+ } catch (SQLException e) {
+ System.err.println("WARNING: Cannot close database connection");
+ e.printStackTrace(System.err);
+ }
+ openHandle = null;
+ database = null;
+ }
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 869a4d4..39d927e 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -14,6 +14,7 @@
package com.google.gerrit.acceptance.api.group;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
@@ -1223,6 +1224,25 @@
}
}
+ @Test
+ @Sandboxed
+ public void groupsOfUserCanBeListedInSlaveMode() throws Exception {
+ // TODO(aliceks): Remove this line when we have a group index in slave mode.
+ assume().that(readGroupsFromNoteDb()).isFalse();
+
+ GroupInput groupInput = new GroupInput();
+ groupInput.name = name("contributors");
+ groupInput.members = ImmutableList.of(user.username);
+ gApi.groups().create(groupInput).get();
+ restartAsSlave();
+
+ setApiUser(user);
+ List<GroupInfo> groups = gApi.groups().list().withUser(user.username).get();
+ ImmutableList<String> groupNames =
+ groups.stream().map(group -> group.name).collect(toImmutableList());
+ assertThat(groupNames).contains(groupInput.name);
+ }
+
private void assertStaleGroupAndReindex(AccountGroup.UUID groupUuid) throws IOException {
// Evict group from cache to be sure that we use the index state for staleness checks. This has
// to happen directly on the groupsByUUID cache because GroupsCacheImpl triggers a reindex for
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index e1c7b6b..ea42f47 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -22,9 +22,14 @@
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.sshd.Commands;
+import com.jcraft.jsch.JSchException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
@@ -36,44 +41,59 @@
public class SshCommandsIT extends AbstractDaemonTest {
private static final Logger log = LoggerFactory.getLogger(SshCommandsIT.class);
- //TODO: It would be better to dynamically generate this list
- private static final Map<String, List<String>> COMMANDS =
+ // TODO: It would be better to dynamically generate these lists
+ private static final List<String> COMMON_ROOT_COMMANDS =
+ ImmutableList.of(
+ "apropos",
+ "close-connection",
+ "flush-caches",
+ "gc",
+ "logging",
+ "ls-groups",
+ "ls-members",
+ "ls-projects",
+ "ls-user-refs",
+ "plugin",
+ "show-caches",
+ "show-connections",
+ "show-queue",
+ "version");
+
+ private static final List<String> MASTER_ONLY_ROOT_COMMANDS =
+ ImmutableList.of(
+ "ban-commit",
+ "create-account",
+ "create-branch",
+ "create-group",
+ "create-project",
+ "gsql",
+ "index",
+ "query",
+ "receive-pack",
+ "rename-group",
+ "review",
+ "set-account",
+ "set-head",
+ "set-members",
+ "set-project",
+ "set-project-parent",
+ "set-reviewers",
+ "stream-events",
+ "test-submit");
+
+ private static final Map<String, List<String>> MASTER_COMMANDS =
ImmutableMap.of(
Commands.ROOT,
- ImmutableList.of(
- "apropos",
- "ban-commit",
- "close-connection",
- "create-account",
- "create-branch",
- "create-group",
- "create-project",
- "flush-caches",
- "gc",
- "gsql",
- "index",
- "logging",
- "ls-groups",
- "ls-members",
- "ls-projects",
- "ls-user-refs",
- "plugin",
- "query",
- "receive-pack",
- "rename-group",
- "review",
- "set-account",
- "set-head",
- "set-members",
- "set-project",
- "set-project-parent",
- "set-reviewers",
- "show-caches",
- "show-connections",
- "show-queue",
- "stream-events",
- "test-submit",
- "version"),
+ ImmutableList.copyOf(
+ new ArrayList<String>() {
+ private static final long serialVersionUID = 1L;
+
+ {
+ addAll(COMMON_ROOT_COMMANDS);
+ addAll(MASTER_ONLY_ROOT_COMMANDS);
+ Collections.sort(this);
+ }
+ }),
"index",
ImmutableList.of("activate", "changes", "project", "start"),
"plugin",
@@ -81,13 +101,29 @@
"test-submit",
ImmutableList.of("rule", "type"));
+ private static final Map<String, List<String>> SLAVE_COMMANDS =
+ ImmutableMap.of(
+ Commands.ROOT,
+ COMMON_ROOT_COMMANDS,
+ "plugin",
+ ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"));
+
@Test
+ @Sandboxed
public void sshCommandCanBeExecuted() throws Exception {
// Access Database capability is required to run the "gerrit gsql" command
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
- for (String root : COMMANDS.keySet()) {
- for (String command : COMMANDS.get(root)) {
+ testCommandExecution(MASTER_COMMANDS);
+
+ restartAsSlave();
+ testCommandExecution(SLAVE_COMMANDS);
+ }
+
+ private void testCommandExecution(Map<String, List<String>> commands)
+ throws JSchException, IOException {
+ for (String root : commands.keySet()) {
+ for (String command : commands.get(root)) {
// We can't assert that adminSshSession.hasError() is false, because using the --help
// option causes the usage info to be written to stderr. Instead, we assert on the
// content of the stderr, which will always start with "gerrit command" when the --help
@@ -109,4 +145,50 @@
assertThat(adminSshSession.getError())
.startsWith("fatal: gerrit: non-existing-command: not found");
}
+
+ @Test
+ @Sandboxed
+ public void listCommands() throws Exception {
+ adminSshSession.exec("gerrit --help");
+ List<String> commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
+ assertThat(commands).containsExactlyElementsIn(MASTER_COMMANDS.get(Commands.ROOT)).inOrder();
+
+ restartAsSlave();
+ adminSshSession.exec("gerrit --help");
+ commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
+ assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get(Commands.ROOT)).inOrder();
+ }
+
+ private List<String> parseCommandsFromGerritHelpText(String helpText) {
+ List<String> commands = new ArrayList<>();
+
+ String[] lines = helpText.split("\\n");
+
+ // Skip all lines including the line starting with "Available commands"
+ int row = 0;
+ do {
+ row++;
+ } while (row < lines.length && !lines[row - 1].startsWith("Available commands"));
+
+ // Skip all empty lines
+ while (lines[row].trim().isEmpty()) {
+ row++;
+ }
+
+ // Parse commands from all lines that are indented (start with a space)
+ while (row < lines.length && lines[row].startsWith(" ")) {
+ String line = lines[row].trim();
+ // Abort on empty line
+ if (line.isEmpty()) {
+ break;
+ }
+
+ // Cut off command description if there is one
+ int endOfCommand = line.indexOf(' ');
+ commands.add(endOfCommand > 0 ? line.substring(0, line.indexOf(' ')) : line);
+ row++;
+ }
+
+ return commands;
+ }
}
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 6442943..bac9b33 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 6442943d6a6de21b7d6d25b3fad2753a3c30d2d8
+Subproject commit bac9b336a326585accfc9a9eda7b5340e62783ac
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
index c13d69f..da9cae6 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
@@ -82,10 +82,10 @@
</span>
</section>
<section>
- <span class="title">Line numbers</span>
+ <span class="title">Hide line numbers</span>
<span class="value">
<input
- id="showLineNumbers"
+ id="hideLineNumbers"
type="checkbox"
checked$="[[editPrefs.hide_line_numbers]]"
on-change="_handleLineNumbersChanged">
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
index 0a791fd..5a97363 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
@@ -50,7 +50,7 @@
},
_handleLineNumbersChanged() {
- this.set('editPrefs.hide_line_numbers', !this.$.showLineNumbers.checked);
+ this.set('editPrefs.hide_line_numbers', this.$.hideLineNumbers.checked);
this._handleEditPrefsChanged();
},
@@ -75,14 +75,10 @@
this._handleEditPrefsChanged();
},
- _handleShowBaseVersionChanged() {
- this.set('editPrefs.show_base', this.$.showShowBaseVersion.checked);
- this._handleEditPrefsChanged();
- },
-
save() {
- return this.$.restAPI.saveEditPreferences(this.editPrefs)
- .then(() => { this.hasUnsavedChanges = false; });
+ return this.$.restAPI.saveEditPreferences(this.editPrefs).then(res => {
+ this.hasUnsavedChanges = false;
+ });
},
});
})();
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
index 118ee4f..a0f95c1 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -93,7 +93,7 @@
.firstElementChild.checked, editPreferences.syntax_highlighting);
assert.equal(valueOf('Show tabs', 'editPreferences')
.firstElementChild.checked, editPreferences.show_tabs);
- assert.equal(valueOf('Line numbers', 'editPreferences')
+ assert.equal(valueOf('Hide line numbers', 'editPreferences')
.firstElementChild.checked, editPreferences.hide_line_numbers);
assert.equal(valueOf('Match brackets', 'editPreferences')
.firstElementChild.checked, editPreferences.match_brackets);
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index 38dfbe5..3578173 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -25,7 +25,7 @@
# post process the XML into our favorite format.
native.genrule(
name = "gen_license_txt_" + name,
- cmd = "python $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
+ cmd = "python2 $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
outs = [ name + ".txt" ],
tools = tools,
**kwargs
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 448d940..9f8b4b7 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -277,7 +277,7 @@
doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
try:
- ext_location = retrieve_ext_location()
+ ext_location = retrieve_ext_location().decode("utf-8")
gen_project(args.project_name)
gen_classpath(ext_location)
gen_factorypath(ext_location)
diff --git a/tools/js/bowerutil.py b/tools/js/bowerutil.py
index 8e8e835..c2e11cd 100644
--- a/tools/js/bowerutil.py
+++ b/tools/js/bowerutil.py
@@ -40,7 +40,7 @@
if f == '.bower.json':
continue
p = os.path.join(root, f)
- hash_obj.update(p[len(path)+1:])
- hash_obj.update(open(p).read())
+ hash_obj.update(p[len(path)+1:].encode("utf-8"))
+ hash_obj.update(open(p, "rb").read())
return hash_obj
diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py
index f5b7bf5..1c8d45e 100755
--- a/tools/js/download_bower.py
+++ b/tools/js/download_bower.py
@@ -68,7 +68,7 @@
deps = info.get('dependencies')
if deps:
with open(os.path.join('.bowerrc'), 'w') as f:
- json.dump({'ignoredDependencies': deps.keys()}, f)
+ json.dump({'ignoredDependencies': list(deps.keys())}, f)
def cache_entry(name, package, version, sha1):
diff --git a/tools/js/npm_pack.py b/tools/js/npm_pack.py
index 9e8482e..de45083 100755
--- a/tools/js/npm_pack.py
+++ b/tools/js/npm_pack.py
@@ -42,7 +42,7 @@
def bundle_dependencies():
with open('package.json') as f:
package = json.load(f)
- package['bundledDependencies'] = package['dependencies'].keys()
+ package['bundledDependencies'] = list(package['dependencies'].keys())
with open('package.json', 'w') as f:
json.dump(package, f)