Merge "Support reading default project.config for All-Projects from etc"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index e13a9b9..6362597 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -4959,6 +4959,38 @@
Assuming that the server is started on `Mon 07:00` then this yields the
first run on Tuesday at 06:00 and a repetition interval of 1 day.
+[[All-Projects-project.config]]
+== File `etc/All-Projects/project.config`
+
+The optional file `'$site_path'/etc/All-Projects/project.config` provides
+defaults for configuration read from
+link:config-project-config.html[`project.config`] in the
+`All-Projects` repo. Unlike `gerrit.config`, this file contains project-type
+configuration rather than server-type configuration.
+
+Most administrators will not need this file, and should instead make commits to
+`All-Projects` to modify global config. However, a separate file can be useful
+when managing multiple Gerrit servers, since pushing changes to defaults using
+Puppet or a similar tool can be easier than scripting git updates to
+`All-Projects`.
+
+The contents of the file are loaded each time the `All-Projects` project is
+reloaded. Updating the file requires either evicting the project cache or
+restarting the server.
+
+Caveats:
+
+* The path from which the file is read corresponds to the name of the repo,
+ which is link:#gerrit.allProjects[configurable].
+* Although the file lives in a directory that shares a name with a repository,
+ this directory is not a Git repository.
+* Only the file `project.config` is read from this directory to provide
+ defaults; any other files in this directory, such as `rules.pl`, are ignored.
+ (This behavior may change in the future.)
+* Group names listed in the access config in this file are resolved to UUIDs
+ using the `groups` file in the repository, not in the config directory. As a
+ result, setting ACLs in this file is not recommended.
+
[[secure.config]]
== File `etc/secure.config`
diff --git a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index 9fd3f16..20e7ba2 100644
--- a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
+++ b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -15,8 +15,10 @@
package com.google.gerrit.pgm.init.api;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.project.GroupList;
import com.google.gerrit.server.project.ProjectConfig;
@@ -27,16 +29,21 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.StoredConfig;
public class AllProjectsConfig extends VersionedMetaDataOnInit {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ @Nullable private final StoredConfig baseConfig;
private Config cfg;
private GroupList groupList;
@Inject
AllProjectsConfig(AllProjectsNameOnInitProvider allProjects, SitePaths site, InitFlags flags) {
super(flags, site, allProjects.get(), RefNames.REFS_CONFIG);
+ this.baseConfig =
+ ProjectConfig.Factory.getBaseConfig(
+ site, new AllProjectsName(allProjects.get()), new Project.NameKey(allProjects.get()));
}
public Config getConfig() {
@@ -55,8 +62,11 @@
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
+ if (baseConfig != null) {
+ baseConfig.load();
+ }
groupList = readGroupList();
- cfg = readConfig(ProjectConfig.PROJECT_CONFIG);
+ cfg = readConfig(ProjectConfig.PROJECT_CONFIG, baseConfig);
}
private GroupList readGroupList() throws IOException {
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index 196267e..b33aa3c 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -462,7 +462,12 @@
}
protected Config readConfig(String fileName) throws IOException, ConfigInvalidException {
- Config rc = new Config();
+ return readConfig(fileName, null);
+ }
+
+ protected Config readConfig(String fileName, Config baseConfig)
+ throws IOException, ConfigInvalidException {
+ Config rc = new Config(baseConfig);
String text = readUTF8(fileName);
if (!text.isEmpty()) {
try {
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 74c0f3b..b16b076 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -52,13 +52,16 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.BranchOrderSection;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
+import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
@@ -83,8 +86,11 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.util.FS;
public class ProjectConfig extends VersionedMetaData implements ValidationError.Sink {
public static final String COMMENTLINK = "commentlink";
@@ -173,8 +179,28 @@
// ProjectCache, so this would retain lots more memory.
@Singleton
public static class Factory {
+ @Nullable
+ public static StoredConfig getBaseConfig(
+ SitePaths sitePaths, AllProjectsName allProjects, Project.NameKey projectName) {
+ return projectName.equals(allProjects)
+ // Delay loading till onLoad method.
+ ? new FileBasedConfig(
+ sitePaths.etc_dir.resolve(allProjects.get()).resolve(PROJECT_CONFIG).toFile(),
+ FS.DETECTED)
+ : null;
+ }
+
+ private final SitePaths sitePaths;
+ private final AllProjectsName allProjects;
+
+ @Inject
+ Factory(SitePaths sitePaths, AllProjectsName allProjects) {
+ this.sitePaths = sitePaths;
+ this.allProjects = allProjects;
+ }
+
public ProjectConfig create(Project.NameKey projectName) {
- return new ProjectConfig(projectName);
+ return new ProjectConfig(projectName, getBaseConfig(sitePaths, allProjects, projectName));
}
public ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException {
@@ -191,6 +217,8 @@
}
}
+ private final StoredConfig baseConfig;
+
private Project project;
private AccountsSection accountsSection;
private GroupList groupList;
@@ -253,8 +281,9 @@
commentLinkSections.add(commentLink);
}
- private ProjectConfig(Project.NameKey projectName) {
+ private ProjectConfig(Project.NameKey projectName, @Nullable StoredConfig baseConfig) {
this.projectName = projectName;
+ this.baseConfig = baseConfig;
}
public void load(Repository repo) throws IOException, ConfigInvalidException {
@@ -516,11 +545,14 @@
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
+ if (baseConfig != null) {
+ baseConfig.load();
+ }
readGroupList();
groupsByName = mapGroupReferences();
rulesId = getObjectId("rules.pl");
- Config rc = readConfig(PROJECT_CONFIG);
+ Config rc = readConfig(PROJECT_CONFIG, baseConfig);
project = new Project(projectName);
Project p = project;
diff --git a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
index c25b846..483e363 100644
--- a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
+++ b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
@@ -17,12 +17,17 @@
import static com.google.gerrit.server.project.ProjectConfig.ACCESS;
import static java.util.stream.Collectors.toList;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@@ -31,22 +36,38 @@
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.StoredConfig;
public class ProjectConfigSchemaUpdate extends VersionedMetaData {
+ public static class Factory {
+ private final SitePaths sitePaths;
+ private final AllProjectsName allProjectsName;
+
+ @Inject
+ Factory(SitePaths sitePaths, AllProjectsName allProjectsName) {
+ this.sitePaths = sitePaths;
+ this.allProjectsName = allProjectsName;
+ }
+
+ ProjectConfigSchemaUpdate read(MetaDataUpdate update)
+ throws IOException, ConfigInvalidException {
+ ProjectConfigSchemaUpdate r =
+ new ProjectConfigSchemaUpdate(
+ update,
+ ProjectConfig.Factory.getBaseConfig(sitePaths, allProjectsName, allProjectsName));
+ r.load(update);
+ return r;
+ }
+ }
private final MetaDataUpdate update;
+ @Nullable private final StoredConfig baseConfig;
private Config config;
private boolean updated;
- public static ProjectConfigSchemaUpdate read(MetaDataUpdate update)
- throws IOException, ConfigInvalidException {
- ProjectConfigSchemaUpdate r = new ProjectConfigSchemaUpdate(update);
- r.load(update);
- return r;
- }
-
- private ProjectConfigSchemaUpdate(MetaDataUpdate update) {
+ private ProjectConfigSchemaUpdate(MetaDataUpdate update, @Nullable StoredConfig baseConfig) {
this.update = update;
+ this.baseConfig = baseConfig;
}
@Override
@@ -56,7 +77,15 @@
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
- config = readConfig(ProjectConfig.PROJECT_CONFIG);
+ if (baseConfig != null) {
+ baseConfig.load();
+ }
+ config = readConfig(ProjectConfig.PROJECT_CONFIG, baseConfig);
+ }
+
+ @VisibleForTesting
+ Config getConfig() {
+ return config;
}
public void removeForceFromPermission(String name) {
diff --git a/java/com/google/gerrit/server/schema/Schema_130.java b/java/com/google/gerrit/server/schema/Schema_130.java
index 0c9d79d..e550121 100644
--- a/java/com/google/gerrit/server/schema/Schema_130.java
+++ b/java/com/google/gerrit/server/schema/Schema_130.java
@@ -41,15 +41,18 @@
private final GitRepositoryManager repoManager;
private final PersonIdent serverUser;
+ private final ProjectConfigSchemaUpdate.Factory projectConfigSchemaUpdateFactory;
@Inject
Schema_130(
Provider<Schema_129> prior,
GitRepositoryManager repoManager,
- @GerritPersonIdent PersonIdent serverUser) {
+ @GerritPersonIdent PersonIdent serverUser,
+ ProjectConfigSchemaUpdate.Factory projectConfigSchemaUpdateFactory) {
super(prior);
this.repoManager = repoManager;
this.serverUser = serverUser;
+ this.projectConfigSchemaUpdateFactory = projectConfigSchemaUpdateFactory;
}
@Override
@@ -60,7 +63,7 @@
for (Project.NameKey projectName : repoList) {
try (Repository git = repoManager.openRepository(projectName);
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, projectName, git)) {
- ProjectConfigSchemaUpdate cfg = ProjectConfigSchemaUpdate.read(md);
+ ProjectConfigSchemaUpdate cfg = projectConfigSchemaUpdateFactory.read(md);
cfg.removeForceFromPermission("pushTag");
if (cfg.isUpdated()) {
repoUpgraded.add(projectName);
diff --git a/javatests/com/google/gerrit/pgm/BUILD b/javatests/com/google/gerrit/pgm/BUILD
index 01ebb0e..8e3b71d 100644
--- a/javatests/com/google/gerrit/pgm/BUILD
+++ b/javatests/com/google/gerrit/pgm/BUILD
@@ -12,6 +12,7 @@
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/securestore/testing",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
"//lib:junit",
"//lib/easymock",
diff --git a/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
new file mode 100644
index 0000000..68b4a8e
--- /dev/null
+++ b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
@@ -0,0 +1,117 @@
+// Copyright (C) 2018 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.pgm.init.api;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.replay;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.securestore.testing.InMemorySecureStore;
+import com.google.gerrit.testing.TempFileUtil;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AllProjectsConfigTest {
+ private static final String ALL_PROJECTS = "All-The-Projects";
+
+ private SitePaths sitePaths;
+ private AllProjectsConfig allProjectsConfig;
+ private File allProjectsRepoFile;
+
+ @Before
+ public void setUp() throws Exception {
+ sitePaths = new SitePaths(TempFileUtil.createTempDirectory().toPath());
+ Files.createDirectories(sitePaths.etc_dir);
+
+ Path gitPath = sitePaths.resolve("git");
+
+ StoredConfig gerritConfig =
+ new FileBasedConfig(
+ sitePaths.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
+ gerritConfig.load();
+ gerritConfig.setString("gerrit", null, "basePath", gitPath.toAbsolutePath().toString());
+ gerritConfig.setString("gerrit", null, "allProjects", ALL_PROJECTS);
+ gerritConfig.save();
+
+ Files.createDirectories(sitePaths.resolve("git"));
+ allProjectsRepoFile = gitPath.resolve("All-The-Projects.git").toFile();
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ repo.create(true);
+ }
+
+ InMemorySecureStore secureStore = new InMemorySecureStore();
+ InitFlags flags = new InitFlags(sitePaths, secureStore, ImmutableList.of(), false);
+ ConsoleUI ui = createStrictMock(ConsoleUI.class);
+ replay(ui);
+ Section.Factory sections =
+ (name, subsection) -> new Section(flags, sitePaths, secureStore, ui, name, subsection);
+ allProjectsConfig =
+ new AllProjectsConfig(new AllProjectsNameOnInitProvider(sections), sitePaths, flags);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TempFileUtil.cleanup();
+ }
+
+ @Test
+ public void noBaseConfig() throws Exception {
+ assertThat(getConfig().getString("foo", null, "bar")).isNull();
+
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ TestRepository<?> tr = new TestRepository<>(repo);
+ tr.branch("refs/meta/config").commit().add("project.config", "[foo]\nbar = baz").create();
+ }
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("baz");
+ }
+
+ @Test
+ public void baseConfig() throws Exception {
+ assertThat(getConfig().getString("foo", null, "bar")).isNull();
+
+ Path baseConfigPath = sitePaths.etc_dir.resolve(ALL_PROJECTS).resolve("project.config");
+ Files.createDirectories(baseConfigPath.getParent());
+ Files.write(baseConfigPath, ImmutableList.of("[foo]", "bar = base"));
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("base");
+
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ TestRepository<?> tr = new TestRepository<>(repo);
+ tr.branch("refs/meta/config").commit().add("project.config", "[foo]\nbar = baz").create();
+ }
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("baz");
+ }
+
+ private Config getConfig() throws IOException, ConfigInvalidException {
+ return allProjectsConfig.load().getConfig();
+ }
+}
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 764d49a..a1e0566 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -16,7 +16,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.reviewdb.client.BooleanProjectConfig.REQUIRE_CHANGE_ID;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
@@ -24,16 +26,22 @@
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.testing.TempFileUtil;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
@@ -50,6 +58,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.util.RawParseUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -74,21 +83,31 @@
+ !LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE
+ "\n";
+ private static final AllProjectsName ALL_PROJECTS = new AllProjectsName("All-The-Projects");
+
private final GroupReference developers =
new GroupReference(new AccountGroup.UUID("X"), "Developers");
private final GroupReference staff = new GroupReference(new AccountGroup.UUID("Y"), "Staff");
+ private SitePaths sitePaths;
private ProjectConfig.Factory factory;
private Repository db;
private TestRepository<?> tr;
@Before
public void setUp() throws Exception {
- factory = new ProjectConfig.Factory();
+ sitePaths = new SitePaths(TempFileUtil.createTempDirectory().toPath());
+ Files.createDirectories(sitePaths.etc_dir);
+ factory = new ProjectConfig.Factory(sitePaths, ALL_PROJECTS);
db = new InMemoryRepository(new DfsRepositoryDescription("repo"));
tr = new TestRepository<>(db);
}
+ @After
+ public void tearDown() throws Exception {
+ TempFileUtil.cleanup();
+ }
+
@Test
public void readConfig() throws Exception {
RevCommit rev =
@@ -593,6 +612,44 @@
+ "commentlink.bugzilla must have either link or html"));
}
+ @Test
+ public void readAllProjectsBaseConfigFromSitePaths() throws Exception {
+ ProjectConfig cfg = factory.create(ALL_PROJECTS);
+ cfg.load(db);
+ assertThat(cfg.getProject().getBooleanConfig(REQUIRE_CHANGE_ID))
+ .isEqualTo(InheritableBoolean.INHERIT);
+
+ writeDefaultAllProjectsConfig("[receive]", "requireChangeId = false");
+
+ cfg.load(db);
+ assertThat(cfg.getProject().getBooleanConfig(REQUIRE_CHANGE_ID))
+ .isEqualTo(InheritableBoolean.FALSE);
+ }
+
+ @Test
+ public void readOtherProjectIgnoresAllProjectsBaseConfig() throws Exception {
+ ProjectConfig cfg = factory.create(new Project.NameKey("test"));
+ cfg.load(db);
+ assertThat(cfg.getProject().getBooleanConfig(REQUIRE_CHANGE_ID))
+ .isEqualTo(InheritableBoolean.INHERIT);
+
+ writeDefaultAllProjectsConfig("[receive]", "requireChangeId = false");
+
+ cfg.load(db);
+ // If we went through ProjectState, then this would return FALSE, since project.config for
+ // All-Projects would inherit from all_projects.config, and this project would inherit from
+ // All-Projects. But in ProjectConfig itself, there is no inheritance from All-Projects, so this
+ // continues to return the default.
+ assertThat(cfg.getProject().getBooleanConfig(REQUIRE_CHANGE_ID))
+ .isEqualTo(InheritableBoolean.INHERIT);
+ }
+
+ private Path writeDefaultAllProjectsConfig(String... lines) throws IOException {
+ Path dir = sitePaths.etc_dir.resolve(ALL_PROJECTS.get());
+ Files.createDirectories(dir);
+ return Files.write(dir.resolve("project.config"), ImmutableList.copyOf(lines));
+ }
+
private ProjectConfig read(RevCommit rev) throws IOException, ConfigInvalidException {
ProjectConfig cfg = factory.create(new Project.NameKey("test"));
cfg.load(db, rev);
diff --git a/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
new file mode 100644
index 0000000..de825e6
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
@@ -0,0 +1,113 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.testing.TempFileUtil;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ProjectConfigSchemaUpdateTest {
+ private static final String ALL_PROJECTS = "All-The-Projects";
+
+ private SitePaths sitePaths;
+ private ProjectConfigSchemaUpdate.Factory factory;
+ private File allProjectsRepoFile;
+
+ @Before
+ public void setUp() throws Exception {
+ sitePaths = new SitePaths(TempFileUtil.createTempDirectory().toPath());
+ Files.createDirectories(sitePaths.etc_dir);
+
+ Path gitPath = sitePaths.resolve("git");
+
+ StoredConfig gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.DETECTED);
+ gerritConfig.load();
+ gerritConfig.setString("gerrit", null, "basePath", gitPath.toAbsolutePath().toString());
+ gerritConfig.setString("gerrit", null, "allProjects", ALL_PROJECTS);
+ gerritConfig.save();
+
+ Files.createDirectories(sitePaths.resolve("git"));
+ allProjectsRepoFile = gitPath.resolve("All-The-Projects.git").toFile();
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ repo.create(true);
+ }
+
+ factory = new ProjectConfigSchemaUpdate.Factory(sitePaths, new AllProjectsName(ALL_PROJECTS));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TempFileUtil.cleanup();
+ }
+
+ @Test
+ public void noBaseConfig() throws Exception {
+ assertThat(getConfig().getString("foo", null, "bar")).isNull();
+
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ TestRepository<?> tr = new TestRepository<>(repo);
+ tr.branch("refs/meta/config").commit().add("project.config", "[foo]\nbar = baz").create();
+ }
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("baz");
+ }
+
+ @Test
+ public void baseConfig() throws Exception {
+ assertThat(getConfig().getString("foo", null, "bar")).isNull();
+
+ Path baseConfigPath = sitePaths.etc_dir.resolve(ALL_PROJECTS).resolve("project.config");
+ Files.createDirectories(baseConfigPath.getParent());
+ Files.write(baseConfigPath, ImmutableList.of("[foo]", "bar = base"));
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("base");
+
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ TestRepository<?> tr = new TestRepository<>(repo);
+ tr.branch("refs/meta/config").commit().add("project.config", "[foo]\nbar = baz").create();
+ }
+
+ assertThat(getConfig().getString("foo", null, "bar")).isEqualTo("baz");
+ }
+
+ private Config getConfig() throws Exception {
+ try (Repository repo = new FileRepository(allProjectsRepoFile)) {
+ return factory
+ .read(
+ new MetaDataUpdate(
+ GitReferenceUpdated.DISABLED, new Project.NameKey(ALL_PROJECTS), repo, null))
+ .getConfig();
+ }
+ }
+}