CheckCodeOwnerConfigFiles: Allow to specify a path glob
This way the caller can control which code owner config files should be
validated.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I424a3c22801c3a2aa46a2d3bc582f953fd187ba1
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
index 6e2abfc..fd865b8 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
@@ -38,4 +38,12 @@
* validated.
*/
public List<String> branches;
+
+ /**
+ * Glob that limits the validation to code owner config files that have a path that matches this
+ * glob.
+ *
+ * <p>By default unset, which means that all code owner config files should be validated.
+ */
+ public String path;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
index 6f86d06..d4a839a 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
@@ -37,6 +37,7 @@
abstract class CheckCodeOwnerConfigFilesRequest {
private boolean validateDisabledBranches;
private ImmutableList<String> branches;
+ private String path;
/**
* Includes code owner config files in branches for which the code owners functionality is
@@ -81,6 +82,24 @@
}
/**
+ * Sets a glob that limits the validation to code owner config files that have a path that
+ * matches this glob.
+ */
+ public CheckCodeOwnerConfigFilesRequest setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Gets the glob that limits the validation to code owner config files that have a path that
+ * matches this glob.
+ */
+ @Nullable
+ public String getPath() {
+ return path;
+ }
+
+ /**
* Executes the request to check the code owner config files and retrieves the result of the
* validation.
*/
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
index 0a4fdad..6011572 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
@@ -85,6 +85,7 @@
CheckCodeOwnerConfigFilesInput input = new CheckCodeOwnerConfigFilesInput();
input.validateDisabledBranches = isValidateDisabledBranches();
input.branches = getBranches();
+ input.path = getPath();
return checkCodeOwnerConfigFiles.apply(projectResource, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot check code owner config files", e);
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
index 6585278..99ee653 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
@@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
@@ -28,6 +29,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
@@ -82,16 +84,39 @@
* @param branchNameKey the project and branch for which the code owner config files should be
* visited
* @param codeOwnerConfigVisitor the callback that is invoked for each code owner config file
+ * @param invalidCodeOwnerConfigCallback callback that is invoked for invalid code owner config
+ * files
*/
public void visit(
BranchNameKey branchNameKey,
CodeOwnerConfigVisitor codeOwnerConfigVisitor,
InvalidCodeOwnerConfigCallback invalidCodeOwnerConfigCallback) {
+ visit(branchNameKey, codeOwnerConfigVisitor, invalidCodeOwnerConfigCallback, null);
+ }
+
+ /**
+ * Visits all code owner config files in the given project and branch.
+ *
+ * @param branchNameKey the project and branch for which the code owner config files should be
+ * visited
+ * @param codeOwnerConfigVisitor the callback that is invoked for each code owner config file
+ * @param invalidCodeOwnerConfigCallback callback that is invoked for invalid code owner config
+ * files
+ * @param pathGlob optional Java NIO glob that the paths of code owner config files must match
+ */
+ public void visit(
+ BranchNameKey branchNameKey,
+ CodeOwnerConfigVisitor codeOwnerConfigVisitor,
+ InvalidCodeOwnerConfigCallback invalidCodeOwnerConfigCallback,
+ @Nullable String pathGlob) {
requireNonNull(branchNameKey, "branchNameKey");
requireNonNull(codeOwnerConfigVisitor, "codeOwnerConfigVisitor");
requireNonNull(invalidCodeOwnerConfigCallback, "invalidCodeOwnerConfigCallback");
CodeOwnerBackend codeOwnerBackend = codeOwnersPluginConfiguration.getBackend(branchNameKey);
+ logger.atFine().log(
+ "scanning code owner files in branch %s of project %s (path glob = %s)",
+ branchNameKey.branch(), branchNameKey.project(), pathGlob);
try (Repository repository = repoManager.openRepository(branchNameKey.project());
RevWalk rw = new RevWalk(repository);
@@ -106,7 +131,8 @@
RevCommit revision = rw.parseCommit(ref.getObjectId());
treeWalk.addTree(revision.getTree());
treeWalk.setRecursive(true);
- treeWalk.setFilter(createCodeOwnerConfigFilter(codeOwnerBackend, branchNameKey.project()));
+ treeWalk.setFilter(
+ createCodeOwnerConfigFilter(codeOwnerBackend, branchNameKey.project(), pathGlob));
while (treeWalk.next()) {
Path filePath = Paths.get(treeWalk.getPathString());
@@ -151,9 +177,16 @@
}
}
- /** Creates a {@link TreeFilter} that matches code owner config files in the given project. */
+ /**
+ * Creates a {@link TreeFilter} that matches code owner config files in the given project.
+ *
+ * @param codeOwnerBackend the code owner backend that is being used
+ * @param project the name of the project in which code owner config files should be matched
+ * @param pathGlob optional Java NIO glob that the paths of code owner config files must match
+ * @return the created {@link TreeFilter}
+ */
private static TreeFilter createCodeOwnerConfigFilter(
- CodeOwnerBackend codeOwnerBackend, Project.NameKey project) {
+ CodeOwnerBackend codeOwnerBackend, Project.NameKey project, @Nullable String pathGlob) {
return new TreeFilter() {
@Override
public boolean shouldBeRecursive() {
@@ -166,6 +199,14 @@
walker.enterSubtree();
return false;
}
+ if (pathGlob != null
+ && !FileSystems.getDefault()
+ .getPathMatcher("glob:" + pathGlob)
+ .matches(JgitPath.of(walker.getPathString()).getAsAbsolutePath())) {
+ logger.atFine().log(
+ "%s filtered out because it doesn't match the path glob", walker.getPathString());
+ return false;
+ }
String fileName = Paths.get(walker.getPathString()).getFileName().toString();
return codeOwnerBackend.isCodeOwnerConfigFile(project, fileName);
}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
index a00f476..09dc34d 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
@@ -108,8 +108,8 @@
logger.atFine().log(
"checking code owner config files for project %s"
- + " (validateDisabledBranches = %s, branches = %s)",
- projectResource.getNameKey(), input.validateDisabledBranches, input.branches);
+ + " (validateDisabledBranches = %s, branches = %s, path = %s)",
+ projectResource.getNameKey(), input.validateDisabledBranches, input.branches, input.path);
ImmutableSet<BranchNameKey> branches = branches(projectResource);
@@ -125,7 +125,8 @@
|| !codeOwnersPluginConfiguration.isDisabled(branchNameKey))
.forEach(
branchNameKey ->
- resultsByBranchBuilder.put(branchNameKey.branch(), checkBranch(branchNameKey)));
+ resultsByBranchBuilder.put(
+ branchNameKey.branch(), checkBranch(input.path, branchNameKey)));
return Response.ok(resultsByBranchBuilder.build());
}
@@ -137,7 +138,8 @@
.collect(toImmutableSet());
}
- private Map<String, List<ConsistencyProblemInfo>> checkBranch(BranchNameKey branchNameKey) {
+ private Map<String, List<ConsistencyProblemInfo>> checkBranch(
+ String pathGlob, BranchNameKey branchNameKey) {
ListMultimap<String, ConsistencyProblemInfo> problemsByPath = LinkedListMultimap.create();
CodeOwnerBackend codeOwnerBackend = codeOwnersPluginConfiguration.getBackend(branchNameKey);
codeOwnerConfigScanner.visit(
@@ -153,7 +155,8 @@
codeOwnerConfigFilePath.toString(),
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.ERROR, configInvalidException.getMessage()));
- });
+ },
+ pathGlob);
return Multimaps.asMap(problemsByPath);
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
index a2f5572..a0f3384 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
@@ -317,6 +317,135 @@
.containsExactly("refs/meta/config", ImmutableMap.of());
}
+ @Test
+ public void validateExactFile() throws Exception {
+ // imports are not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ // Create some code owner config files with issues.
+ CodeOwnerConfig.Key keyOfInvalidConfig1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addImport(
+ CodeOwnerConfigReference.create(
+ CodeOwnerConfigImportMode.ALL, "/not-a-code-owner-config"))
+ .create();
+
+ CodeOwnerConfig.Key keyOfInvalidConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail("unknown1@example.com")
+ .addCodeOwnerEmail("unknown2@example.com")
+ .create();
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .checkCodeOwnerConfigFiles()
+ .setBranches(ImmutableList.of("master"))
+ .setPath(getCodeOwnerConfigFilePath(keyOfInvalidConfig1))
+ .check())
+ .containsExactly(
+ "refs/heads/master",
+ ImmutableMap.of(
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig1),
+ ImmutableList.of(
+ error(
+ String.format(
+ "invalid global import in '%s': '/not-a-code-owner-config' is"
+ + " not a code owner config file",
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig1))))));
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .checkCodeOwnerConfigFiles()
+ .setBranches(ImmutableList.of("master"))
+ .setPath(getCodeOwnerConfigFilePath(keyOfInvalidConfig2))
+ .check())
+ .containsExactly(
+ "refs/heads/master",
+ ImmutableMap.of(
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig2),
+ ImmutableList.of(
+ error(
+ String.format(
+ "code owner email 'unknown1@example.com' in '%s' cannot be"
+ + " resolved for admin",
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig2))),
+ error(
+ String.format(
+ "code owner email 'unknown2@example.com' in '%s' cannot be"
+ + " resolved for admin",
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig2))))));
+ }
+
+ @Test
+ public void validateFilesMatchingGlob() throws Exception {
+ // imports are not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ // Create some code owner config files with issues.
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addImport(
+ CodeOwnerConfigReference.create(
+ CodeOwnerConfigImportMode.ALL, "/not-a-code-owner-config"))
+ .create();
+
+ CodeOwnerConfig.Key keyOfInvalidConfig2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail("unknown1@example.com")
+ .create();
+
+ CodeOwnerConfig.Key keyOfInvalidConfig3 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail("unknown2@example.com")
+ .create();
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .checkCodeOwnerConfigFiles()
+ .setBranches(ImmutableList.of("master"))
+ .setPath("/foo/**")
+ .check())
+ .containsExactly(
+ "refs/heads/master",
+ ImmutableMap.of(
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig2),
+ ImmutableList.of(
+ error(
+ String.format(
+ "code owner email 'unknown1@example.com' in '%s' cannot be"
+ + " resolved for admin",
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig2)))),
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig3),
+ ImmutableList.of(
+ error(
+ String.format(
+ "code owner email 'unknown2@example.com' in '%s' cannot be"
+ + " resolved for admin",
+ getCodeOwnerConfigFilePath(keyOfInvalidConfig3))))));
+ }
+
private ConsistencyProblemInfo error(String message) {
return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, message);
}
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 189ff23..84e36a1 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -383,6 +383,7 @@
| ---------------------------- | -------- | ----------- |
| `validate_disabled_branches` | optional | Whether code owner config files in branches for which the code owners functionality is disabled should be validated too. By default unset, `false`.
| `branches` | optional | List of branches for which code owner config files should be validated. The `refs/heads/` prefix may be omitted. By default unset, which means that code owner config files in all branches should be validated.
+| `path` | optional | Glob that limits the validation to code owner config files that have a path that matches this glob. By default unset, which means that all code owner config files should be validated.
---