PutConfig: Allow to add or update commentlinks
Add a new CommentLinkInput class in the extension API, and include it
in the ConfigInput as a map of commentlink names to CommentLinkInput.
Each entry in the map is added or updated in the project's config. If
the config already contains a commentlink with the same name, it is
updated with the values supplied, otherwise it is added. If the map
contains a null value, that entry is removed.
Since commentlinks at project level may not supply the 'html' value,
this is not included in the input.
Now that we are able to set commentlinks via the API, also expand the
tests to cover the behavior of project specific commentlinks together
with globally configured commentlinks, and inheritance of commentlinks
from the parent project.
Change-Id: Ief84c2f3bb85c6fcb28ccb1c09c537f6ab75395a
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index a09075d..1db3746 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3454,6 +3454,25 @@
|`enabled` |optional|Whether the commentlink is enabled, as documented
in link:config-gerrit.html#commentlink.name.enabled[
commentlink.name.enabled]. If not set the commentlink is enabled.
+
+[[commentlink-input]]
+=== CommentLinkInput
+The `CommentLinkInput` entity describes the input for a
+link:config-gerrit.html#commentlink[commentlink].
+
+|==================================================
+[options="header",cols="1,^2,4"]
+|==================================================
+|Field Name | |Description
+|`match` | |A JavaScript regular expression to match
+positions to be replaced with a hyperlink, as documented in
+link:config-gerrit.html#commentlink.name.match[commentlink.name.match].
+|`link` | |The URL to direct the user to whenever the
+regular expression is matched, as documented in
+link:config-gerrit.html#commentlink.name.link[commentlink.name.link].
+|`enabled` |optional|Whether the commentlink is enabled, as documented
+in link:config-gerrit.html#commentlink.name.enabled[
+commentlink.name.enabled]. If not set the commentlink is enabled.
|==================================================
[[config-info]]
@@ -3604,6 +3623,11 @@
Whether empty commits should be rejected when a change is merged.
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
+|commentlinks |optional|
+Map of commentlink names to link:#commentlink-input[CommentLinkInput]
+entities to add or update on the project. If the given commentlink
+already exists, it will be updated with the given values, otherwise
+it will be created. If the value is null, that entry is deleted.
|======================================================
[[config-parameter-info]]
diff --git a/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java b/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java
new file mode 100644
index 0000000..3aad7e1
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 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.google.gerrit.extensions.api.projects;
+
+/*
+ * Input for a commentlink configuration on a project.
+ */
+public class CommentLinkInput {
+ /** A JavaScript regular expression to match positions to be replaced with a hyperlink. */
+ public String match;
+ /** The URL to direct the user to whenever the regular expression is matched. */
+ public String link;
+ /** Whether the commentlink is enabled. */
+ public Boolean enabled;
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
index 1a6d77b..8005fc5 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
@@ -38,4 +38,5 @@
public SubmitType submitType;
public ProjectState state;
public Map<String, Map<String, ConfigValue>> pluginConfigValues;
+ public Map<String, CommentLinkInput> commentLinks;
}
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 9604f28..4d551a2 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.common.data.Permission.isPermission;
import static com.google.gerrit.entities.Project.DEFAULT_SUBMIT_TYPE;
import static com.google.gerrit.server.permissions.PluginPermissionsUtil.isValidPluginPermission;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
@@ -112,10 +113,10 @@
public static final String KEY_CAN_OVERRIDE = "canOverride";
public static final String KEY_BRANCH = "branch";
- private static final String KEY_MATCH = "match";
+ public static final String KEY_MATCH = "match";
private static final String KEY_HTML = "html";
- private static final String KEY_LINK = "link";
- private static final String KEY_ENABLED = "enabled";
+ public static final String KEY_LINK = "link";
+ public static final String KEY_ENABLED = "enabled";
public static final String PROJECT_CONFIG = "project.config";
@@ -291,6 +292,11 @@
commentLinkSections.put(commentLink.name, commentLink);
}
+ public void removeCommentLinkSection(String name) {
+ requireNonNull(name);
+ requireNonNull(commentLinkSections.remove(name));
+ }
+
private ProjectConfig(Project.NameKey projectName, @Nullable StoredConfig baseConfig) {
this.projectName = projectName;
this.baseConfig = baseConfig;
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index 696ac37..a0badd7 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -14,11 +14,17 @@
package com.google.gerrit.server.restapi.project;
+import static com.google.gerrit.server.project.ProjectConfig.COMMENTLINK;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_ENABLED;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_LINK;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_MATCH;
+
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.api.projects.CommentLinkInput;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -59,6 +65,7 @@
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
@@ -154,6 +161,10 @@
setPluginConfigValues(projectState, projectConfig, input.pluginConfigValues);
}
+ if (input.commentLinks != null) {
+ updateCommentLinks(projectConfig, input.commentLinks);
+ }
+
md.setMessage("Modified project settings\n");
try {
projectConfig.commit(md);
@@ -278,6 +289,25 @@
}
}
+ private void updateCommentLinks(
+ ProjectConfig projectConfig, Map<String, CommentLinkInput> input) {
+ for (Map.Entry<String, CommentLinkInput> e : input.entrySet()) {
+ String name = e.getKey();
+ CommentLinkInput value = e.getValue();
+ if (value != null) {
+ // Add or update the commentlink section
+ Config cfg = new Config();
+ cfg.setString(COMMENTLINK, name, KEY_MATCH, value.match);
+ cfg.setString(COMMENTLINK, name, KEY_LINK, value.link);
+ cfg.setBoolean(COMMENTLINK, name, KEY_ENABLED, value.enabled == null || value.enabled);
+ projectConfig.addCommentLinkSection(ProjectConfig.buildCommentLink(cfg, name, false));
+ } else {
+ // Delete the commentlink section
+ projectConfig.removeCommentLinkSection(name);
+ }
+ }
+ }
+
private static void validateProjectConfigEntryIsEditable(
ProjectConfigEntry projectConfigEntry,
ProjectState projectState,
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index eebcc5b..1a8790a 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -49,6 +49,7 @@
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
+import com.google.gerrit.extensions.api.projects.CommentLinkInput;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -717,6 +718,139 @@
assertCommentLinks(getConfig(), expected);
}
+ @Test
+ public void projectConfigUsesLocallySetCommentlinks() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ @GerritConfig(name = "commentlink.bugzilla.match", value = BUGZILLA_MATCH)
+ @GerritConfig(name = "commentlink.bugzilla.link", value = BUGZILLA_LINK)
+ public void projectConfigUsesCommentLinksFromGlobalAndLocal() throws Exception {
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ assertCommentLinks(getConfig(), expected);
+
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ @GerritConfig(name = "commentlink.bugzilla.match", value = BUGZILLA_MATCH)
+ @GerritConfig(name = "commentlink.bugzilla.link", value = BUGZILLA_LINK)
+ public void localCommentLinkOverridesGlobalConfig() throws Exception {
+ String otherLink = "https://other.example.com";
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+
+ ConfigInfo info = setConfig(project, input);
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ public void localCommentLinksAreInheritedFromParent() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ Project.NameKey child = projectOperations.newProject().parent(project).create();
+ assertCommentLinks(getConfig(child), expected);
+ }
+
+ @Test
+ public void localCommentLinkOverridesParentCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ Project.NameKey child = projectOperations.newProject().parent(project).create();
+
+ String otherLink = "https://other.example.com";
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+ info = setConfig(child, input);
+
+ expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+
+ assertCommentLinks(getConfig(child), expected);
+ }
+
+ @Test
+ public void updateExistingCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ String otherLink = "https://other.example.com";
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+ info = setConfig(project, input);
+
+ expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(getConfig(project), expected);
+ }
+
+ @Test
+ public void removeCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, null);
+ info = setConfig(project, input);
+
+ expected = new HashMap<>();
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(getConfig(project), expected);
+ }
+
private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
return new CommentLinkInfoImpl(name, match, link, null /*html*/, null /*enabled*/);
}
@@ -725,6 +859,21 @@
assertThat(actual.commentlinks).containsExactlyEntriesIn(expected);
}
+ private void addCommentLink(ConfigInput configInput, String name, String match, String link) {
+ CommentLinkInput commentLinkInput = new CommentLinkInput();
+ commentLinkInput.match = match;
+ commentLinkInput.link = link;
+ addCommentLink(configInput, name, commentLinkInput);
+ }
+
+ private void addCommentLink(
+ ConfigInput configInput, String name, CommentLinkInput commentLinkInput) {
+ if (configInput.commentLinks == null) {
+ configInput.commentLinks = new HashMap<>();
+ }
+ configInput.commentLinks.put(name, commentLinkInput);
+ }
+
private ConfigInfo setConfig(Project.NameKey name, ConfigInput input) throws Exception {
return gApi.projects().name(name.get()).config(input);
}