Merge "Support wildcard matching in repository configuration"
diff --git a/.gitignore b/.gitignore
index f2cb839..1125f5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@
 *.swp
 *.asc
 /bin/
+/UNIT_TEST_GERRIT_SITE
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 43c0b22..dff2cd0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.httpd.rpc.project;
 
+import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
@@ -102,6 +103,7 @@
   protected Change.Id updateProjectConfig(ProjectControl ctl,
       ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
       throws IOException, OrmException {
+    md.setInsertChangeId(true);
     Change.Id changeId = new Change.Id(db.nextChangeId());
     RevCommit commit =
         config.commitToNewRef(md, new PatchSet.Id(changeId,
@@ -111,7 +113,7 @@
     }
 
     Change change = new Change(
-        new Change.Key("I" + commit.name()),
+        getChangeId(commit),
         changeId,
         user.getAccountId(),
         new Branch.NameKey(
@@ -135,6 +137,14 @@
     return changeId;
   }
 
+  private static Change.Key getChangeId(RevCommit commit) {
+    List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
+    Change.Key changeKey = !idList.isEmpty()
+        ? new Change.Key(idList.get(idList.size() - 1).trim())
+        : new Change.Key("I" + commit.name());
+    return changeKey;
+  }
+
   private void addProjectOwnersAsReviewers(ChangeResource rsrc) {
     final String projectOwners =
         groupBackend.get(SystemGroupBackend.PROJECT_OWNERS).getName();
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 1b92ab2..2b6c1281 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -554,7 +554,7 @@
         doc.add(new StoredField(name, (byte[]) value));
       }
     } else {
-      throw QueryBuilder.badFieldType(type);
+      throw FieldType.badFieldType(type);
     }
   }
 
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index bb29db2..218bb71 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -148,7 +148,7 @@
     } else if (p.getType() == FieldType.FULL_TEXT) {
       return fullTextQuery(p);
     } else {
-      throw badFieldType(p.getType());
+      throw FieldType.badFieldType(p.getType());
     }
   }
 
@@ -249,8 +249,4 @@
   public int toIndexTimeInMinutes(Date ts) {
     return (int) (ts.getTime() / 60000);
   }
-
-  public static IllegalArgumentException badFieldType(FieldType<?> t) {
-    return new IllegalArgumentException("unknown index field type " + t);
-  }
 }
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 86785ef..99ff581 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
@@ -122,6 +122,7 @@
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.util.IdGenerator;
+import com.google.gerrit.server.util.SubmoduleSectionParser;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.validators.GroupCreationValidationListener;
 import com.google.gerrit.server.validators.HashtagValidationListener;
@@ -291,6 +292,7 @@
     factory(MergeValidators.Factory.class);
     factory(ProjectConfigValidator.Factory.class);
     factory(NotesBranchUtil.Factory.class);
+    factory(SubmoduleSectionParser.Factory.class);
 
     bind(AccountManager.class);
     bind(ChangeUserName.CurrentUser.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index c71c94f..840b167 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -148,6 +148,7 @@
   private final BatchRefUpdate batch;
   private final CommitBuilder commit;
   private boolean allowEmpty;
+  private boolean insertChangeId;
 
   @AssistedInject
   public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
@@ -180,6 +181,10 @@
     this.allowEmpty = allowEmpty;
   }
 
+  public void setInsertChangeId(boolean insertChangeId) {
+    this.insertChangeId = insertChangeId;
+  }
+
   /** @return batch in which to run the update, or {@code null} for no batch. */
   BatchRefUpdate getBatch() {
     return batch;
@@ -202,6 +207,10 @@
     return allowEmpty;
   }
 
+  boolean insertChangeId() {
+    return insertChangeId;
+  }
+
   public CommitBuilder getCommitBuilder() {
     return commit;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index e67dd2c..f05a1d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -93,6 +93,7 @@
   private final Set<Branch.NameKey> updatedSubscribers;
   private final Account account;
   private final ChangeHooks changeHooks;
+  private final SubmoduleSectionParser.Factory subSecParserFactory;
 
   @Inject
   public SubmoduleOp(@Assisted Branch.NameKey destBranch,
@@ -109,7 +110,8 @@
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
       @Nullable @Assisted Account account,
-      ChangeHooks changeHooks) {
+      ChangeHooks changeHooks,
+      SubmoduleSectionParser.Factory subSecParserFactory) {
     this.destBranch = destBranch;
     this.mergeTip = mergeTip;
     this.rw = rw;
@@ -124,6 +126,7 @@
     this.gitRefUpdated = gitRefUpdated;
     this.account = account;
     this.changeHooks = changeHooks;
+    this.subSecParserFactory = subSecParserFactory;
 
     updatedSubscribers = new HashSet<>();
   }
@@ -168,8 +171,8 @@
         final Set<SubmoduleSubscription> oldSubscriptions =
             new HashSet<>(schema.submoduleSubscriptions()
                 .bySuperProject(destBranch).toList());
-        final List<SubmoduleSubscription> newSubscriptions =
-            new SubmoduleSectionParser(bbc, thisServer, target, repoManager)
+        List<SubmoduleSubscription> newSubscriptions =
+            subSecParserFactory.create(bbc, thisServer, target)
                 .parseAllSections();
 
         final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index b905f67..37df726 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -43,6 +43,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.ChangeIdUtil;
 import org.eclipse.jgit.util.RawParseUtils;
 
 import java.io.BufferedReader;
@@ -271,6 +272,14 @@
           commit.addParentId(src);
         }
 
+        if (update.insertChangeId()) {
+          ObjectId id =
+              ChangeIdUtil.computeChangeId(res, getRevision(),
+                  commit.getAuthor(), commit.getCommitter(),
+                  commit.getMessage());
+          commit.setMessage(ChangeIdUtil.insertId(commit.getMessage(), id));
+        }
+
         src = inserter.insert(commit);
         srcTree = res;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
index dce8a20..89dc808 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -65,4 +65,8 @@
   public String toString() {
     return name;
   }
+
+  public static IllegalArgumentException badFieldType(FieldType<?> t) {
+    return new IllegalArgumentException("unknown index field type " + t);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index 8970425..45aa31b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -17,7 +17,9 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.BlobBasedConfig;
 import org.eclipse.jgit.lib.Constants;
@@ -45,18 +47,26 @@
  * </pre>
  */
 public class SubmoduleSectionParser {
+
+  public interface Factory {
+    SubmoduleSectionParser create(BlobBasedConfig bbc, String thisServer,
+        Branch.NameKey superProjectBranch);
+  }
+
+  private final ProjectCache projectCache;
   private final BlobBasedConfig bbc;
   private final String thisServer;
   private final Branch.NameKey superProjectBranch;
-  private final GitRepositoryManager repoManager;
 
-  public SubmoduleSectionParser(final BlobBasedConfig bbc,
-      final String thisServer, final Branch.NameKey superProjectBranch,
-      final GitRepositoryManager repoManager) {
+  @Inject
+  public SubmoduleSectionParser(ProjectCache projectCache,
+      @Assisted BlobBasedConfig bbc,
+      @Assisted String thisServer,
+      @Assisted Branch.NameKey superProjectBranch) {
+    this.projectCache = projectCache;
     this.bbc = bbc;
     this.thisServer = thisServer;
     this.superProjectBranch = superProjectBranch;
-    this.repoManager = repoManager;
   }
 
   public List<SubmoduleSubscription> parseAllSections() {
@@ -106,12 +116,10 @@
               projectName = projectName.substring(0, //
                   projectName.length() - Constants.DOT_GIT_EXT.length());
             }
-
-            if (repoManager.list().contains(new Project.NameKey(projectName))) {
-              return new SubmoduleSubscription(
-                  superProjectBranch,
-                  new Branch.NameKey(new Project.NameKey(projectName), branch),
-                  path);
+            Project.NameKey projectKey = new Project.NameKey(projectName);
+            if (projectCache.get(projectKey) != null) {
+              return new SubmoduleSubscription(superProjectBranch,
+                  new Branch.NameKey(projectKey, branch), path);
             }
           }
         }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index d87888f..2b8f522 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.util;
 
+import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.createStrictMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
@@ -23,7 +24,8 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.lib.BlobBasedConfig;
@@ -33,16 +35,14 @@
 
 import java.net.URI;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.TreeSet;
 
 public class SubmoduleSectionParserTest extends LocalDiskRepositoryTestCase {
   private static final String THIS_SERVER = "localhost";
-  private GitRepositoryManager repoManager;
+  private ProjectCache projectCache;
   private BlobBasedConfig bbc;
 
   @Override
@@ -50,16 +50,16 @@
   public void setUp() throws Exception {
     super.setUp();
 
-    repoManager = createStrictMock(GitRepositoryManager.class);
+    projectCache = createStrictMock(ProjectCache.class);
     bbc = createStrictMock(BlobBasedConfig.class);
   }
 
   private void doReplay() {
-    replay(repoManager, bbc);
+    replay(projectCache, bbc);
   }
 
   private void doVerify() {
-    verify(repoManager, bbc);
+    verify(projectCache, bbc);
   }
 
   @Test
@@ -214,13 +214,12 @@
                 projectNameCandidate.length() - Constants.DOT_GIT_EXT.length());
           }
           if (projectNameCandidate.equals(reposToBeFound.get(id))) {
-            expect(repoManager.list()).andReturn(
-                new TreeSet<>(Collections.singletonList(
-                    new Project.NameKey(projectNameCandidate))));
+            expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
+                .andReturn(createNiceMock(ProjectState.class));
             break;
           } else {
-            expect(repoManager.list()).andReturn(
-                new TreeSet<>(Collections.<Project.NameKey> emptyList()));
+            expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
+                .andReturn(null);
           }
         }
       }
@@ -229,8 +228,8 @@
     doReplay();
 
     final SubmoduleSectionParser ssp =
-        new SubmoduleSectionParser(bbc, THIS_SERVER, superProjectBranch,
-            repoManager);
+        new SubmoduleSectionParser(projectCache, bbc, THIS_SERVER,
+            superProjectBranch);
 
     List<SubmoduleSubscription> returnedSubscriptions = ssp.parseAllSections();
 
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
index ab95ae4..b9e47954 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
@@ -319,7 +319,7 @@
         doc.addField(name, value);
       }
     } else {
-      throw QueryBuilder.badFieldType(type);
+      throw FieldType.badFieldType(type);
     }
   }