From b432980c97e4b57d45cc32c3f368b1ce69c8540c Mon Sep 17 00:00:00 2001 From: Martin Wallgren Date: Thu, 8 Mar 2018 14:13:33 +0100 Subject: [PATCH] Merge parent config values with child values ProjectLevelConfig.getWithInheritance returns a config where values defined in the child config will replace any matching value in the parent config. This change adds a boolean merge parameter to ProjectLevelConfig.getWithInheritance that will allow the caller to generate a config where the values from the parent config are merged with the values from the child project. Bug: Issue 8468 Change-Id: I5edacfacb37b7ee525b7c4e5340b97a62c4dc243 --- .../rest/project/ProjectLevelConfigIT.java | 64 +++++++++++++++++++ .../server/config/PluginConfigFactory.java | 52 ++++++++++++++- .../gerrit/server/git/ProjectLevelConfig.java | 43 ++++++++++++- 3 files changed, 155 insertions(+), 4 deletions(-) diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java index c1bbf2e553..c78b47bbfe 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java @@ -22,6 +22,7 @@ import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.project.ProjectState; +import java.util.Arrays; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.junit.Before; @@ -114,4 +115,67 @@ public class ProjectLevelConfigIT extends AbstractDaemonTest { assertThat(state.getConfig(configName).get().toText()).isEqualTo(cfg.toText()); } + + @Test + public void withMergedInheritance() throws Exception { + String configName = "test.config"; + + Config parentCfg = new Config(); + parentCfg.setString("s1", null, "k1", "parentValue1"); + parentCfg.setString("s1", null, "k2", "parentValue2"); + parentCfg.setString("s2", "ss", "k3", "parentValue3"); + parentCfg.setString("s2", "ss", "k4", "parentValue4"); + + pushFactory + .create( + db, + admin.getIdent(), + testRepo, + "Create Project Level Config", + configName, + parentCfg.toText()) + .to(RefNames.REFS_CONFIG) + .assertOkStatus(); + + Project.NameKey childProject = createProject("child", project); + TestRepository childTestRepo = cloneProject(childProject); + fetch(childTestRepo, RefNames.REFS_CONFIG + ":refs/heads/config"); + childTestRepo.reset("refs/heads/config"); + + Config cfg = new Config(); + cfg.setString("s1", null, "k1", "parentValue1"); + cfg.setString("s1", null, "k2", "parentValue2"); + cfg.setString("s2", "ss", "k3", "parentValue3"); + cfg.setString("s2", "ss", "k4", "parentValue4"); + cfg.setString("s1", null, "k1", "childValue1"); + cfg.setString("s2", "ss", "k3", "childValue2"); + cfg.setString("s3", null, "k5", "childValue3"); + cfg.setString("s3", "ss", "k6", "childValue4"); + + pushFactory + .create( + db, + admin.getIdent(), + childTestRepo, + "Create Project Level Config", + configName, + cfg.toText()) + .to(RefNames.REFS_CONFIG) + .assertOkStatus(); + + ProjectState state = projectCache.get(childProject); + + Config expectedCfg = new Config(); + expectedCfg.setStringList("s1", null, "k1", Arrays.asList("childValue1", "parentValue1")); + expectedCfg.setString("s1", null, "k2", "parentValue2"); + expectedCfg.setStringList("s2", "ss", "k3", Arrays.asList("childValue2", "parentValue3")); + expectedCfg.setString("s2", "ss", "k4", "parentValue4"); + expectedCfg.setString("s3", null, "k5", "childValue3"); + expectedCfg.setString("s3", "ss", "k6", "childValue4"); + + assertThat(state.getConfig(configName).getWithInheritance(true).toText()) + .isEqualTo(expectedCfg.toText()); + + assertThat(state.getConfig(configName).get().toText()).isEqualTo(cfg.toText()); + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java index 6310c0881a..09f2837c8b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java @@ -288,7 +288,32 @@ public class PluginConfigFactory implements ReloadPluginListener { */ public Config getProjectPluginConfigWithInheritance( Project.NameKey projectName, String pluginName) throws NoSuchProjectException { - return getPluginConfig(projectName, pluginName).getWithInheritance(); + return getPluginConfig(projectName, pluginName).getWithInheritance(false); + } + + /** + * Returns the configuration for the specified plugin that is stored in the '{@code + * .config}' file in the 'refs/meta/config' branch of the specified project. + * Parameters from the '{@code .config}' of the parent project are appended to this + * project's '{@code .config}' files. + * + *

E.g.: child project: [mySection "mySubsection"] myKey = childValue + * + *

parent project: [mySection "mySubsection"] myKey = parentValue anotherKey = someValue + * + *

return: [mySection "mySubsection"] myKey = childValue myKey = parentValue anotherKey = + * someValue + * + * @param projectName the name of the project for which the plugin configuration should be + * returned + * @param pluginName the name of the plugin for which the configuration should be returned + * @return the plugin configuration from the '{@code .config}' file of the specified + * project with parameters from the parent projects appended to the project values + * @throws NoSuchProjectException thrown if the specified project does not exist + */ + public Config getProjectPluginConfigWithMergedInheritance( + Project.NameKey projectName, String pluginName) throws NoSuchProjectException { + return getPluginConfig(projectName, pluginName).getWithInheritance(true); } /** @@ -310,7 +335,30 @@ public class PluginConfigFactory implements ReloadPluginListener { */ public Config getProjectPluginConfigWithInheritance( ProjectState projectState, String pluginName) { - return projectState.getConfig(pluginName + EXTENSION).getWithInheritance(); + return projectState.getConfig(pluginName + EXTENSION).getWithInheritance(false); + } + + /** + * Returns the configuration for the specified plugin that is stored in the '{@code + * .config}' file in the 'refs/meta/config' branch of the specified project. + * Parameters from the '{@code .config}' of the parent project are appended to this + * project's '{@code .config}' files. + * + *

E.g.: child project: [mySection "mySubsection"] myKey = childValue + * + *

parent project: [mySection "mySubsection"] myKey = parentValue anotherKey = someValue + * + *

return: [mySection "mySubsection"] myKey = childValue myKey = parentValue anotherKey = + * someValue + * + * @param projectState the project for which the plugin configuration should be returned + * @param pluginName the name of the plugin for which the configuration should be returned + * @return the plugin configuration from the '{@code .config}' file of the specified + * project with inheriting non-set parameters from the parent projects + */ + public Config getProjectPluginConfigWithMergedInheritance( + ProjectState projectState, String pluginName) { + return projectState.getConfig(pluginName + EXTENSION).getWithInheritance(true); } private ProjectLevelConfig getPluginConfig(Project.NameKey projectName, String pluginName) diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java index 18dcef88c5..dd18e3b086 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java @@ -15,11 +15,14 @@ package com.google.gerrit.server.git; import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.project.ProjectState; import java.io.IOException; import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Config; @@ -53,6 +56,22 @@ public class ProjectLevelConfig extends VersionedMetaData { } public Config getWithInheritance() { + return getWithInheritance(false); + } + + /** + * Get a Config that includes the values from all parent projects. + * + *

Merging means that matching sections/subsection will be merged to include the values from + * both parent and child config. + * + *

No merging means that matching sections/subsections in the child project will replace the + * corresponding value from the parent. + * + * @param merge whether to merge parent values with child values or not. + * @return a combined config. + */ + public Config getWithInheritance(boolean merge) { Config cfgWithInheritance = new Config(); try { cfgWithInheritance.fromText(get().toText()); @@ -65,21 +84,41 @@ public class ProjectLevelConfig extends VersionedMetaData { for (String section : parentCfg.getSections()) { Set allNames = get().getNames(section); for (String name : parentCfg.getNames(section)) { + String[] parentValues = parentCfg.getStringList(section, null, name); if (!allNames.contains(name)) { + cfgWithInheritance.setStringList(section, null, name, Arrays.asList(parentValues)); + } else if (merge) { cfgWithInheritance.setStringList( - section, null, name, Arrays.asList(parentCfg.getStringList(section, null, name))); + section, + null, + name, + Stream.concat( + Arrays.stream(cfg.getStringList(section, null, name)), + Arrays.stream(parentValues)) + .sorted() + .distinct() + .collect(Collectors.toList())); } } for (String subsection : parentCfg.getSubsections(section)) { allNames = get().getNames(section, subsection); for (String name : parentCfg.getNames(section, subsection)) { + String[] parentValues = parentCfg.getStringList(section, subsection, name); if (!allNames.contains(name)) { + cfgWithInheritance.setStringList( + section, subsection, name, Arrays.asList(parentValues)); + } else if (merge) { cfgWithInheritance.setStringList( section, subsection, name, - Arrays.asList(parentCfg.getStringList(section, subsection, name))); + Streams.concat( + Arrays.stream(cfg.getStringList(section, subsection, name)), + Arrays.stream(parentValues)) + .sorted() + .distinct() + .collect(Collectors.toList())); } } }