Prevent inheriting from multiple projects

Adding more than one project to inherit from manually in project.config
will silently ignore all but the last value, which is likely not what
the user expects. Reject the config instead.

Change-Id: I8139746a31e1ba78a88c54ed486920acf5544c1d
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index b7f09d1..02cf4f8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.fail;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.common.data.Permission;
@@ -28,10 +29,13 @@
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.Util;
 
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -143,6 +147,36 @@
         .isEqualTo(parent.name);
   }
 
+  @Test
+  public void rejectDoubleInheritance() throws Exception {
+    setApiUser(admin);
+    // Create separate projects to test the config
+    Project.NameKey parent = createProject("projectToInheritFrom");
+    Project.NameKey child = createProject("projectWithMalformedConfig");
+
+    String config = gApi.projects()
+        .name(child.get())
+        .branch(RefNames.REFS_CONFIG).file("project.config").asString();
+
+    // Append and push malformed project config
+    String pattern =  "[access]\n"
+        + "\tinheritFrom = " + allProjects.get() + "\n";
+    String doubleInherit = pattern + "\tinheritFrom = " + parent.get() + "\n";
+    config = config.replace(pattern, doubleInherit);
+
+    TestRepository<InMemoryRepository> childRepo =
+        cloneProject(child, admin);
+    // Fetch meta ref
+    GitUtil.fetch(childRepo, RefNames.REFS_CONFIG + ":cfg");
+    childRepo.reset("cfg");
+    PushOneCommit push = pushFactory.create(
+        db, admin.getIdent(), childRepo, "Subject", "project.config",
+        config);
+    PushOneCommit.Result res = push.to(RefNames.REFS_CONFIG);
+    res.assertErrorStatus();
+    res.assertMessage("cannot inherit from multiple projects");
+  }
+
   private void fetchRefsMetaConfig() throws Exception {
     git().fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config"))
         .call();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 6d9d759..be7fb04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -479,6 +479,13 @@
     if (p.getDescription() == null) {
       p.setDescription("");
     }
+
+    if (rc.getStringList(ACCESS, null, KEY_INHERIT_FROM).length > 1) {
+      // The config must not contain more than one parent to inherit from
+      // as there is no guarantee which of the parents would be used then.
+      error(new ValidationError(PROJECT_CONFIG,
+          "Cannot inherit from multiple projects"));
+    }
     p.setParentName(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
 
     p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, InheritableBoolean.INHERIT));