diff --git a/.gitignore b/.gitignore index 18dd4401d..ca2dad8a1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ local.properties .classpath .settings/ .loadpath + +### Visual Studio Code ### +.vscode # External tool builders .externalToolBuilders/ @@ -45,7 +48,7 @@ target/ # Test properties file for gitlab4j test-gitlab4j.properties -!src/test/resoures/test-gitlab4j.properties +!src/test/resources/test-gitlab4j.properties # git-changelog plugin # .okhttpcache diff --git a/src/main/java/org/gitlab4j/api/AbstractApi.java b/src/main/java/org/gitlab4j/api/AbstractApi.java index 3fd19ddd5..6381549f1 100644 --- a/src/main/java/org/gitlab4j/api/AbstractApi.java +++ b/src/main/java/org/gitlab4j/api/AbstractApi.java @@ -272,6 +272,42 @@ protected Response head(Response.Status expectedStatus, MultivaluedMap queryParams, Object... pathArgs) throws GitLabApiException { + try { + return validate(getApiClient().patch(queryParams, pathArgs), expectedStatus); + } catch (Exception e) { + throw handle(e); + } + } + + /** + * Perform an HTTP PATCH call with the specified query parameters and URL, returning + * a ClientResponse instance with the data returned from the endpoint. + * + * @param expectedStatus the HTTP status that should be returned from the server + * @param queryParams multivalue map of request parameters + * @param url the fully formed path to the GitLab API endpoint + * @return a ClientResponse instance with the data returned from the endpoint + * @throws GitLabApiException if any exception occurs during execution + */ + protected Response patch(Response.Status expectedStatus, MultivaluedMap queryParams, URL url) throws GitLabApiException { + try { + return validate(getApiClient().patch(queryParams, url), expectedStatus); + } catch (Exception e) { + throw handle(e); + } + } + /** * Perform an HTTP POST call with the specified form data and path objects, returning * a ClientResponse instance with the data returned from the endpoint. diff --git a/src/main/java/org/gitlab4j/api/GitLabApiClient.java b/src/main/java/org/gitlab4j/api/GitLabApiClient.java index 3322e8ba1..169e185ce 100755 --- a/src/main/java/org/gitlab4j/api/GitLabApiClient.java +++ b/src/main/java/org/gitlab4j/api/GitLabApiClient.java @@ -287,8 +287,8 @@ void enableRequestResponseLogging(Logger logger, Level level, int maxEntityLengt * @param readTimeout the per request read timeout in milliseconds, can be null to use default */ void setRequestTimeout(Integer connectTimeout, Integer readTimeout) { - this.connectTimeout = connectTimeout; - this.readTimeout = readTimeout; + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; } /** @@ -470,6 +470,35 @@ protected Response head(MultivaluedMap queryParams, URL url) { return (invocation(url, queryParams).head()); } + /** + * Perform an HTTP PATCH call with the specified query parameters and path objects, returning + * a ClientResponse instance with the data returned from the endpoint. + * + * @param queryParams multivalue map of request parameters + * @param pathArgs variable list of arguments used to build the URI + * @return a ClientResponse instance with the data returned from the endpoint + * @throws IOException if an error occurs while constructing the URL + */ + protected Response patch(MultivaluedMap queryParams, Object... pathArgs) throws IOException { + URL url = getApiUrl(pathArgs); + return (patch(queryParams, url)); + } + + /** + * Perform an HTTP PATCH call with the specified query parameters and URL, returning + * a ClientResponse instance with the data returned from the endpoint. + * + * @param queryParams multivalue map of request parameters + * @param url the fully formed path to the GitLab API endpoint + * @return a ClientResponse instance with the data returned from the endpoint + */ + protected Response patch(MultivaluedMap queryParams, URL url) { + Entity empty = Entity.text(""); + // use "X-HTTP-Method-Override" header on POST to override to unsupported PATCH + return (invocation(url, queryParams) + .header("X-HTTP-Method-Override", "PATCH").post(empty)); + } + /** * Perform an HTTP POST call with the specified form data and path objects, returning * a ClientResponse instance with the data returned from the endpoint. @@ -918,7 +947,7 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngi // Ignore differences between given hostname and certificate hostname HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override - public boolean verify(String hostname, SSLSession session) { + public boolean verify(String hostname, SSLSession session) { return true; } }; diff --git a/src/main/java/org/gitlab4j/api/ProtectedBranchesApi.java b/src/main/java/org/gitlab4j/api/ProtectedBranchesApi.java index c64f416f3..c018ff7a6 100644 --- a/src/main/java/org/gitlab4j/api/ProtectedBranchesApi.java +++ b/src/main/java/org/gitlab4j/api/ProtectedBranchesApi.java @@ -154,8 +154,8 @@ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, * @throws GitLabApiException if any exception occurs */ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, - AccessLevel pushAccessLevel, AccessLevel mergeAccessLevel, AccessLevel unprotectAccessLevel, - Boolean codeOwnerApprovalRequired) throws GitLabApiException { + AccessLevel pushAccessLevel, AccessLevel mergeAccessLevel, AccessLevel unprotectAccessLevel, + Boolean codeOwnerApprovalRequired) throws GitLabApiException { Form formData = new GitLabApiForm() .withParam("name", branchName, true) .withParam("push_access_level", pushAccessLevel) @@ -184,10 +184,10 @@ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, * @throws GitLabApiException if any exception occurs */ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, - Integer allowedToPushUserId, Integer allowedToMergeUserId, Integer allowedToUnprotectUserId, - Boolean codeOwnerApprovalRequired) throws GitLabApiException { + Integer allowedToPushUserId, Integer allowedToMergeUserId, Integer allowedToUnprotectUserId, + Boolean codeOwnerApprovalRequired) throws GitLabApiException { - Form formData = new GitLabApiForm() + Form formData = new GitLabApiForm() .withParam("name", branchName, true) .withParam("allowed_to_push[][user_id]", allowedToPushUserId) .withParam("allowed_to_merge[][user_id]", allowedToMergeUserId) @@ -215,22 +215,46 @@ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, * @throws GitLabApiException if any exception occurs */ public ProtectedBranch protectBranch(Object projectIdOrPath, String branchName, - AllowedTo allowedToPush, AllowedTo allowedToMerge, AllowedTo allowedToUnprotect, - Boolean codeOwnerApprovalRequired) throws GitLabApiException { + AllowedTo allowedToPush, AllowedTo allowedToMerge, AllowedTo allowedToUnprotect, + Boolean codeOwnerApprovalRequired) throws GitLabApiException { GitLabApiForm formData = new GitLabApiForm() .withParam("name", branchName, true) .withParam("code_owner_approval_required", codeOwnerApprovalRequired); - if (allowedToPush != null) - allowedToPush.getForm(formData, "allowed_to_push"); - if (allowedToMerge != null) - allowedToMerge.getForm(formData, "allowed_to_merge"); - if (allowedToUnprotect != null) - allowedToUnprotect.getForm(formData, "allowed_to_unprotect"); + if (allowedToPush != null) + allowedToPush.getForm(formData, "allowed_to_push"); + if (allowedToMerge != null) + allowedToMerge.getForm(formData, "allowed_to_merge"); + if (allowedToUnprotect != null) + allowedToUnprotect.getForm(formData, "allowed_to_unprotect"); - Response response = post(Response.Status.CREATED, formData.asMap(), + Response response = post(Response.Status.CREATED, formData.asMap(), "projects", getProjectIdOrPath(projectIdOrPath), "protected_branches"); return (response.readEntity(ProtectedBranch.class)); } + + /** + * Sets the code_owner_approval_required flag on the specified protected branch. + * + *

NOTE: This method is only available in GitLab Premium or higher.

+ * + *
GitLab Endpoint: PATCH /projects/:id/protected_branches/:branch_name?code_owner_approval_required=true
+ * + * @param projectIdOrPath the project in the form of an Long(ID), String(path), or Project instance + * @param branchName the name of the branch to protect, can be a wildcard + * @param codeOwnerApprovalRequired prevent pushes to this branch if it matches an item in the CODEOWNERS file. + * @return the branch info for the protected branch + * @throws GitLabApiException if any exception occurs + */ + public ProtectedBranch setCodeOwnerApprovalRequired(Object projectIdOrPath, String branchName, + Boolean codeOwnerApprovalRequired) throws GitLabApiException { + Form formData = new GitLabApiForm() + .withParam("code_owner_approval_required", codeOwnerApprovalRequired); + + Response response = patch(Response.Status.OK, formData.asMap(), + "projects", this.getProjectIdOrPath(projectIdOrPath), + "protected_branches", urlEncode(branchName)); + return (response.readEntity(ProtectedBranch.class)); + } } diff --git a/src/test/java/org/gitlab4j/api/TestProtectedBranchesApi.java b/src/test/java/org/gitlab4j/api/TestProtectedBranchesApi.java index 842fad9a7..7b0b480a2 100644 --- a/src/test/java/org/gitlab4j/api/TestProtectedBranchesApi.java +++ b/src/test/java/org/gitlab4j/api/TestProtectedBranchesApi.java @@ -1,7 +1,9 @@ package org.gitlab4j.api; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -45,7 +47,7 @@ public class TestProtectedBranchesApi extends AbstractIntegrationTest { @BeforeAll public static void setup() { - // Must setup the connection to the GitLab test server and get the test Project instance + // Must setup the connection to the GitLab test server and get the test Project instance gitLabApi = baseTestSetup(); testProject = getTestProject(); } @@ -124,4 +126,20 @@ public void testProtectBranch() throws GitLabApiException { assertTrue(branches.stream() .anyMatch((protectedBranch) -> protectedBranch.getName().equals(TEST_BRANCH_NAME))); } + + @Test + public void testSetCodeOwnerApprovalRequired() throws GitLabApiException { + + assumeTrue(testProject != null); + + ProtectedBranch branch = gitLabApi.getProtectedBranchesApi().getProtectedBranch(testProject, TEST_BRANCH_NAME); + assertNotNull(branch); + // current version returns null, but will return boolean (false) with newer Premium + assertFalse(branch.getCodeOwnerApprovalRequired() != null); + + // current version returns 404, but will return branch with "code_owner_approval_required = true" with newer Premium + GitLabApiException gae = assertThrowsExactly(GitLabApiException.class, + () -> gitLabApi.getProtectedBranchesApi().setCodeOwnerApprovalRequired(testProject, TEST_BRANCH_NAME, true)); + assertTrue(gae.getHttpStatus() == 404); + } }