REST API to expose owners of each file
This API will expose, for a given patch set, all the owners
of a given file.
Here an example:
```
GET /changes/{change-id}/revisions/{revision-id}/owners~files-owners
{
"AJavaFile.java":[
{ "name":"John", "id": 1 },
{ "name":"Bob", "id": 2 }
],
"Aptyhonfileroot.py":[
{ "name":"John", "id": 1 },
{ "name":"Bob", "id": 2 },
{ "name":"John", "id": 3 }
]
}
```
The full name and id of the owner will be displayed. If not available
it will fallback to the account Id.
Change-Id: I4167989fe0fe58efa7e547c06489960af13de73c
diff --git a/owners/BUILD b/owners/BUILD
index ab6705e..d04d83d 100644
--- a/owners/BUILD
+++ b/owners/BUILD
@@ -1,6 +1,7 @@
-load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS_NEVERLINK", "PLUGIN_TEST_DEPS", "gerrit_plugin")
+load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_DEPS_NEVERLINK", "PLUGIN_TEST_DEPS", "gerrit_plugin")
load("//lib/prolog:prolog.bzl", "prolog_cafe_library")
load("//owners-common:common.bzl", "EXTERNAL_DEPS", "EXTERNAL_TEST_DEPS")
+load("//tools/bzl:junit.bzl", "junit_tests")
PROLOG_PREDICATES = glob(["src/main/java/gerrit_owners/**/*.java"]) + [
"src/main/java/com/googlesource/gerrit/owners/OwnersStoredValues.java",
@@ -49,5 +50,14 @@
name = "owners__plugin_test_deps",
testonly = 1,
visibility = ["//visibility:public"],
- exports = EXTERNAL_DEPS + EXTERNAL_TEST_DEPS + PLUGIN_TEST_DEPS,
+ exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+ ":owners",
+ ],
+)
+
+junit_tests(
+ name = "owners_tests",
+ srcs = glob(["src/test/java/**/*.java"]),
+ tags = ["owners"],
+ deps = ["owners__plugin_test_deps"],
)
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersModule.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersModule.java
index 750c58e..e3a915b 100644
--- a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersModule.java
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersModule.java
@@ -25,5 +25,6 @@
DynamicSet.bind(binder(), PredicateProvider.class)
.to(OwnerPredicateProvider.class)
.asEagerSingleton();
+ install(new OwnersRestApiModule());
}
}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/OwnersRestApiModule.java b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersRestApiModule.java
new file mode 100644
index 0000000..ccd3fcb
--- /dev/null
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/OwnersRestApiModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 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.googlesource.gerrit.owners;
+
+import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.change.RevisionResource;
+import com.googlesource.gerrit.owners.restapi.GetFilesOwners;
+
+public class OwnersRestApiModule extends RestApiModule {
+ @Override
+ protected void configure() {
+ get(RevisionResource.REVISION_KIND, "files-owners").to(GetFilesOwners.class);
+ }
+}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/entities/Owner.java b/owners/src/main/java/com/googlesource/gerrit/owners/entities/Owner.java
new file mode 100644
index 0000000..e580f45
--- /dev/null
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/entities/Owner.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2022 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.googlesource.gerrit.owners.entities;
+
+import com.google.common.base.Objects;
+import com.google.gerrit.entities.Account;
+
+/** Class representing a file Owner * */
+public class Owner {
+ private final String name;
+ private final int id;
+
+ public Owner(String name, int id) {
+ this.name = name;
+ this.id = id;
+ }
+
+ /**
+ * Get the {@link Owner} name.
+ *
+ * @return the Owner name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get the {@link Owner} account id.
+ *
+ * @return an {@code int} representation of the Owner {@link Account.Id}.
+ */
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Owner owner = (Owner) o;
+ return Objects.equal(id, owner.id) && Objects.equal(name, owner.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name, id);
+ }
+}
diff --git a/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
new file mode 100644
index 0000000..a59e876
--- /dev/null
+++ b/owners/src/main/java/com/googlesource/gerrit/owners/restapi/GetFilesOwners.java
@@ -0,0 +1,102 @@
+// Copyright (C) 2022 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.googlesource.gerrit.owners.restapi;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.owners.common.Accounts;
+import com.googlesource.gerrit.owners.common.PathOwners;
+import com.googlesource.gerrit.owners.common.PluginSettings;
+import com.googlesource.gerrit.owners.entities.Owner;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.Repository;
+
+@Singleton
+public class GetFilesOwners implements RestReadView<RevisionResource> {
+
+ private final PatchListCache patchListCache;
+ private final Accounts accounts;
+ private final AccountCache accountCache;
+ private final GitRepositoryManager repositoryManager;
+ private final PluginSettings pluginSettings;
+
+ @Inject
+ GetFilesOwners(
+ PatchListCache patchListCache,
+ Accounts accounts,
+ AccountCache accountCache,
+ GitRepositoryManager repositoryManager,
+ PluginSettings pluginSettings) {
+ this.patchListCache = patchListCache;
+ this.accounts = accounts;
+ this.accountCache = accountCache;
+ this.repositoryManager = repositoryManager;
+ this.pluginSettings = pluginSettings;
+ }
+
+ @Override
+ public Response<?> apply(RevisionResource revision)
+ throws AuthException, BadRequestException, ResourceConflictException, Exception {
+ PatchSet ps = revision.getPatchSet();
+ Change change = revision.getChange();
+
+ try (Repository repository = repositoryManager.openRepository(change.getProject())) {
+ PatchList patchList = patchListCache.get(change, ps);
+
+ String branch = change.getDest().branch();
+ PathOwners owners =
+ new PathOwners(
+ accounts,
+ repository,
+ pluginSettings.isBranchDisabled(branch) ? Optional.empty() : Optional.of(branch),
+ patchList);
+
+ Map<String, Set<Owner>> fileToOwners =
+ Maps.transformValues(
+ owners.getFileOwners(),
+ ids ->
+ ids.stream()
+ .map(this::getOwnerFromAccountId)
+ .flatMap(Optional::stream)
+ .collect(Collectors.toSet()));
+
+ return Response.ok(fileToOwners);
+ }
+ }
+
+ private Optional<Owner> getOwnerFromAccountId(Account.Id accountId) {
+ return accountCache
+ .get(accountId)
+ .map(as -> new Owner(as.account().fullName(), as.account().id().get()));
+ }
+}
diff --git a/owners/src/main/resources/Documentation/rest-api.md b/owners/src/main/resources/Documentation/rest-api.md
new file mode 100644
index 0000000..2341607
--- /dev/null
+++ b/owners/src/main/resources/Documentation/rest-api.md
@@ -0,0 +1,13 @@
+# Rest API
+
+The @PLUGIN@ exposes a Rest API endpoint to list the owners associated to each file:
+
+```bash
+GET /changes/{change-id}/revisions/{revision-id}/owners~getFilesOwners
+
+{
+ "AJavaFile.java":[{ "name":"John", "id": 1 }, { "name":"Bob", "id": 2 }],
+ "Aptyhonfileroot.py":[{ "name":"John", "id": 1 }, { "name":"Bob", "id": 2 }, { "name":"John", "id": 3 }]
+}
+
+```
\ No newline at end of file
diff --git a/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java
new file mode 100644
index 0000000..a46f7cf
--- /dev/null
+++ b/owners/src/test/java/com/googlesource/gerrit/owners/restapi/GetFilesOwnersIT.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2022 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.googlesource.gerrit.owners.restapi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.server.change.RevisionResource;
+import com.googlesource.gerrit.owners.entities.Owner;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.compress.utils.Sets;
+import org.junit.Test;
+
+@TestPlugin(name = "owners", httpModule = "com.googlesource.gerrit.owners.OwnersRestApiModule")
+public class GetFilesOwnersIT extends LightweightPluginDaemonTest {
+
+ private GetFilesOwners ownersApi;
+
+ @Override
+ public void setUpTestPlugin() throws Exception {
+ super.setUpTestPlugin();
+
+ ownersApi = plugin.getSysInjector().getInstance(GetFilesOwners.class);
+ }
+
+ @Test
+ @UseLocalDisk
+ public void shouldReturnACorrectResponse() throws Exception {
+ // Add OWNERS file to root:
+ //
+ // inherited: true
+ // owners:
+ // - Administrator
+ merge(createChange(testRepo, "master", "Add OWNER file", "OWNERS", getOwnerFileContent(), ""));
+
+ PushOneCommit.Result result = createChange();
+ RevisionResource revisionResource = parseCurrentRevisionResource(result.getChangeId());
+
+ Response<?> resp = ownersApi.apply(revisionResource);
+
+ assertThat(resp.statusCode()).isEqualTo(HttpServletResponse.SC_OK);
+
+ Map<String, Set<Owner>> responseValue = (Map<String, Set<Owner>>) resp.value();
+ HashMap<String, Set<Owner>> expectedResponseValue =
+ new HashMap<String, Set<Owner>>() {
+ {
+ put("a.txt", Sets.newHashSet(new Owner(admin.fullName(), admin.id().get())));
+ }
+ };
+
+ assertThat(responseValue).containsExactlyEntriesIn(expectedResponseValue);
+ }
+
+ private String getOwnerFileContent() {
+ return "owners:\n" + "- " + admin.email() + "\n";
+ }
+}