diff --git a/.travis.yml b/.travis.yml index 87800ef2..257d4981 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: required services: - docker jdk: -- oraclejdk8 +- openjdk8 install: - ./mvnw -B -q -Pdocker-gitlab dependency:go-offline verify -DskipTests -Ddocker.skip script: diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 00000000..f8749726 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,2 @@ +Current +Fixed: GITHUB-297: The "connectTimeout" parameter is not configurable. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2977f0c9..75b52484 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,202 @@ -Copyright 2013-2014 Timothy Olshansky - -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. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..2977f0c9 --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +Copyright 2013-2014 Timothy Olshansky + +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. diff --git a/README.md b/README.md index fff8b844..4dea5cbd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Maven Central](https://img.shields.io/maven-central/v/org.gitlab/java-gitlab-api.svg)](http://mvnrepository.com/artifact/org.gitlab/java-gitlab-api) [![Build Status](https://travis-ci.org/timols/java-gitlab-api.svg?branch=master)](https://travis-ci.org/timols/java-gitlab-api) -A wrapper for the [Gitlab API](https://gitlab.org) written in Java. +A wrapper for the [Gitlab API](https://docs.gitlab.com/ee/api/) written in Java. [Documentation](https://timols.github.io/java-gitlab-api) is available in the form of [Javadocs](https://timols.github.io/java-gitlab-api) The major version indicates the API version of gitlab. diff --git a/build.gradle b/build.gradle index a9820738..a245c85b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { - tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - } + tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + } } plugins { @@ -14,8 +14,8 @@ plugins { sourceCompatibility = 1.8 targetCompatibility = 1.8 -group = 'org.gitlab' -version = '4.0.1-SNAPSHOT' +group = "org.gitlab" +version = "4.1.2-SNAPSHOT" repositories { mavenLocal() @@ -23,27 +23,42 @@ repositories { } dependencies { - compile(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.5.+') - compile(group: 'commons-io', name: 'commons-io', version: '2.4') - testCompile(group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3') - testCompile(group: 'junit', name: 'junit', version: '4.12') + compile(group: "org.slf4j", name: "slf4j-api", version: "1.8.0-beta2") + compile(group: "commons-io", name: "commons-io", version: "2.4") + compile(group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.5.+") + compile(group: "com.fasterxml.jackson.datatype", name: "jackson-datatype-jsr310", version: "2.5.1") + testCompile(group: "org.apache.logging.log4j", name: "log4j-api", version: "2.11.0") + testCompile(group: "org.apache.logging.log4j", name: "log4j-slf4j-impl", version: "2.11.0") + testCompile(group: "org.hamcrest", name: "hamcrest-all", version: "1.3") + testCompile(group: "org.mockito", name: "mockito-core", version: "2.18.3") + testCompile(group: "org.junit.jupiter", name: "junit-jupiter-api", version: "5.2.0") + testCompile(group: "junit", name: "junit", version: "4.12") + testRuntime(group: "org.junit.jupiter", name: "junit-jupiter-engine", version: "5.2.0") + testRuntime(group: "org.junit.vintage", name: "junit-vintage-engine", version: "5.2.0") } jar { - manifest { attributes 'Gradle-Version': gradle.gradleVersion } + manifest { attributes "Gradle-Version": gradle.gradleVersion } + from "LICENSE" + from "NOTICE" } install { - repositories.mavenInstaller { pom.artifactId = 'java-gitlab-api' } + repositories.mavenInstaller { pom.artifactId = "java-gitlab-api" } } task sourcesJar(type: Jar, dependsOn:classes) { - classifier = 'sources' - from sourceSets.main.allSource + classifier = "sources" + from sourceSets.main.allSource + from "LICENSE" + from "NOTICE" } artifacts { archives sourcesJar } -task wrapper(type: Wrapper) { - gradleVersion = '4.4' +test { + useJUnitPlatform { + includeEngines 'junit-jupiter' + includeEngines 'junit-vintage' + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b5166dad..f6b961fd 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c1c608ef..430dfabc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed May 13 23:55:44 CEST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip diff --git a/gradlew b/gradlew index 91a7e269..cccdd3d5 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730..e95643d6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/pom.xml b/pom.xml index ab24b40d..902f0424 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.gitlab java-gitlab-api - 4.0.1-SNAPSHOT + 4.1.2-SNAPSHOT Gitlab Java API Wrapper A Java wrapper for the Gitlab Git Hosting Server API @@ -38,6 +38,14 @@ luu@fuzzproductions.com Fuzz Productions + + Oleg Shaburov + shaburov.o.a@gmail.com + + Automation QA + + +3 + @@ -64,9 +72,11 @@ 1.8 UTF-8 UTF-8 - 2.20 + 2.21.0 180000 + 2.11.0 + 5.2.0 @@ -78,17 +88,30 @@ com.fasterxml.jackson.core jackson-databind - 2.5.3 + 2.9.10.5 commons-io commons-io 2.4 + - org.hamcrest - hamcrest-all - 1.3 + org.slf4j + slf4j-api + 1.8.0-beta2 + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} test @@ -97,9 +120,58 @@ 4.12 test + + org.junit.vintage + junit-vintage-engine + ${junit.jupiter.version} + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.mockito + mockito-core + 2.10.0 + test + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + test + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + test + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.5.1 + + + + + + + ./ + + LICENSE + NOTICE + + + org.apache.maven.plugins @@ -139,7 +211,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.0 attach-sources @@ -150,6 +222,26 @@ + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + @@ -225,7 +317,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.0 + 3.0.0-M1 -Xdoclint:none diff --git a/src/main/java/org/gitlab/api/GitlabAPI.java b/src/main/java/org/gitlab/api/GitlabAPI.java index a9806d23..c0c88fa7 100644 --- a/src/main/java/org/gitlab/api/GitlabAPI.java +++ b/src/main/java/org/gitlab/api/GitlabAPI.java @@ -5,6 +5,11 @@ import org.gitlab.api.http.GitlabHTTPRequestor; import org.gitlab.api.http.Query; import org.gitlab.api.models.*; +import org.gitlab.api.query.PaginationQuery; +import org.gitlab.api.query.PipelinesQuery; +import org.gitlab.api.query.ProjectsQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -18,20 +23,26 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Collection; +import static org.gitlab.api.http.Method.*; /** * Gitlab API Wrapper class * * @author @timols (Tim O) */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "WeakerAccess"}) public class GitlabAPI { + private static final Logger LOG = LoggerFactory.getLogger(GitlabAPI.class); + public static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - private static final String API_NAMESPACE = "/api/v4"; + + private static final String DEFAULT_API_NAMESPACE = "/api/v4"; private static final String PARAM_SUDO = "sudo"; + private static final String PARAM_WITH_PROJECTS = "with_projects"; private static final String PARAM_MAX_ITEMS_PER_PAGE = new Pagination().withPerPage(Pagination.MAX_ITEMS_PER_PAGE).toString(); private final String hostUrl; @@ -39,21 +50,29 @@ public class GitlabAPI { private final String apiToken; private final TokenType tokenType; private AuthMethod authMethod; + private final String apiNamespace; private boolean ignoreCertificateErrors = false; private Proxy proxy; - private int requestTimeout = 0; + private int defaultTimeout = 0; + private int readTimeout = defaultTimeout; + private int connectionTimeout = defaultTimeout; private String userAgent = GitlabAPI.class.getCanonicalName() + "/" + System.getProperty("java.version"); - private GitlabAPI(String hostUrl, String apiToken, TokenType tokenType, AuthMethod method) { + private GitlabAPI(String hostUrl, String apiToken, TokenType tokenType, AuthMethod method, String apiNamespace) { this.hostUrl = hostUrl.endsWith("/") ? hostUrl.replaceAll("/$", "") : hostUrl; this.apiToken = apiToken; this.tokenType = tokenType; this.authMethod = method; + this.apiNamespace = apiNamespace; + } + + private GitlabAPI(String hostUrl, String apiToken, TokenType tokenType, AuthMethod method) { + this(hostUrl, apiToken, tokenType, method, DEFAULT_API_NAMESPACE); } public static GitlabSession connect(String hostUrl, String username, String password) throws IOException { String tailUrl = GitlabSession.URL; - GitlabAPI api = connect(hostUrl, null, null, null); + GitlabAPI api = connect(hostUrl, null, null, (AuthMethod) null); return api.dispatch().with("login", username).with("password", password) .to(tailUrl, GitlabSession.class); } @@ -70,6 +89,14 @@ public static GitlabAPI connect(String hostUrl, String apiToken, TokenType token return new GitlabAPI(hostUrl, apiToken, tokenType, method); } + public static GitlabAPI connect(String hostUrl, String apiToken, TokenType tokenType, String apiNamespace) { + return new GitlabAPI(hostUrl, apiToken, tokenType, AuthMethod.HEADER, apiNamespace); + } + + public static GitlabAPI connect(String hostUrl, String apiToken, TokenType tokenType, AuthMethod method, String apiNamespace) { + return new GitlabAPI(hostUrl, apiToken, tokenType, method, apiNamespace); + } + public GitlabAPI ignoreCertificateErrors(boolean ignoreCertificateErrors) { this.ignoreCertificateErrors = ignoreCertificateErrors; return this; @@ -80,12 +107,50 @@ public GitlabAPI proxy(Proxy proxy) { return this; } + public int getResponseReadTimeout() { + return readTimeout; + } + + /** + * @deprecated use this.getResponseReadTimeout() method + */ + @Deprecated public int getRequestTimeout() { - return requestTimeout; + return getResponseReadTimeout(); + } + + /** + * @deprecated use this.setResponseReadTimeout(int readTimeout) method + */ + @Deprecated + public GitlabAPI setRequestTimeout(int readTimeout) { + setResponseReadTimeout(readTimeout); + return this; + } + + public GitlabAPI setResponseReadTimeout(int readTimeout) { + if (readTimeout < 0) { + LOG.warn("The value of the \"Response Read Timeout\" parameter can not be negative. " + + "The default value [{}] will be used.", defaultTimeout); + this.readTimeout = defaultTimeout; + } else { + this.readTimeout = readTimeout; + } + return this; + } + + public int getConnectionTimeout() { + return connectionTimeout; } - public GitlabAPI setRequestTimeout(int requestTimeout) { - this.requestTimeout = requestTimeout; + public GitlabAPI setConnectionTimeout(int connectionTimeout) { + if (connectionTimeout < 0) { + LOG.warn("The value of the \"Connection Timeout\" parameter can not be negative. " + + "The default value [{}] will be used.", defaultTimeout); + this.connectionTimeout = defaultTimeout; + } else { + this.connectionTimeout = connectionTimeout; + } return this; } @@ -94,7 +159,11 @@ public GitlabHTTPRequestor retrieve() { } public GitlabHTTPRequestor dispatch() { - return new GitlabHTTPRequestor(this).authenticate(apiToken, tokenType, authMethod).method("POST"); + return new GitlabHTTPRequestor(this).authenticate(apiToken, tokenType, authMethod).method(POST); + } + + public GitlabHTTPRequestor put() { + return new GitlabHTTPRequestor(this).authenticate(apiToken, tokenType, authMethod).method(PUT); } public boolean isIgnoreCertificateErrors() { @@ -109,7 +178,7 @@ public URL getAPIUrl(String tailAPIUrl) throws IOException { if (!tailAPIUrl.startsWith("/")) { tailAPIUrl = "/" + tailAPIUrl; } - return new URL(hostUrl + API_NAMESPACE + tailAPIUrl); + return new URL(hostUrl + apiNamespace + tailAPIUrl); } public URL getUrl(String tailAPIUrl) throws IOException { @@ -120,7 +189,11 @@ public URL getUrl(String tailAPIUrl) throws IOException { return new URL(hostUrl + tailAPIUrl); } - public List getUsers() throws IOException { + public String getHost() { + return hostUrl; + } + + public List getUsers() { String tailUrl = GitlabUser.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabUser[].class); } @@ -131,10 +204,10 @@ public List getUsers() throws IOException { * @param emailOrUsername Some portion of the email address or username * @return A non-null List of GitlabUser instances. If the search term is * null or empty a List with zero GitlabUsers is returned. - * @throws IOException + * @throws IOException on gitlab api call error */ public List findUsers(String emailOrUsername) throws IOException { - List users = new ArrayList(); + List users = new ArrayList<>(); if (emailOrUsername != null && !emailOrUsername.equals("")) { String tailUrl = GitlabUser.URL + "?search=" + emailOrUsername; GitlabUser[] response = retrieve().to(tailUrl, GitlabUser[].class); @@ -179,7 +252,8 @@ public GitlabUser getUserViaSudo(String username) throws IOException { * @param isAdmin Is Admin * @param can_create_group Can Create Group * @param skip_confirmation Skip Confirmation - * @return A GitlabUser + * @param external External + * @return A GitlabUser * @throws IOException on gitlab api call error * @see http://doc.gitlab.com/ce/api/users.html */ @@ -188,7 +262,7 @@ public GitlabUser createUser(String email, String password, String username, String twitter, String website_url, Integer projects_limit, String extern_uid, String extern_provider_name, String bio, Boolean isAdmin, Boolean can_create_group, - Boolean skip_confirmation) throws IOException { + Boolean skip_confirmation, Boolean external) throws IOException { Query query = new Query() .append("email", email) @@ -205,7 +279,8 @@ public GitlabUser createUser(String email, String password, String username, .appendIf("provider", extern_provider_name) .appendIf("bio", bio) .appendIf("admin", isAdmin) - .appendIf("can_create_group", can_create_group); + .appendIf("can_create_group", can_create_group) + .appendIf("external", external); String tailUrl = GitlabUser.USERS_URL + query.toString(); @@ -243,6 +318,7 @@ public GitlabUser createUser(CreateUserRequest request) throws IOException { * @param bio Bio * @param isAdmin Is Admin * @param can_create_group Can Create Group + * @param external External * @return The Updated User * @throws IOException on gitlab api call error */ @@ -251,7 +327,7 @@ public GitlabUser updateUser(Integer targetUserId, String fullName, String skypeId, String linkedIn, String twitter, String website_url, Integer projects_limit, String extern_uid, String extern_provider_name, - String bio, Boolean isAdmin, Boolean can_create_group) throws IOException { + String bio, Boolean isAdmin, Boolean can_create_group, Boolean external) throws IOException { Query query = new Query() .append("email", email) @@ -267,11 +343,12 @@ public GitlabUser updateUser(Integer targetUserId, .appendIf("provider", extern_provider_name) .appendIf("bio", bio) .appendIf("admin", isAdmin) - .appendIf("can_create_group", can_create_group); + .appendIf("can_create_group", can_create_group) + .appendIf("external", external); String tailUrl = GitlabUser.USERS_URL + "/" + targetUserId + query.toString(); - return retrieve().method("PUT").to(tailUrl, GitlabUser.class); + return retrieve().method(PUT).to(tailUrl, GitlabUser.class); } /** @@ -284,7 +361,7 @@ public void blockUser(Integer targetUserId) throws IOException { String tailUrl = GitlabUser.USERS_URL + "/" + targetUserId + GitlabUser.BLOCK_URL; - retrieve().method("POST").to(tailUrl, Void.class); + retrieve().method(POST).to(tailUrl, Void.class); } /** @@ -297,7 +374,7 @@ public void unblockUser(Integer targetUserId) throws IOException { String tailUrl = GitlabUser.USERS_URL + "/" + targetUserId + GitlabUser.UNBLOCK_URL; - retrieve().method("POST").to(tailUrl, Void.class); + retrieve().method(POST).to(tailUrl, Void.class); } /** @@ -320,6 +397,25 @@ public GitlabSSHKey createSSHKey(Integer targetUserId, String title, String key) return dispatch().to(tailUrl, GitlabSSHKey.class); } + /** + * Create a new ssh key for the authenticated user. + * + * @param title The title of the ssh key + * @param key The public key + * @return The new GitlabSSHKey + * @throws IOException on gitlab api call error + */ + public GitlabSSHKey createSSHKey(String title, String key) throws IOException { + + Query query = new Query() + .append("title", title) + .append("key", key); + + String tailUrl = GitlabUser.USER_URL + GitlabSSHKey.KEYS_URL + query.toString(); + + return dispatch().to(tailUrl, GitlabSSHKey.class); + } + /** * Delete user's ssh key * @@ -329,7 +425,7 @@ public GitlabSSHKey createSSHKey(Integer targetUserId, String title, String key) */ public void deleteSSHKey(Integer targetUserId, Integer targetKeyId) throws IOException { String tailUrl = GitlabUser.USERS_URL + "/" + targetUserId + GitlabSSHKey.KEYS_URL + "/" + targetKeyId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } @@ -365,23 +461,56 @@ public GitlabSSHKey getSSHKey(Integer keyId) throws IOException { */ public void deleteUser(Integer targetUserId) throws IOException { String tailUrl = GitlabUser.USERS_URL + "/" + targetUserId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } public GitlabGroup getGroup(Integer groupId) throws IOException { return getGroup(groupId.toString()); } + public GitlabGroup getGroupWithoutProjects(Integer groupId) throws IOException { + return getGroupWithoutProjects(groupId.toString()); + } + /** - * Get a group by path + * Get a group by path. Don't include the projects. * * @param path Path of the group - * @return - * @throws IOException + * @return {@link GitlabGroup} object + * + * @throws IOException on gitlab api call error + */ + public GitlabGroup getGroupWithoutProjects(String path) throws IOException { + return getGroup(path, false); + } + + /** + * Get a group by path, including its projects. + * + * @param path Path of the group + * @return {@link GitlabGroup} object + * + * @throws IOException on gitlab api call error */ public GitlabGroup getGroup(String path) throws IOException { + return getGroup(path, true); + } + + /** + * Get a group by path + * + * @param path Path of the group + * @param withProjects If true, include the projects + * @return {@link GitlabGroup} object + * + * @throws IOException on gitlab api call error + */ + public GitlabGroup getGroup(String path, boolean withProjects) throws IOException { String tailUrl = GitlabGroup.URL + "/" + URLEncoder.encode(path, "UTF-8"); - return retrieve().to(tailUrl, GitlabGroup.class); + Query query = new Query() + .append(PARAM_WITH_PROJECTS, "" + withProjects); + + return retrieve().to(tailUrl + query.toString(), GitlabGroup.class); } public List getGroups() throws IOException { @@ -406,9 +535,8 @@ public List getGroupsViaSudo(String username, Pagination pagination * * @param group the target group * @return a list of projects for the group - * @throws IOException */ - public List getGroupProjects(GitlabGroup group) throws IOException { + public List getGroupProjects(GitlabGroup group) { return getGroupProjects(group.getId()); } @@ -417,9 +545,8 @@ public List getGroupProjects(GitlabGroup group) throws IOExceptio * * @param groupId the target group's id. * @return a list of projects for the group - * @throws IOException */ - public List getGroupProjects(Integer groupId) throws IOException { + public List getGroupProjects(Integer groupId) { String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabProject.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabProject[].class); } @@ -429,9 +556,8 @@ public List getGroupProjects(Integer groupId) throws IOException * * @param group The GitLab Group * @return The Group Members - * @throws IOException on gitlab api call error */ - public List getGroupMembers(GitlabGroup group) throws IOException { + public List getGroupMembers(GitlabGroup group) { return getGroupMembers(group.getId()); } @@ -440,9 +566,8 @@ public List getGroupMembers(GitlabGroup group) throws IOExcep * * @param groupId The id of the GitLab Group * @return The Group Members - * @throws IOException on gitlab api call error */ - public List getGroupMembers(Integer groupId) throws IOException { + public List getGroupMembers(Integer groupId) { String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabGroupMember.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabGroupMember[].class); } @@ -559,6 +684,65 @@ public GitlabGroup createGroup(String name, String path, String ldapCn, GitlabAc return dispatch().to(tailUrl, GitlabGroup.class); } + /** + * Creates a Group + * + * @param group The gitlab Group object + * @param sudoUser The user to create the group on behalf of + * + * @return The GitLab Group + * @throws IOException on gitlab api call error + */ + public GitlabGroup createGroup(GitlabGroup group, GitlabUser sudoUser) throws IOException { + + Query query = new Query() + .append("name", group.getName()) + .append("path", group.getPath()) + .appendIf("description", group.getDescription()) + .appendIf("membership_lock", group.getMembershipLock()) + .appendIf("share_with_group_lock", group.getShareWithGroupLock()) + .appendIf("visibility", group.getVisibility().toString()) + .appendIf("lfs_enabled", group.isLfsEnabled()) + .appendIf("request_access_enabled", group.isRequestAccessEnabled()) + .appendIf("shared_runners_minutes_limit", group.getSharedRunnersMinutesLimit()) + .appendIf("ldap_cn", group.getLdapCn()) + .appendIf("ldap_access", group.getLdapAccess()) + .appendIf(PARAM_SUDO, sudoUser != null ? sudoUser.getId() : null); + + String tailUrl = GitlabGroup.URL + query.toString(); + + return dispatch().to(tailUrl, GitlabGroup.class); + } + + /** + * Updates a Group + * + * @param group the group object + * @param sudoUser The user to create the group on behalf of + * @return The GitLab Group + * @throws IOException on gitlab api call error + */ + public GitlabGroup updateGroup(GitlabGroup group, GitlabUser sudoUser) throws IOException { + + Query query = new Query() + .appendIf("name", group.getName()) + .appendIf("path", group.getPath()) + .appendIf("description", group.getDescription()) + .appendIf("membership_lock", group.getMembershipLock()) + .appendIf("share_with_group_lock", group.getShareWithGroupLock()) + .appendIf("visibility", group.getVisibility().toString()) + .appendIf("lfs_enabled", group.isLfsEnabled()) + .appendIf("request_access_enabled", group.isRequestAccessEnabled()) + .appendIf("shared_runners_minutes_limit", group.getSharedRunnersMinutesLimit()) + .appendIf("ldap_cn", group.getLdapCn()) + .appendIf("ldap_access", group.getLdapAccess()) + .appendIf(PARAM_SUDO, sudoUser != null ? sudoUser.getId() : null); + + String tailUrl = GitlabGroup.URL + "/" + group.getId() + query.toString(); + + return retrieve().method(PUT).to(tailUrl, GitlabGroup.class); + } + /** * Add a group member. * @@ -610,7 +794,7 @@ public void deleteGroupMember(GitlabGroup group, GitlabUser user) throws IOExcep */ public void deleteGroupMember(Integer groupId, Integer userId) throws IOException { String tailUrl = GitlabGroup.URL + "/" + groupId + "/" + GitlabGroupMember.URL + "/" + userId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -621,27 +805,25 @@ public void deleteGroupMember(Integer groupId, Integer userId) throws IOExceptio */ public void deleteGroup(Integer groupId) throws IOException { String tailUrl = GitlabGroup.URL + "/" + groupId; - retrieve().method("DELETE").to(tailUrl, Void.class); - } - - /** - * - * Get's all projects in Gitlab, requires sudo user - * - * @return A list of gitlab projects - * @throws IOException - */ - public List getAllProjects() throws IOException { - String tailUrl = GitlabProject.URL; - return retrieve().getAll(tailUrl, GitlabProject[].class); - } - - /** - * Get Project by project Id - * - * @param projectId - * @return - * @throws IOException + retrieve().method(DELETE).to(tailUrl, Void.class); + } + + /** + * Get's all projects in Gitlab, requires sudo user + * + * @return A list of gitlab projects + */ + public List getAllProjects() { + String tailUrl = GitlabProject.URL; + return retrieve().getAll(tailUrl, GitlabProject[].class); + } + + /** + * Get Project by project Id + * + * @param projectId - gitlab project Id + * @return {@link GitlabProject} + * @throws IOException on gitlab api call error */ public GitlabProject getProject(Serializable projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId); @@ -676,21 +858,63 @@ public String getProjectJson(String namespace, String projectName) throws IOExce * Get a list of projects accessible by the authenticated user. * * @return A list of gitlab projects - * @throws IOException */ - public List getProjects() throws IOException { + public List getProjects() { String tailUrl = GitlabProject.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabProject[].class); } + /** + * Get a list of projects of size perPage accessible by the authenticated user. + * + * @param page page offset. + * @param perPage number elements to get after page offset. + * @return A list of gitlab projects + * @throws IOException on Gitlab API call error + */ + public List getProjectsWithPagination(int page, int perPage) throws IOException { + Pagination pagination = new Pagination() + .withPage(page) + .withPerPage(perPage); + return getProjectsWithPagination(pagination); + } + + /** + * Get a list of projects accessible by the authenticated user. + * + * @return A list of gitlab projects + */ + public List getProjects(ProjectsQuery projectsQuery) { + String tailUrl = GitlabProject.URL + projectsQuery; + return retrieve().getAll(tailUrl, GitlabProject[].class); + } + + /** + * Get a list of projects by pagination accessible by the authenticated user. + * + * @param pagination + * @return + * @throws IOException on gitlab api call error + */ + public List getProjectsWithPagination(Pagination pagination) throws IOException { + StringBuilder tailUrl = new StringBuilder(GitlabProject.URL); + + if (pagination != null) { + Query query = pagination.asQuery(); + tailUrl.append(query.toString()); + } + + return Arrays.asList(retrieve().method(GET).to(tailUrl.toString(), GitlabProject[].class)); + } + /** * Get a list of projects owned by the authenticated user. * * @return A list of gitlab projects - * @throws IOException + * @throws IOException on gitlab api call error */ public List getOwnedProjects() throws IOException { - Query query = new Query().append("owner", "true"); + Query query = new Query().append("owned", "true"); query.mergeWith(new Pagination().withPerPage(Pagination.MAX_ITEMS_PER_PAGE).asQuery()); String tailUrl = GitlabProject.URL + query.toString(); return retrieve().getAll(tailUrl, GitlabProject[].class); @@ -700,7 +924,7 @@ public List getOwnedProjects() throws IOException { * Get a list of projects that the authenticated user is a member of. * * @return A list of gitlab projects - * @throws IOException + * @throws IOException on gitlab api call error */ public List getMembershipProjects() throws IOException { Query query = new Query().append("membership", "true"); @@ -713,7 +937,7 @@ public List getMembershipProjects() throws IOException { * Get a list of projects starred by the authenticated user. * * @return A list of gitlab projects - * @throws IOException + * @throws IOException on gitlab api call error */ public List getStarredProjects() throws IOException { Query query = new Query().append("starred", "true"); @@ -726,7 +950,7 @@ public List getStarredProjects() throws IOException { * Get a list of projects accessible by the authenticated user. * * @return A list of gitlab projects - * @throws IOException + * @throws IOException on gitlab api call error */ public List getProjectsViaSudo(GitlabUser user) throws IOException { Query query = new Query() @@ -736,14 +960,51 @@ public List getProjectsViaSudo(GitlabUser user) throws IOExceptio return retrieve().getAll(tailUrl, GitlabProject[].class); } + /** + * Get a list of projects of perPage elements accessible by the authenticated user given page offset + * + * @param user Gitlab User to invoke sudo with + * @param page Page offset + * @param perPage Number of elements to get after page offset + * @return A list of gitlab projects + * @throws IOException Gitlab API call error + */ + public List getProjectsViaSudoWithPagination(GitlabUser user, int page, int perPage) throws IOException { + Pagination pagination = new Pagination() + .withPage(page) + .withPerPage(perPage); + return getProjectsViaSudoWithPagination(user, pagination); + } + + /** + * Get a list of projects of with Pagination. + * + * @param user Gitlab User to invoke sudo with + * @param pagination + * @return A list of gitlab projects + * @throws IOException Gitlab API call error + */ + public List getProjectsViaSudoWithPagination(GitlabUser user, Pagination pagination) throws IOException { + StringBuilder tailUrl = new StringBuilder(GitlabProject.URL); + + Query query = new Query() + .appendIf(PARAM_SUDO, user.getId()); + + if (pagination != null) { + query.mergeWith(pagination.asQuery()); + } + + tailUrl.append(query.toString()); + return Arrays.asList(retrieve().method(GET).to(tailUrl.toString(), GitlabProject[].class)); + } + /** * Get a list of the namespaces of the authenticated user. * If the user is an administrator, a list of all namespaces in the GitLab instance is shown. * * @return A list of gitlab namespace - * @throws IOException */ - public List getNamespaces() throws IOException { + public List getNamespaces() { String tailUrl = GitlabNamespace.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabNamespace[].class); } @@ -754,21 +1015,84 @@ public List getNamespaces() throws IOException { * @param project * @param file * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabUpload uploadFile(GitlabProject project, File file) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(project.getId()) + GitlabUpload.URL; return dispatch().withAttachment("file", file).to(tailUrl, GitlabUpload.class); } + /** + * Get a project's pipeline + * + * @param project the project + * @param pipeline the pipeline + * @return The project pipeline + */ + public GitlabPipeline getProjectPipeline(GitlabProject project, GitlabPipeline pipeline) throws IOException { + return getProjectPipeline(project.getId(), pipeline.getId()); + } + + /** + * Get a project's pipeline + * + * @param projectId the project id + * @param pipelineId the pipeline id + * @return The project pipeline + */ + public GitlabPipeline getProjectPipeline(Integer projectId, Integer pipelineId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabPipeline.URL + "/" + sanitizeId(pipelineId, "pipelineId"); + return retrieve().to(tailUrl, GitlabPipeline.class); + } + + /** + * Get a list of a project's pipelines in Gitlab + * + * @param project the project + * @return A list of project pipelines + */ + public List getProjectPipelines(GitlabProject project) { + return getProjectPipelines(project.getId()); + } + + /** + * Get a list of a project's pipelines in Gitlab + * + * @param projectId the project id + * @return A list of project pipelines + */ + public List getProjectPipelines(Integer projectId) { + return getProjectPipelines(projectId, new PipelinesQuery().withPerPage(PaginationQuery.MAX_ITEMS_PER_PAGE)); + } + + /** + * Get a list of a project's pipelines in Gitlab + * + * @param project the project + * @return A list of project pipelines + */ + public List getProjectPipelines(GitlabProject project, PipelinesQuery pipelinesQuery) { + return getProjectPipelines(project.getId(), pipelinesQuery); + } + + /** + * Get a list of a project's pipelines in Gitlab + * + * @param projectId the project id + * @return A list of project pipelines + */ + public List getProjectPipelines(Integer projectId, PipelinesQuery pipelinesQuery) { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabPipeline.URL + pipelinesQuery; + return retrieve().getAll(tailUrl, GitlabPipeline[].class); + } + /** * Gets a list of a project's jobs in Gitlab * * @param project the project * @return A list of project jobs - * @throws IOException */ - public List getProjectJobs(GitlabProject project) throws IOException { + public List getProjectJobs(GitlabProject project) { return getProjectJobs(project.getId()); } @@ -777,23 +1101,48 @@ public List getProjectJobs(GitlabProject project) throws IOException * * @param projectId the project id * @return A list of project jobs - * @throws IOException */ - public List getProjectJobs(Integer projectId) throws IOException { + public List getProjectJobs(Integer projectId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabJob[].class); } + /** + * Run pipeline for selected project and branch + * @param project project + * @param ref branch + * @param variables pipeline variables + * @return Created pipeline + * @throws IOException + */ + public GitlabPipeline runPipeline(GitlabProject project, String ref, List variables) throws IOException { + return runPipeline(project.getId(), ref, variables); + } + + + /** + * Run pipeline for selected project and branch + * @param projectId project's id + * @param ref branch + * @param variables pipeline variables + * @return Created pipeline + * @throws IOException + */ + public GitlabPipeline runPipeline(Integer projectId, String ref, List variables) throws IOException { + Query query = new Query().appendIf("ref", ref); + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabPipeline.CREATE_URL + query.toString(); + return dispatch().with("variables", variables).to(tailUrl, GitlabPipeline.class); + } + /** * Gets a list of project's jobs of the given pipeline in Gitlab * * @param project the project * @param pipelineId * @return A list of project jobs - * @throws IOException */ - public List getPipelineJobs(GitlabProject project, Integer pipelineId) throws IOException { + public List getPipelineJobs(GitlabProject project, Integer pipelineId) { return getPipelineJobs(project.getId(), pipelineId); } @@ -803,7 +1152,6 @@ public List getPipelineJobs(GitlabProject project, Integer pipelineId * @param projectId * @param pipelineId * @return A list of project jobs - * @throws IOException */ public List getPipelineJobs(Integer projectId, Integer pipelineId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabPipeline.URL + "/" + sanitizeId(pipelineId, "PipelineID") + GitlabJob.URL + PARAM_MAX_ITEMS_PER_PAGE; @@ -817,7 +1165,7 @@ public List getPipelineJobs(Integer projectId, Integer pipelineId) { * @param projectId * @param jobId * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabJob cancelJob(Integer projectId, Integer jobId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + "/" + sanitizeId(jobId, "JobID") + "/cancel"; @@ -830,7 +1178,7 @@ public GitlabJob cancelJob(Integer projectId, Integer jobId) throws IOException * @param projectId * @param jobId * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabJob retryJob(Integer projectId, Integer jobId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + "/" + sanitizeId(jobId, "JobID") + "/retry"; @@ -843,7 +1191,7 @@ public GitlabJob retryJob(Integer projectId, Integer jobId) throws IOException { * @param projectId * @param jobId * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabJob eraseJob(Integer projectId, Integer jobId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + "/" + sanitizeId(jobId, "JobID") + "/erase"; @@ -857,7 +1205,7 @@ public GitlabJob eraseJob(Integer projectId, Integer jobId) throws IOException { * @param projectId * @param jobId * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabJob playJob(Integer projectId, Integer jobId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + "/" + sanitizeId(jobId, "JobID") + "/play"; @@ -871,7 +1219,7 @@ public GitlabJob playJob(Integer projectId, Integer jobId) throws IOException { * @param projectId the project id * @param jobId the build id * @return A list of project jobs - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabJob getProjectJob(Integer projectId, Integer jobId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabJob.URL + "/" + jobId; @@ -956,7 +1304,8 @@ public GitlabProject createProject(GitlabProject project) throws IOException { .appendIf("request_access_enabled", project.isRequestAccessEnabled()) .appendIf("repository_storage", project.getRepositoryStorage()) .appendIf("approvals_before_merge", project.getApprovalsBeforeMerge()) - .appendIf("printing_merge_request_link_enabled", project.isPrintingMergeRequestLinkEnabled()); + .appendIf("printing_merge_request_link_enabled", project.isPrintingMergeRequestLinkEnabled()) + .appendIf("initialize_with_readme",project.isInitializeWithReadme()); GitlabNamespace namespace = project.getNamespace(); if (namespace != null) { @@ -1105,17 +1454,30 @@ public GitlabProject createUserProject(Integer userId, String name, String descr /** * @param namespace The namespace of the fork * @param projectId ProjectId of the project forked + * @param path The path that will be assigned to the resultant project after forking. (Optional) + * @param name The name that will be assigned to the resultant project after forking. (Optional) * @return The new Gitlab Project * @throws IOException on gitlab api call error */ - public GitlabProject createFork(String namespace, Integer projectId) throws IOException { + public GitlabProject createFork(String namespace, Integer projectId, String path, String name) throws IOException { Query query = new Query() - .appendIf("id", projectId) - .append("namespace", namespace); - String tailUrl = GitlabProject.URL + "/" + projectId + "/fork"; + .appendIf("namespace", namespace) + .appendIf("path", path) + .appendIf("name", name); + String tailUrl = GitlabProject.URL + "/" + projectId + "/fork" + query.toString(); return dispatch().to(tailUrl, GitlabProject.class); } + /** + * @param namespace The namespace of the fork + * @param projectId ProjectId of the project forked + * @return The new Gitlab Project + * @throws IOException on gitlab api call error + */ + public GitlabProject createFork(String namespace, Integer projectId) throws IOException { + return createFork(namespace, projectId, null, null); + } + /** * @param namespace The namespace of the fork * @param gitlabProject The project forked @@ -1168,7 +1530,7 @@ public GitlabProject updateProject( String tailUrl = GitlabProject.URL + "/" + projectId + query.toString(); - return retrieve().method("PUT").to(tailUrl, GitlabProject.class); + return retrieve().method(PUT).to(tailUrl, GitlabProject.class); } /** @@ -1179,7 +1541,7 @@ public GitlabProject updateProject( */ public void deleteProject(Serializable projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId); - retrieve().method("DELETE").to(tailUrl, null); + retrieve().method(DELETE).to(tailUrl, null); } public List getOpenMergeRequests(Serializable projectId) throws IOException { @@ -1252,27 +1614,27 @@ public List getMergeRequestsWithStatus(GitlabProject project return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } - public List getMergeRequests(Serializable projectId) throws IOException { + public List getMergeRequests(Serializable projectId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabMergeRequest.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } - public List getMergeRequests(Serializable projectId, Pagination pagination) throws IOException { + public List getMergeRequests(Serializable projectId, Pagination pagination) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabMergeRequest.URL + pagination.toString(); return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } - public List getMergeRequests(GitlabProject project) throws IOException { + public List getMergeRequests(GitlabProject project) { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabMergeRequest.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } - public List getMergeRequests(GitlabProject project, Pagination pagination) throws IOException { + public List getMergeRequests(GitlabProject project, Pagination pagination) { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabMergeRequest.URL + pagination.toString(); return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } - public List getAllMergeRequests(GitlabProject project) throws IOException { + public List getAllMergeRequests(GitlabProject project) { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabMergeRequest.URL; return retrieve().getAll(tailUrl, GitlabMergeRequest[].class); } @@ -1287,6 +1649,36 @@ public GitlabMergeRequestApprovals getMergeRequestApprovals(GitlabMergeRequest m return retrieve().to(tailUrl, GitlabMergeRequestApprovals.class); } + /** + * Set the number of required approvers. + * + * EE only. + */ + public GitlabMergeRequestApprovals setMergeRequestApprovals(GitlabMergeRequest mr, int count) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(mr.getProjectId()) + + GitlabMergeRequest.URL + "/" + mr.getIid() + GitlabMergeRequestApprovals.URL; + return dispatch() + .with("approvals_required", count) + .to(tailUrl, GitlabMergeRequestApprovals.class); + } + + /** + * Set the list of approvers. Important: Approvers and groups not + * in the request will be removed + * + * EE only. + */ + public GitlabMergeRequestApprovals setMergeRequestApprovers(GitlabMergeRequest mr, + Collection userApproverIds, + Collection groupApproverIds) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(mr.getProjectId()) + + GitlabMergeRequest.URL + "/" + mr.getIid() + GitlabMergeRequestApprovals.APPROVERS_URL; + return put() + .with("approver_ids", userApproverIds) + .with("approver_group_ids", groupApproverIds) + .to(tailUrl, GitlabMergeRequestApprovals.class); + } + /** * Cherry picks a commit. * @@ -1310,7 +1702,7 @@ public GitlabCommit cherryPick(GitlabProject project, String sha, String targetB * Return Merge Request. * * @param projectId The id of the project - * @param mergeRequestIid The iid of the merge request + * @param mergeRequestIid The internal id of the merge request * @return the Gitlab Merge Request * @throws IOException on gitlab api call error */ @@ -1322,21 +1714,29 @@ public GitlabMergeRequest getMergeRequestByIid(Serializable projectId, Integer m /** * Return a Merge Request including its changes. * - * @param projectId The id of the project - * @param mergeRequestId The id of the merge request + * @param projectId The id of the project + * @param mergeRequestIid The internal id of the merge request * @return the Gitlab Merge Request * @throws IOException on gitlab api call error */ - public GitlabMergeRequest getMergeRequestChanges(Serializable projectId, Integer mergeRequestId) throws IOException { - String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabMergeRequest.URL + "/" + mergeRequestId + "/changes"; + public GitlabMergeRequest getMergeRequestChanges(Serializable projectId, + Integer mergeRequestIid) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + + GitlabMergeRequest.URL + "/" + mergeRequestIid + "/changes"; return retrieve().to(tailUrl, GitlabMergeRequest.class); } - public GitlabMergeRequest getMergeRequest(GitlabProject project, Integer mergeRequestId) throws IOException { - String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabMergeRequest.URL + "/" + mergeRequestId; + public GitlabMergeRequest getMergeRequest(Serializable projectId, + Integer mergeRequestIid) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + + GitlabMergeRequest.URL + "/" + mergeRequestIid; return retrieve().to(tailUrl, GitlabMergeRequest.class); } + public GitlabMergeRequest getMergeRequest(GitlabProject project, + Integer mergeRequestIid) throws IOException { + return getMergeRequest(project.getId(), mergeRequestIid); + } /** * Create a new MergeRequest @@ -1367,18 +1767,18 @@ public GitlabMergeRequest createMergeRequest(Serializable projectId, String sour /** * Updates a Merge Request * - * @param projectId The id of the project - * @param mergeRequestId The id of the merge request to update - * @param targetBranch The target branch of the merge request, otherwise null to leave it untouched - * @param assigneeId The id of the assignee, otherwise null to leave it untouched - * @param title The title of the merge request, otherwise null to leave it untouched - * @param description The description of the merge request, otherwise null to leave it untouched - * @param stateEvent The state (close|reopen|merge) of the merge request, otherwise null to leave it untouched - * @param labels A comma separated list of labels, otherwise null to leave it untouched + * @param projectId The id of the project + * @param mergeRequestIid The internal id of the merge request to update + * @param targetBranch The target branch of the merge request, otherwise null to leave it untouched + * @param assigneeId The id of the assignee, otherwise null to leave it untouched + * @param title The title of the merge request, otherwise null to leave it untouched + * @param description The description of the merge request, otherwise null to leave it untouched + * @param stateEvent The state (close|reopen|merge) of the merge request, otherwise null to leave it untouched + * @param labels A comma separated list of labels, otherwise null to leave it untouched * @return the Merge Request * @throws IOException on gitlab api call error */ - public GitlabMergeRequest updateMergeRequest(Serializable projectId, Integer mergeRequestId, String targetBranch, + public GitlabMergeRequest updateMergeRequest(Serializable projectId, Integer mergeRequestIid, String targetBranch, Integer assigneeId, String title, String description, String stateEvent, String labels) throws IOException { Query query = new Query() @@ -1389,59 +1789,323 @@ public GitlabMergeRequest updateMergeRequest(Serializable projectId, Integer mer .appendIf("state_event", stateEvent) .appendIf("labels", labels); - String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabMergeRequest.URL + "/" + mergeRequestId + query.toString(); + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + + GitlabMergeRequest.URL + "/" + mergeRequestIid + query.toString(); - return retrieve().method("PUT").to(tailUrl, GitlabMergeRequest.class); + return retrieve().method(PUT).to(tailUrl, GitlabMergeRequest.class); } /** * @param project The Project - * @param mergeRequestId Merge Request ID + * @param mergeRequestIid Merge Request internal ID * @param mergeCommitMessage optional merge commit message. Null if not set * @return new merge request status * @throws IOException on gitlab api call error */ - public GitlabMergeRequest acceptMergeRequest(GitlabProject project, Integer mergeRequestId, String mergeCommitMessage) throws IOException { - String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabMergeRequest.URL + "/" + mergeRequestId + "/merge"; - GitlabHTTPRequestor requestor = retrieve().method("PUT"); - requestor.with("id", project.getId()); - requestor.with("merge_request_id", mergeRequestId); + public GitlabMergeRequest acceptMergeRequest(GitlabProject project, Integer mergeRequestIid, String mergeCommitMessage) throws IOException { + return acceptMergeRequest(project.getId(), mergeRequestIid, mergeCommitMessage); + } + + public GitlabMergeRequest acceptMergeRequest(Serializable projectId, Integer mergeRequestIid, String mergeCommitMessage) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + + GitlabMergeRequest.URL + "/" + mergeRequestIid + "/merge"; + GitlabHTTPRequestor requestor = retrieve().method(PUT); + requestor.with("id", projectId); + requestor.with("merge_request_iid", mergeRequestIid); if (mergeCommitMessage != null) requestor.with("merge_commit_message", mergeCommitMessage); return requestor.to(tailUrl, GitlabMergeRequest.class); } /** - * Get a Note from a Merge Request. + * Get a Note from a Merge Request. + * + * @param mergeRequest The merge request + * @param noteId The id of the note + * @return the Gitlab Note + * @throws IOException on gitlab api call error + */ + public GitlabNote getNote(GitlabMergeRequest mergeRequest, + Integer noteId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabNote.URL + "/" + noteId; + + return retrieve().to(tailUrl, GitlabNote.class); + } + + public List getNotes(GitlabMergeRequest mergeRequest) throws IOException { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabNote.URL; + + GitlabNote[] notes = retrieve().to(tailUrl, GitlabNote[].class); + return Arrays.asList(notes); + } + + public List getAllNotes(GitlabMergeRequest mergeRequest) { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabNote.URL + PARAM_MAX_ITEMS_PER_PAGE; + + return retrieve().getAll(tailUrl, GitlabNote[].class); + } + + /** + * Get a discussion by id from a merge request. + * https://docs.gitlab.com/ce/api/discussions.html#get-single-merge-request-discussion + * + * @param mergeRequest to fetch the discussion from. + * @param discussionId The id of the discussion. + * + * @return The GitLab discussion identified by the given id. + * @throws IOException on a GitLab api call error + */ + public GitlabDiscussion getDiscussion(GitlabMergeRequest mergeRequest, + int discussionId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL + "/" + discussionId; + return retrieve().to(tailUrl, GitlabDiscussion.class); + } + + /** + * Get the discussions from a merge request. + * https://docs.gitlab.com/ce/api/discussions.html#list-project-merge-request-discussions + * + * @param mergeRequest to fetch the discussions from. + * + * @return The discussions contained in the given merge request. + * @throws IOException on a GitLab api call error + */ + public List getDiscussions(GitlabMergeRequest mergeRequest) throws IOException { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL; + + GitlabDiscussion[] discussions = retrieve().to(tailUrl, GitlabDiscussion[].class); + return Arrays.asList(discussions); + } + + /** + * Create a discussion just with the required arguments. + * + * @param mergeRequest The merge request where the discussion is created. + * @param body The content of a discussion. + * @param positionBaseSha The base commit SHA in the source branch. + * @param positionStartSha The SHA referencing the commit in the target branch. + * @param positionHeadSha The SHA referencing the HEAD of this merge request. + * + * @return The created discussion object. + * @throws IOException on a GitLab api call error + */ + public GitlabDiscussion createDiscussion(GitlabMergeRequest mergeRequest, + String body, String positionBaseSha, String positionStartSha, + String positionHeadSha) throws IOException { + return createTextDiscussion(mergeRequest, body, null, + positionBaseSha, positionStartSha, positionHeadSha, + null, null, null, null); + } + + /** + * Create a new discussion with position type text on the given merge request. + * https://docs.gitlab.com/ce/api/discussions.html#create-new-merge-request-discussion + * + * @param mergeRequest The merge request where the discussion is created. + * @param body The content of a discussion. + * @param position The position when creating a diff note. (hash) + * @param positionBaseSha The base commit SHA in the source branch. + * @param positionStartSha The SHA referencing the commit in the target branch. + * @param positionHeadSha The SHA referencing the HEAD of this merge request. + * @param positionNewPath The file path after the change. + * @param positionNewLine The Line number after change + * @param positionOldPath The file path before the change. + * @param positionOldLine The Line number before change. + * + * @return The created discussion object. + * @throws IOException on a GitLab api call error + */ + public GitlabDiscussion createTextDiscussion(GitlabMergeRequest mergeRequest, + String body, String position, String positionBaseSha, String positionStartSha, + String positionHeadSha, String positionNewPath, Integer positionNewLine, + String positionOldPath, Integer positionOldLine) throws IOException { + checkRequiredCreateDiscussionArguments(body, positionBaseSha, positionStartSha, positionHeadSha); + Query query = new Query() + .append("body", body) + .appendIf("position", position) + .append("position[base_sha]", positionBaseSha) + .append("position[start_sha]", positionStartSha) + .append("position[head_sha]", positionHeadSha) + .append("position[position_type]", "text") + .appendIf("position[new_path]", positionNewPath) + .appendIf("position[new_line]", positionNewLine) + .appendIf("position[old_path]", positionOldPath) + .appendIf("position[old_line]", positionOldLine); + + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL + query.toString(); + + return dispatch().to(tailUrl, GitlabDiscussion.class); + } + + /** + * Create a new discussion with position type image on the given merge request. + * https://docs.gitlab.com/ce/api/discussions.html#create-new-merge-request-discussion + * + * @param mergeRequest The merge request where the discussion is created. + * @param body The content of a discussion. + * @param position The position when creating a diff note. (hash) + * @param positionBaseSha The base commit SHA in the source branch. + * @param positionStartSha The SHA referencing the commit in the target branch. + * @param positionHeadSha The SHA referencing the HEAD of this merge request. + * @param positionNewPath The file path after the change. + * @param positionOldPath The file path before the change. + * @param positionWidth The width of the image. + * @param positionHeight The height of the image. + * @param positionX The X coordinate. + * @param positionY The Y coordinate. + * + * @return The created discussion object. + * @throws IOException on a GitLab api call error + */ + public GitlabDiscussion createImageDiscussion( + GitlabMergeRequest mergeRequest, String body, String position, + String positionBaseSha, String positionStartSha, + String positionHeadSha, String positionNewPath, String positionOldPath, + Integer positionWidth, Integer positionHeight, Integer positionX, + Integer positionY + ) throws IOException { + checkRequiredCreateDiscussionArguments(body, positionBaseSha, positionStartSha, positionHeadSha); + Query query = new Query() + .append("body", body) + .appendIf("position", position) + .append("position[base_sha]", positionBaseSha) + .append("position[start_sha]", positionStartSha) + .append("position[head_sha]", positionHeadSha) + .append("position[position_type]", "image") + .appendIf("position[new_path]", positionNewPath) + .appendIf("position[old_path]", positionOldPath) + .appendIf("position[width]", positionWidth) + .appendIf("position[height]", positionHeight) + .appendIf("position[x]", positionX) + .appendIf("position[y]", positionY); + + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL + query.toString(); + + return dispatch().to(tailUrl, GitlabDiscussion.class); + } + + /** + * Check if the required arguments to create a discussion are present and + * contain values. + * + * @param body The content of a discussion. + * @param positionBaseSha The base commit SHA in the source branch. + * @param positionStartSha The SHA referencing commit in target branch + * @param positionHeadSha The SHA referencing HEAD of this merge request + */ + private void checkRequiredCreateDiscussionArguments(String body, + String positionBaseSha, String positionStartSha, String positionHeadSha) { + if (body == null || body.isEmpty()) { + throw new IllegalArgumentException("Missing required argument 'body'!"); + } else if (positionBaseSha == null || positionBaseSha.isEmpty()) { + throw new IllegalArgumentException("Missing required argument 'positionBaseSha'!"); + } else if (positionStartSha == null || positionStartSha.isEmpty()) { + throw new IllegalArgumentException("Missing required argument 'positionStartSha'!"); + } else if (positionHeadSha == null || positionHeadSha.isEmpty()) { + throw new IllegalArgumentException("Missing required argument 'positionHeadSha'!"); + } + } + + /** + * Resolve or unresolve a whole discussion of a merge request. * - * @param mergeRequest The merge request - * @param noteId The id of the note - * @return the Gitlab Note - * @throws IOException on gitlab api call error + * @param mergeRequest The merge request of the discussion. + * @param discussionId The id of the discussion to resolve. + * @param resolved Resolve or unresolve the note. + * + * @return The discussion object. + * @throws IOException on a GitLab api call error */ - public GitlabNote getNote(GitlabMergeRequest mergeRequest, Integer noteId) throws IOException { + public GitlabDiscussion resolveDiscussion(GitlabMergeRequest mergeRequest, + int discussionId, boolean resolved) throws IOException { String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + - GitlabNote.URL + "/" + noteId; - - return retrieve().to(tailUrl, GitlabNote.class); + GitlabDiscussion.URL + "/" + discussionId; + return retrieve().method(PUT) + .with("resolved", resolved) + .to(tailUrl, GitlabDiscussion.class); } - public List getNotes(GitlabMergeRequest mergeRequest) throws IOException { + /** + * Add a note to existing merge request discussion. + * + * @param mergeRequest The merge request of the discussion. + * @param discussionId The id of the discussion to add a note to. + * @param body The content of the discussion. + * + * @return The added note object. + * @throws IOException on a GitLab api call error + */ + public GitlabNote addDiscussionNote(GitlabMergeRequest mergeRequest, + int discussionId, String body) throws IOException { String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL + "/" + discussionId + GitlabNote.URL; - - GitlabNote[] notes = retrieve().to(tailUrl, GitlabNote[].class); - return Arrays.asList(notes); + return dispatch().with("body", body).to(tailUrl, GitlabNote.class); } - public List getAllNotes(GitlabMergeRequest mergeRequest) throws IOException { + /** + * Modify or resolve an existing discussion note of the given merge request. + * + * @param mergeRequest The merge request of the discussion. + * @param discussionId The id of the discussion to modify. + * @param noteId The id of the discussion note. + * @param body The content of the discussion. + * @param resolved Resolve or unresolve the note. + * + * @return The modified note object. + * @throws IOException on a GitLab api call error + */ + public GitlabNote modifyDiscussionNote(GitlabMergeRequest mergeRequest, int discussionId, + int noteId, String body, Boolean resolved) throws IOException { + boolean bodyHasValue = false; + if (body != null && !body.isEmpty()) { + bodyHasValue = true; + } + if ((!bodyHasValue && resolved == null) || (bodyHasValue && resolved != null)) { + throw new IllegalArgumentException("Exactly one of body or resolved must be set!"); + } String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + - GitlabNote.URL + PARAM_MAX_ITEMS_PER_PAGE; + GitlabDiscussion.URL + "/" + discussionId + + GitlabNote.URL + "/" + noteId; + return retrieve().method(PUT) + .with("body", body) + .with("resolved", resolved) + .to(tailUrl, GitlabNote.class); + } - return retrieve().getAll(tailUrl, GitlabNote[].class); + /** + * Delete a discussion note of a merge request. + * + * @param mergeRequest The merge request of the discussion. + * @param discussionId The id of the discussion to resolve. + * @param noteId The id of a discussion note. + * + * @return The deleted note object. + * @throws IOException on a GitLab api call error + */ + public void deleteDiscussionNote(GitlabMergeRequest mergeRequest, int discussionId, int noteId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabDiscussion.URL + "/" + discussionId + + GitlabNote.URL + "/" + noteId; + retrieve().method(DELETE).to(tailUrl, Void.class); } // Get a specific commit identified by the commit hash or name of a branch or tag @@ -1461,13 +2125,9 @@ public List getCommits(GitlabMergeRequest mergeRequest, Pagination projectId = mergeRequest.getProjectId(); } - Query query = new Query() - .append("ref_name", mergeRequest.getSourceBranch()); - - query.mergeWith(pagination.asQuery()); - String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + - "/repository" + GitlabCommit.URL + query.toString(); + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + + GitlabCommit.URL + pagination.toString(); GitlabCommit[] commits = retrieve().to(tailUrl, GitlabCommit[].class); return Arrays.asList(commits); @@ -1483,11 +2143,18 @@ public List getLastCommits(Serializable projectId, String branchOr public List getCommits(Serializable projectId, Pagination pagination, String branchOrTag) throws IOException { + return getCommits(projectId, pagination, branchOrTag, null); + } + + public List getCommits(Serializable projectId, Pagination pagination, + String branchOrTag, String path) throws IOException { final Query query = new Query(); if (branchOrTag != null) { query.append("ref_name", branchOrTag); } - + if (path != null) { + query.append("path", path); + } if (pagination != null) { query.mergeWith(pagination.asQuery()); } @@ -1559,12 +2226,21 @@ public GitlabCommitComparison compareCommits(Serializable projectId, String comm // List commit statuses for a project ID and commit hash // GET /projects/:id/repository/commits/:sha/statuses public List getCommitStatuses(GitlabProject project, String commitHash) throws IOException { - return getCommitStatuses(project, commitHash, new Pagination()); + return getCommitStatuses(project.getId(), commitHash, new Pagination()); + } + + public List getCommitStatuses(Serializable projectId, String commitHash) throws IOException { + return getCommitStatuses(projectId, commitHash, new Pagination()); } public List getCommitStatuses(GitlabProject project, String commitHash, Pagination pagination) throws IOException { - String tailUrl = GitlabProject.URL + "/" + project.getId() + "/repository" + GitlabCommit.URL + "/" + + return getCommitStatuses(project.getId(), commitHash, pagination); + } + + public List getCommitStatuses(Serializable projectId, String commitHash, + Pagination pagination) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + "/repository" + GitlabCommit.URL + "/" + commitHash + GitlabCommitStatus.URL + pagination; GitlabCommitStatus[] statuses = retrieve().to(tailUrl, GitlabCommitStatus[].class); return Arrays.asList(statuses); @@ -1574,7 +2250,12 @@ public List getCommitStatuses(GitlabProject project, String // GET /projects/:id/statuses/:sha public GitlabCommitStatus createCommitStatus(GitlabProject project, String commitHash, String state, String ref, String name, String targetUrl, String description) throws IOException { - String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabCommitStatus.URL + "/" + commitHash; + return createCommitStatus(project.getId(), commitHash, state, ref, name, targetUrl, description); + } + + public GitlabCommitStatus createCommitStatus(Serializable projectId, String commitHash, String state, String ref, + String name, String targetUrl, String description) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabCommitStatus.URL + "/" + commitHash; return dispatch() .with("state", state) .with("ref", ref) @@ -1604,11 +2285,11 @@ public byte[] getRawFileContent(GitlabProject project, String sha, String filepa * @param filepath The path of the file * @throws IOException on gitlab api call error */ - public byte[] getRawFileContent(Integer projectId, String sha, String filepath) throws IOException { + public byte[] getRawFileContent(Serializable projectId, String sha, String filepath) throws IOException { Query query = new Query() .append("ref", sha); - String tailUrl = GitlabProject.URL + "/" + projectId + "/repository/files/" + sanitizePath(filepath) + "/raw" + query.toString(); + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + "/repository/files/" + sanitizePath(filepath) + "/raw" + query.toString(); return retrieve().to(tailUrl, byte[].class); } @@ -1695,7 +2376,7 @@ public GitlabSimpleRepositoryFile createRepositoryFile(GitlabProject project, St */ public GitlabSimpleRepositoryFile updateRepositoryFile(GitlabProject project, String path, String branchName, String commitMsg, String content) throws IOException { String tailUrl = GitlabProject.URL + "/" + project.getId() + "/repository/files/" + sanitizePath(path); - GitlabHTTPRequestor requestor = retrieve().method("PUT"); + GitlabHTTPRequestor requestor = retrieve().method(PUT); return requestor .with("branch", branchName) @@ -1719,7 +2400,7 @@ public void deleteRepositoryFile(GitlabProject project, String path, String bran .append("branch", branchName) .append("commit_message", commitMsg); String tailUrl = GitlabProject.URL + "/" + project.getId() + "/repository/files/" + sanitizePath(path) + query.toString(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -1738,7 +2419,7 @@ public GitlabNote updateNote(GitlabMergeRequest mergeRequest, Integer noteId, St String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + GitlabNote.URL + "/" + noteId + query.toString(); - return retrieve().method("PUT").to(tailUrl, GitlabNote.class); + return retrieve().method(PUT).to(tailUrl, GitlabNote.class); } public GitlabNote createNote(GitlabMergeRequest mergeRequest, String body) throws IOException { @@ -1758,15 +2439,15 @@ public GitlabNote createNote(GitlabMergeRequest mergeRequest, String body) throw public void deleteNote(GitlabMergeRequest mergeRequest, GitlabNote noteToDelete) throws IOException { String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + GitlabNote.URL + "/" + noteToDelete.getId(); - retrieve().method("DELETE").to(tailUrl, GitlabNote.class); + retrieve().method(DELETE).to(tailUrl, GitlabNote.class); } - public List getBranches(Serializable projectId) throws IOException { + public List getBranches(Serializable projectId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBranch.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabBranch[].class); } - public List getBranches(GitlabProject project) throws IOException { + public List getBranches(GitlabProject project) { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabBranch.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabBranch[].class); } @@ -1811,7 +2492,7 @@ public void createBranch(Serializable projectId, String branchName, String ref) */ public void deleteBranch(Serializable projectId, String branchName) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBranch.URL + '/' + sanitizePath(branchName); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } public GitlabBranch getBranch(Serializable projectId, String branchName) throws IOException { @@ -1832,24 +2513,22 @@ public void protectBranchWithDeveloperOptions(GitlabProject project, String bran final Query query = new Query() .append("developers_can_push", Boolean.toString(developers_can_push)) .append("developers_can_merge", Boolean.toString(developers_can_merge)); - retrieve().method("PUT").to(tailUrl + query.toString(), Void.class); + retrieve().method(PUT).to(tailUrl + query.toString(), Void.class); } public void unprotectBranch(GitlabProject project, String branchName) throws IOException { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabBranch.URL + '/' + sanitizePath(branchName) + "/unprotect"; - retrieve().method("PUT").to(tailUrl, Void.class); + retrieve().method(PUT).to(tailUrl, Void.class); } public List getProjectHooks(Serializable projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabProjectHook.URL; - GitlabProjectHook[] hooks = retrieve().to(tailUrl, GitlabProjectHook[].class); - return Arrays.asList(hooks); + return retrieve().getAll(tailUrl, GitlabProjectHook[].class); } public List getProjectHooks(GitlabProject project) throws IOException { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabProjectHook.URL; - GitlabProjectHook[] hooks = retrieve().to(tailUrl, GitlabProjectHook[].class); - return Arrays.asList(hooks); + return retrieve().getAll(tailUrl, GitlabProjectHook[].class); } public GitlabProjectHook getProjectHook(GitlabProject project, String hookId) throws IOException { @@ -1873,7 +2552,7 @@ public GitlabProjectHook addProjectHook(GitlabProject project, String url, Strin .to(tailUrl, GitlabProjectHook.class); } - public GitlabProjectHook addProjectHook(Serializable projectId, String url, boolean pushEvents, boolean issuesEvents, boolean mergeRequestEvents, boolean tagPushEvents, boolean sslVerification) throws IOException { + public GitlabProjectHook addProjectHook(Serializable projectId, String url, boolean pushEvents, boolean issuesEvents, boolean mergeRequestEvents, boolean noteEvents, boolean tagPushEvents, boolean sslVerification, boolean jobEvents, boolean pipelineEvents, boolean wikiPageEvents, String token) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabProjectHook.URL; return dispatch() @@ -1881,46 +2560,76 @@ public GitlabProjectHook addProjectHook(Serializable projectId, String url, bool .with("push_events", pushEvents ? "true" : "false") .with("issues_events", issuesEvents ? "true" : "false") .with("merge_requests_events", mergeRequestEvents ? "true" : "false") + .with("note_events", noteEvents ? "true" : "false") .with("tag_push_events", tagPushEvents ? "true" : "false") .with("enable_ssl_verification", sslVerification ? "true" : "false") + .with("job_events", jobEvents ? "true" : "false") + .with("pipeline_events", pipelineEvents ? "true" : "false") + .with("wiki_page_events", wikiPageEvents ? "true" : "false") + .with("token", token) .to(tailUrl, GitlabProjectHook.class); } - public GitlabProjectHook editProjectHook(GitlabProject project, String hookId, String url) throws IOException { - Query query = new Query() - .append("url", url); + public GitlabProjectHook addProjectHook(Serializable projectId, String url, GitlabProjectHook hook, String token) throws IOException { + return this.addProjectHook(projectId, url, hook.getPushEvents(), hook.getIssueEvents(), hook.isMergeRequestsEvents(), + hook.isNoteEvents(), hook.isTagPushEvents(), hook.isSslVerificationEnabled(), hook.isJobEvents(), + hook.isPipelineEvents(), hook.isWikiPageEvents(), token); + } + public GitlabProjectHook editProjectHook(GitlabProject project, String hookId, String url, + boolean pushEvents, boolean issuesEvents, boolean mergeRequestEvents, boolean noteEvents, + boolean tagPushEvents, boolean sslVerification, boolean jobEvents, boolean pipelineEvents, + boolean wikiPageEvents, String token) throws IOException { + Query query = new Query(); + query.append("url", url); + query.append("push_events", String.valueOf(pushEvents)); + query.append("issues_events", String.valueOf(issuesEvents)); + query.append("merge_request_events", String.valueOf(mergeRequestEvents)); + query.append("note_events", String.valueOf(noteEvents)); + query.append("tag_push_events", String.valueOf(tagPushEvents)); + query.append("enable_ssl_verification", String.valueOf(sslVerification)); + query.append("job_events", String.valueOf(jobEvents)); + query.append("pipeline_events", String.valueOf(pipelineEvents)); + query.append("wiki_page_events", String.valueOf(wikiPageEvents)); + query.append("token", token); String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabProjectHook.URL + "/" + hookId + query.toString(); - return retrieve().method("PUT").to(tailUrl, GitlabProjectHook.class); + return retrieve().method(PUT).to(tailUrl, GitlabProjectHook.class); + } + + public GitlabProjectHook editProjectHook(GitlabProject project, GitlabProjectHook projectHook, String token) throws IOException { + return editProjectHook(project, projectHook.getId(), projectHook.getUrl(), projectHook.getPushEvents(), + projectHook.getIssueEvents(), projectHook.isMergeRequestsEvents(), projectHook.isNoteEvents(), + projectHook.isTagPushEvents(), projectHook.isSslVerificationEnabled(), projectHook.isJobEvents(), + projectHook.isWikiPageEvents(), projectHook.isPipelineEvents(), token); } public void deleteProjectHook(GitlabProjectHook hook) throws IOException { String tailUrl = GitlabProject.URL + "/" + hook.getProjectId() + GitlabProjectHook.URL + "/" + hook.getId(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } public void deleteProjectHook(GitlabProject project, String hookId) throws IOException { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabProjectHook.URL + "/" + hookId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } - public List getIssues(GitlabProject project) throws IOException { + public List getIssues(GitlabProject project) { return getIssues(project.getId()); } - public List getIssues(Serializable projectId) throws IOException { + public List getIssues(Serializable projectId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabIssue.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabIssue[].class); } - public List getIssues(GitlabProject project, GitlabMilestone milestone) throws IOException { + public List getIssues(GitlabProject project, GitlabMilestone milestone) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(project.getId()) + GitlabMilestone.URL + "/" + sanitizeMilestoneId(milestone.getId()) + GitlabIssue.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabIssue[].class); } - public List getIssues(GitlabGroup group, GitlabMilestone milestone) throws IOException { + public List getIssues(GitlabGroup group, GitlabMilestone milestone) { String tailUrl = GitlabGroup.URL + "/" + sanitizeGroupId(group.getId()) + GitlabMilestone.URL + "/" + sanitizeMilestoneId(milestone.getId()) + GitlabIssue.URL + PARAM_MAX_ITEMS_PER_PAGE; @@ -1956,7 +2665,7 @@ public GitlabIssue moveIssue(Integer projectId, Integer issueId, Integer toProje public GitlabIssue editIssue(int projectId, int issueId, int assigneeId, int milestoneId, String labels, String description, String title, GitlabIssue.Action action) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + GitlabIssue.URL + "/" + issueId; - GitlabHTTPRequestor requestor = retrieve().method("PUT"); + GitlabHTTPRequestor requestor = retrieve().method(PUT); applyIssue(requestor, projectId, assigneeId, milestoneId, labels, description, title); if (action != GitlabIssue.Action.LEAVE) { @@ -1982,14 +2691,14 @@ private void applyIssue(GitlabHTTPRequestor requestor, int projectId, public GitlabNote getNote(GitlabIssue issue, Integer noteId) throws IOException { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + - GitlabIssue.URL + "/" + issue.getId() + + GitlabIssue.URL + "/" + issue.getIid() + GitlabNote.URL + "/" + noteId; return retrieve().to(tailUrl, GitlabNote.class); } public List getNotes(GitlabIssue issue) throws IOException { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + GitlabIssue.URL + "/" - + issue.getId() + GitlabNote.URL; + + issue.getIid() + GitlabNote.URL; return Arrays.asList(retrieve().to(tailUrl, GitlabNote[].class)); } @@ -2000,7 +2709,7 @@ public GitlabNote createNote(Serializable projectId, Integer issueId, String mes } public GitlabNote createNote(GitlabIssue issue, String message) throws IOException { - return createNote(String.valueOf(issue.getProjectId()), issue.getId(), message); + return createNote(String.valueOf(issue.getProjectId()), issue.getIid(), message); } /** @@ -2015,7 +2724,7 @@ public void deleteNote(Serializable projectId, Integer issueId, GitlabNote noteT String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabIssue.URL + "/" + issueId + GitlabNote.URL + "/" + noteToDelete.getId(); - retrieve().method("DELETE").to(tailUrl, GitlabNote.class); + retrieve().method(DELETE).to(tailUrl, GitlabNote.class); } /** @@ -2029,12 +2738,168 @@ public void deleteNote(GitlabIssue issue, GitlabNote noteToDelete) throws IOExce deleteNote(String.valueOf(issue.getProjectId()), issue.getId(), noteToDelete); } + /** + * Get project badges + * + * @param projectId The id of the project for which the badges should be retrieved + * @return The list of badges + * + * @throws IOException on GitLab API call error + */ + public List getProjectBadges(Serializable projectId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBadge.URL; + return Arrays.asList(retrieve().to(tailUrl, GitlabBadge[].class)); + } + + /** + * Get project badge + * + * @param projectId The id of the project for which the badge should be retrieved + * @param badgeId The id of the badge that should be retrieved + * @return The badge with a given id + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge getProjectBadge(Serializable projectId, Integer badgeId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBadge.URL + + "/" + badgeId; + return retrieve().to(tailUrl, GitlabBadge.class); + } + + /** + * Add project badge + * + * @param projectId The id of the project for which the badge should be added + * @param linkUrl The URL that the badge should link to + * @param imageUrl The URL to the badge image + * @return The created badge + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge addProjectBadge(Serializable projectId, String linkUrl, String imageUrl) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBadge.URL; + return dispatch().with("link_url", linkUrl) + .with("image_url", imageUrl) + .to(tailUrl, GitlabBadge.class); + } + + /** + * Edit project badge + * + * @param projectId The id of the project for which the badge should be edited + * @param badgeId The id of the badge that should be edited + * @param linkUrl The URL that the badge should link to + * @param imageUrl The URL to the badge image + * @return The updated badge + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge editProjectBadge(Serializable projectId, Integer badgeId, String linkUrl, String imageUrl) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBadge.URL + + "/" + badgeId; + GitlabHTTPRequestor requestor = retrieve().method(PUT); + requestor.with("link_url", linkUrl) + .with("image_url", imageUrl); + return requestor.to(tailUrl, GitlabBadge.class); + } + + /** + * Delete project badge + * + * @param projectId The id of the project for which the badge should be deleted + * @param badgeId The id of the badge that should be deleted + * @throws IOException on GitLab API call error + */ + public void deleteProjectBadge(Serializable projectId, Integer badgeId) throws IOException { + String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabBadge.URL + + "/" + badgeId; + retrieve().method(DELETE).to(tailUrl, Void.class); + } + + /** + * Get project badges + * + * @param groupId The id of the group for which the badges should be retrieved + * @return The list of badges + * + * @throws IOException on GitLab API call error + */ + public List getGroupBadges(Integer groupId) throws IOException { + String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabBadge.URL; + return Arrays.asList(retrieve().to(tailUrl, GitlabBadge[].class)); + } + + /** + * Get group badge + * + * @param groupId The id of the group for which the badge should be retrieved + * @param badgeId The id of the badge that should be retrieved + * @return The badge with a given id + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge getGroupBadge(Integer groupId, Integer badgeId) throws IOException { + String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabBadge.URL + + "/" + badgeId; + return retrieve().to(tailUrl, GitlabBadge.class); + } + + /** + * Add group badge + * + * @param groupId The id of the group for which the badge should be added + * @param linkUrl The URL that the badge should link to + * @param imageUrl The URL to the badge image + * @return The created badge + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge addGroupBadge(Integer groupId, String linkUrl, String imageUrl) throws IOException { + String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabBadge.URL; + return dispatch().with("link_url", linkUrl) + .with("image_url", imageUrl) + .to(tailUrl, GitlabBadge.class); + } + + /** + * Edit group badge + * + * @param groupId The id of the group for which the badge should be edited + * @param badgeId The id of the badge that should be edited + * @param linkUrl The URL that the badge should link to + * @param imageUrl The URL to the badge image + * @return The updated badge + * + * @throws IOException on GitLab API call error + */ + public GitlabBadge editGroupBadge(Integer groupId, Integer badgeId, String linkUrl, String imageUrl) throws IOException { + String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabBadge.URL + + "/" + badgeId; + GitlabHTTPRequestor requestor = retrieve().method(PUT); + requestor.with("link_url", linkUrl) + .with("image_url", imageUrl); + return requestor.to(tailUrl, GitlabBadge.class); + } + + /** + * Delete group badge + * + * @param groupId The id of the group for which the badge should be deleted + * @param badgeId The id of the badge that should be deleted + * @throws IOException on GitLab API call error + */ + public void deleteGroupBadge(Integer groupId, Integer badgeId) throws IOException { + String tailUrl = GitlabGroup.URL + "/" + groupId + GitlabBadge.URL + + "/" + badgeId; + retrieve().method(DELETE).to(tailUrl, Void.class); + } + /** * Gets labels associated with a project. * * @param projectId The ID of the project. * @return A non-null list of labels. - * @throws IOException + * @throws IOException on gitlab api call error */ public List getLabels(Serializable projectId) throws IOException { @@ -2048,7 +2913,7 @@ public List getLabels(Serializable projectId) * * @param project The project associated with labels. * @return A non-null list of labels. - * @throws IOException + * @throws IOException on gitlab api call error */ public List getLabels(GitlabProject project) throws IOException { @@ -2062,7 +2927,7 @@ public List getLabels(GitlabProject project) * @param name The name of the label. * @param color The color of the label (eg #ff0000). * @return The newly created label. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabLabel createLabel( Serializable projectId, @@ -2093,7 +2958,7 @@ public GitlabLabel createLabel(Serializable projectId, GitlabLabel label) * * @param projectId The ID of the project containing the label. * @param name The name of the label to delete. - * @throws IOException + * @throws IOException on gitlab api call error */ public void deleteLabel(Serializable projectId, String name) throws IOException { @@ -2103,7 +2968,7 @@ public void deleteLabel(Serializable projectId, String name) sanitizeProjectId(projectId) + GitlabLabel.URL + query.toString(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2111,7 +2976,7 @@ public void deleteLabel(Serializable projectId, String name) * * @param projectId The ID of the project containing the label. * @param label The label to delete. - * @throws IOException + * @throws IOException on gitlab api call error */ public void deleteLabel(Serializable projectId, GitlabLabel label) throws IOException { @@ -2126,14 +2991,14 @@ public void deleteLabel(Serializable projectId, GitlabLabel label) * @param newName The updated name. * @param newColor The updated color. * @return The updated, deserialized label. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabLabel updateLabel(Serializable projectId, String name, String newName, String newColor) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabLabel.URL; - GitlabHTTPRequestor requestor = retrieve().method("PUT"); + GitlabHTTPRequestor requestor = retrieve().method(PUT); requestor.with("name", name); if (newName != null) { requestor.with("new_name", newName); @@ -2171,7 +3036,7 @@ public List getGroupMilestones(Serializable groupId) throws IOE * @param dueDate The date the milestone is due. (Optional) * @param startDate The start date of the milestone. (Optional) * @return The newly created, de-serialized milestone. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabMilestone createMilestone( Serializable projectId, @@ -2200,7 +3065,7 @@ public GitlabMilestone createMilestone( * @param projectId The ID of the project. * @param milestone The milestone to create. * @return The newly created, de-serialized milestone. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabMilestone createMilestone( Serializable projectId, @@ -2224,7 +3089,7 @@ public GitlabMilestone createMilestone( * @param stateEvent A value used to update the state of the milestone. * (Optional) (activate | close) * @return The updated, de-serialized milestone. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabMilestone updateMilestone( Serializable projectId, @@ -2238,7 +3103,7 @@ public GitlabMilestone updateMilestone( sanitizeProjectId(projectId) + GitlabMilestone.URL + "/" + milestoneId; - GitlabHTTPRequestor requestor = retrieve().method("PUT"); + GitlabHTTPRequestor requestor = retrieve().method(PUT); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); if (title != null) { requestor.with("title", title); @@ -2266,7 +3131,7 @@ public GitlabMilestone updateMilestone( * @param stateEvent A value used to update the state of the milestone. * (Optional) (activate | close) * @return The updated, de-serialized milestone. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabMilestone updateMilestone( Serializable projectId, @@ -2288,7 +3153,7 @@ public GitlabMilestone updateMilestone( * @param stateEvent A value used to update the state of the milestone. * (Optional) (activate | close) * @return The updated, de-serialized milestone. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabMilestone updateMilestone( GitlabMilestone edited, @@ -2348,9 +3213,41 @@ public void deleteProjectMember(GitlabProject project, GitlabUser user) throws I */ public void deleteProjectMember(Integer projectId, Integer userId) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + "/" + GitlabProjectMember.URL + "/" + userId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); + } + + /** + * Updates a project member. + * + * @param projectId the project id + * @param userId the user id + * @param accessLevel the updated access level for the specified user + * @return GitLabProjectMember with updated access level on success + * @throws IOException on Gitlab API call error + */ + public GitlabProjectMember updateProjectMember(Integer projectId, Integer userId, GitlabAccessLevel accessLevel) throws IOException { + return updateProjectMember(projectId, userId, accessLevel, null); + } + + /** + * Updates a project member. + * + * @param projectId the project id + * @param userId the user id + * @param accessLevel the updated access level for the specified user + * @param expiresAt the date at which the user's membership expires at in the form YEAR-MONTH-DAY + * @return GitLabProjectMember with updated access level on success + * @throws IOException on Gitlab API call error + */ + public GitlabProjectMember updateProjectMember(Integer projectId, Integer userId, GitlabAccessLevel accessLevel, String expiresAt) throws IOException { + Query query = new Query() + .appendIf("access_level", accessLevel) + .appendIf("expires_at", expiresAt); + String tailUrl = GitlabProject.URL + "/" + projectId + GitlabProjectMember.URL + "/" + userId + query.toString(); + return retrieve().method(PUT).to(tailUrl, GitlabProjectMember.class); } + public List getProjectMembers(GitlabProject project) throws IOException { return getProjectMembers(project.getId()); } @@ -2399,8 +3296,9 @@ public List getNamespaceMembers(Integer namespaceId) throws * @throws IOException on gitlab api call error */ public void transfer(Integer namespaceId, Integer projectId) throws IOException { - String tailUrl = GitlabGroup.URL + "/" + namespaceId + GitlabProject.URL + "/" + projectId; - dispatch().to(tailUrl, Void.class); + Query query = new Query().append("namespace", String.valueOf(namespaceId)); + String tailUrl = GitlabProject.URL + "/" + projectId + "/transfer" + query.toString(); + retrieve().method(PUT).to(tailUrl, Void.class); } /** @@ -2450,7 +3348,7 @@ private GitlabSSHKey createDeployKey(Integer targetProjectId, String title, Stri */ public void deleteDeployKey(Integer targetProjectId, Integer targetKeyId) throws IOException { String tailUrl = GitlabProject.URL + "/" + targetProjectId + GitlabSSHKey.DEPLOY_KEYS_URL + "/" + targetKeyId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2509,7 +3407,7 @@ public void testSystemHook(Integer hookId) throws IOException { */ public GitlabSystemHook deleteSystemHook(Integer hookId) throws IOException { String tailUrl = GitlabSystemHook.URL + "/" + hookId; - return retrieve().method("DELETE").to(tailUrl, GitlabSystemHook.class); + return retrieve().method(DELETE).to(tailUrl, GitlabSystemHook.class); } private String sanitizeProjectId(Serializable projectId) { @@ -2538,7 +3436,7 @@ private String sanitizeId(Serializable id, String parameterName) { private String sanitizePath(String branch) { try { - return URLEncoder.encode(branch, "UTF-8"); + return URLEncoder.encode(branch, "UTF-8").replaceAll("\\+", "%20"); } catch (UnsupportedEncodingException e) { throw new RuntimeException((e)); } @@ -2557,7 +3455,7 @@ private String sanitizePath(String branch) { * @throws IOException on gitlab api call error * @see http://doc.gitlab.com/ce/api/commits.html#post-comment-to-commit */ - public CommitComment createCommitComment(Integer projectId, String sha, String note, + public CommitComment createCommitComment(Serializable projectId, String sha, String note, String path, String line, String line_type) throws IOException { Query query = new Query() @@ -2593,9 +3491,8 @@ public List getCommitComments(Integer projectId, String sha) thro * * @param projectId * @return - * @throws IOException on gitlab api call error */ - public List getTags(Serializable projectId) throws IOException { + public List getTags(Serializable projectId) { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabTag.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabTag[].class); } @@ -2605,13 +3502,25 @@ public List getTags(Serializable projectId) throws IOException { * * @param project * @return - * @throws IOException on gitlab api call error */ - public List getTags(GitlabProject project) throws IOException { + public List getTags(GitlabProject project) { String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabTag.URL + PARAM_MAX_ITEMS_PER_PAGE; return retrieve().getAll(tailUrl, GitlabTag[].class); } + /** + * Get a single repository tag in a specific project + * + * @param project (required) The ID or URL-encoded path of the project + * @param tagName (required) The name of the tag + * @return the found git tag object + * @throws IOException on gitlab api call error + */ + public GitlabTag getTag(GitlabProject project, String tagName) throws IOException { + String tailUrl = GitlabProject.URL + "/" + project.getId() + GitlabTag.URL + "/" + tagName; + return retrieve().to(tailUrl, GitlabTag.class); + } + /** * Create tag in specific project * @@ -2663,7 +3572,7 @@ public GitlabTag addTag(GitlabProject project, String tagName, String ref, Strin */ public void deleteTag(Serializable projectId, String tagName) throws IOException { String tailUrl = GitlabProject.URL + "/" + sanitizeProjectId(projectId) + GitlabTag.URL + "/" + tagName; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2675,16 +3584,15 @@ public void deleteTag(Serializable projectId, String tagName) throws IOException */ public void deleteTag(GitlabProject project, String tagName) throws IOException { String tailUrl = GitlabProject.URL + "/" + project + GitlabTag.URL + "/" + tagName; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** * Get all awards for a merge request * * @param mergeRequest - * @throws IOException on gitlab api call error */ - public List getAllAwards(GitlabMergeRequest mergeRequest) throws IOException { + public List getAllAwards(GitlabMergeRequest mergeRequest) { String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + GitlabAward.URL + PARAM_MAX_ITEMS_PER_PAGE; @@ -2731,16 +3639,15 @@ public void deleteAward(GitlabMergeRequest mergeRequest, GitlabAward award) thro String tailUrl = GitlabProject.URL + "/" + mergeRequest.getProjectId() + GitlabMergeRequest.URL + "/" + mergeRequest.getIid() + GitlabAward.URL + "/" + award.getId(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** * Get all awards for an issue * * @param issue - * @throws IOException on gitlab api call error */ - public List getAllAwards(GitlabIssue issue) throws IOException { + public List getAllAwards(GitlabIssue issue) { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + GitlabIssue.URL + "/" + issue.getId() + GitlabAward.URL + PARAM_MAX_ITEMS_PER_PAGE; @@ -2786,7 +3693,7 @@ public GitlabAward createAward(GitlabIssue issue, String awardName) throws IOExc public void deleteAward(GitlabIssue issue, GitlabAward award) throws IOException { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + GitlabIssue.URL + "/" + issue.getId() + GitlabAward.URL + "/" + award.getId(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2794,9 +3701,8 @@ public void deleteAward(GitlabIssue issue, GitlabAward award) throws IOException * * @param issue * @param noteId - * @throws IOException on gitlab api call error */ - public List getAllAwards(GitlabIssue issue, Integer noteId) throws IOException { + public List getAllAwards(GitlabIssue issue, Integer noteId) { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + GitlabIssue.URL + "/" + issue.getId() + GitlabNote.URL + noteId + GitlabAward.URL + PARAM_MAX_ITEMS_PER_PAGE; @@ -2845,7 +3751,7 @@ public GitlabAward createAward(GitlabIssue issue, Integer noteId, String awardNa public void deleteAward(GitlabIssue issue, Integer noteId, GitlabAward award) throws IOException { String tailUrl = GitlabProject.URL + "/" + issue.getProjectId() + GitlabIssue.URL + "/" + issue.getId() + GitlabNote.URL + noteId + GitlabAward.URL + "/" + award.getId(); - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2853,7 +3759,7 @@ public void deleteAward(GitlabIssue issue, Integer noteId, GitlabAward award) th * * @param projectId The ID of the project. * @return A non-null list of variables. - * @throws IOException + * @throws IOException on gitlab api call error */ public List getBuildVariables(Integer projectId) throws IOException { @@ -2867,7 +3773,7 @@ public List getBuildVariables(Integer projectId) * * @param project The project associated with variables. * @return A non-null list of variables. - * @throws IOException + * @throws IOException on gitlab api call error */ public List getBuildVariables(GitlabProject project) throws IOException { @@ -2880,7 +3786,7 @@ public List getBuildVariables(GitlabProject project) * @param projectId The ID of the project. * @param key The key of the variable. * @return A variable. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabBuildVariable getBuildVariable(Integer projectId, String key) throws IOException { @@ -2896,7 +3802,7 @@ public GitlabBuildVariable getBuildVariable(Integer projectId, String key) * * @param project The project associated with the variable. * @return A variable. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabBuildVariable getBuildVariable(GitlabProject project, String key) throws IOException { @@ -2910,7 +3816,7 @@ public GitlabBuildVariable getBuildVariable(GitlabProject project, String key) * @param key The key of the variable. * @param value The value of the variable * @return The newly created variable. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabBuildVariable createBuildVariable( Integer projectId, @@ -2941,7 +3847,7 @@ public GitlabBuildVariable createBuildVariable(Integer projectId, GitlabBuildVar * * @param projectId The ID of the project containing the variable. * @param key The key of the variable to delete. - * @throws IOException + * @throws IOException on gitlab api call error */ public void deleteBuildVariable(Integer projectId, String key) throws IOException { @@ -2949,7 +3855,7 @@ public void deleteBuildVariable(Integer projectId, String key) projectId + GitlabBuildVariable.URL + "/" + key; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -2957,7 +3863,7 @@ public void deleteBuildVariable(Integer projectId, String key) * * @param projectId The ID of the project containing the variable. * @param variable The variable to delete. - * @throws IOException + * @throws IOException on gitlab api call error */ public void deleteBuildVariable(Integer projectId, GitlabBuildVariable variable) throws IOException { @@ -2971,7 +3877,7 @@ public void deleteBuildVariable(Integer projectId, GitlabBuildVariable variable) * @param key The key of the variable to update. * @param newValue The updated value. * @return The updated, deserialized variable. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabBuildVariable updateBuildVariable(Integer projectId, String key, @@ -2980,7 +3886,7 @@ public GitlabBuildVariable updateBuildVariable(Integer projectId, projectId + GitlabBuildVariable.URL + "/" + key; - GitlabHTTPRequestor requestor = retrieve().method("PUT"); + GitlabHTTPRequestor requestor = retrieve().method(PUT); if (newValue != null) { requestor = requestor.with("value", newValue); } @@ -2993,9 +3899,8 @@ public GitlabBuildVariable updateBuildVariable(Integer projectId, * @param project the project * @return list of build triggers * @throws IllegalStateException if jobs are not enabled for the project - * @throws IOException */ - public List getPipelineTriggers(GitlabProject project) throws IOException { + public List getPipelineTriggers(GitlabProject project) { if (!project.isJobsEnabled()) { // if the project has not allowed jobs, you will only get a 403 forbidden message which is // not helpful. @@ -3009,7 +3914,7 @@ public List getPipelineTriggers(GitlabProject project) throws IOE * Gets email-on-push service setup for a projectId. * * @param projectId The ID of the project containing the variable. - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabServiceEmailOnPush getEmailsOnPush(Integer projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceEmailOnPush.URL; @@ -3022,11 +3927,9 @@ public GitlabServiceEmailOnPush getEmailsOnPush(Integer projectId) throws IOExce * @param projectId The ID of the project containing the variable. * @param emailAddress The emailaddress of the recipent who is going to receive push notification. * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public boolean updateEmailsOnPush(Integer projectId, String emailAddress) throws IOException { - String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceEmailOnPush.URL; - GitlabServiceEmailOnPush emailOnPush = this.getEmailsOnPush(projectId); GitlabEmailonPushProperties properties = emailOnPush.getProperties(); String appendedRecipients = properties.getRecipients(); @@ -3041,8 +3944,8 @@ public boolean updateEmailsOnPush(Integer projectId, String emailAddress) throws .appendIf("active", true) .appendIf("recipients", appendedRecipients); - tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceEmailOnPush.URL + query.toString(); - return retrieve().method("PUT").to(tailUrl, Boolean.class); + String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceEmailOnPush.URL + query.toString(); + return retrieve().method(PUT).to(tailUrl, Boolean.class); } /** @@ -3051,7 +3954,7 @@ public boolean updateEmailsOnPush(Integer projectId, String emailAddress) throws * * @param projectId The ID of the project containing the variable. * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public GitlabServiceJira getJiraService(Integer projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceJira.URL; @@ -3064,11 +3967,11 @@ public GitlabServiceJira getJiraService(Integer projectId) throws IOException { * * @param projectId The ID of the project containing the variable. * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public boolean deleteJiraService(Integer projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceJira.URL; - return retrieve().method("DELETE").to(tailUrl, Boolean.class); + return retrieve().method(DELETE).to(tailUrl, Boolean.class); } /** @@ -3078,7 +3981,7 @@ public boolean deleteJiraService(Integer projectId) throws IOException { * @param projectId The ID of the project containing the variable. * @param jiraPropties * @return - * @throws IOException + * @throws IOException on gitlab api call error */ public boolean createOrEditJiraService(Integer projectId, GitlabJiraProperties jiraPropties) throws IOException { @@ -3100,7 +4003,7 @@ public boolean createOrEditJiraService(Integer projectId, GitlabJiraProperties j String tailUrl = GitlabProject.URL + "/" + projectId + GitlabServiceJira.URL + query.toString(); - return retrieve().method("PUT").to(tailUrl, Boolean.class); + return retrieve().method(PUT).to(tailUrl, Boolean.class); } @@ -3108,7 +4011,7 @@ public boolean createOrEditJiraService(Integer projectId, GitlabJiraProperties j * Get a list of projects accessible by the authenticated user by search. * * @return A list of gitlab projects - * @throws IOException + * @throws IOException on gitlab api call error */ public List searchProjects(String search) throws IOException { Query query = new Query() @@ -3157,7 +4060,7 @@ public void deleteSharedProjectGroupLink(GitlabGroup group, GitlabProject projec */ public void deleteSharedProjectGroupLink(int groupId, int projectId) throws IOException { String tailUrl = GitlabProject.URL + "/" + projectId + "/share/" + groupId; - retrieve().method("DELETE").to(tailUrl, Void.class); + retrieve().method(DELETE).to(tailUrl, Void.class); } /** @@ -3181,10 +4084,10 @@ public GitlabVersion getVersion() throws IOException { * Returns a List of all GitlabRunners. * * @return List of GitlabRunners - * @throws IOException + * @throws IOException on gitlab api call error */ public List getRunners() throws IOException { - return getRunners(GitlabRunner.RunnerScope.ALL); + return getRunnersWithPagination(GitlabRunner.RunnerScope.ALL, null); } /** @@ -3195,16 +4098,158 @@ public List getRunners() throws IOException { * @throws IOException on Gitlab API call error */ public List getRunners(GitlabRunner.RunnerScope scope) throws IOException { - StringBuilder tailUrl = new StringBuilder("runners/all"); + return getRunnersWithPagination(scope, null); + } + + /** + * Returns a list of runners with perPage elements on the page number specified. + * + * @param scope Can be null. Defines type of Runner to retrieve. + * @param page Page to get perPage number of Runners from. + * @param perPage Number of elements to get per page. + * @return List of GitlabRunners + * @throws IOException on Gitlab API call error + */ + public List getRunnersWithPagination(GitlabRunner.RunnerScope scope, int page, int perPage) throws IOException { + Pagination pagination = new Pagination() + .withPage(page) + .withPerPage(perPage); + return getRunnersWithPagination(scope, pagination); + } + + /** + * Returns a list of runners with perPage elements on the page number specified. + * + * @param scope Can be null. Defines type of Runner to retrieve. + * @param pagination Can be null. Pagination to query by. + * @return List of GitlabRunners + * @throws IOException on Gitlab API call error + */ + public List getRunnersWithPagination(GitlabRunner.RunnerScope scope, Pagination pagination) throws IOException { + StringBuilder tailUrl = new StringBuilder(GitlabRunner.URL).append("/all"); Query query = new Query() .appendIf("scope", scope.getScope()); + + if (pagination != null) { + query.mergeWith(pagination.asQuery()); + } + tailUrl.append(query.toString()); - return retrieve().getAll(tailUrl.toString(), GitlabRunner[].class); + return Arrays.asList(retrieve().method(GET).to(tailUrl.toString(), GitlabRunner[].class)); } + /** + * Get details information of the runner with the specified id. + * + * @param id Runner id. + * @return Extensive GitlabRunner Details. + * @throws IOException on gitlab api call error + */ public GitlabRunner getRunnerDetail(int id) throws IOException { - String tailUrl = String.format("runners/%d", id); + String tailUrl = String.format("%s/%d", GitlabRunner.URL, id); return retrieve().to(tailUrl, GitlabRunner.class); } + /** + * Get events for a project. + * + * @param action If not null, include only events of a particular action type + * @param targetType If not null, include only events of a particular target type + * @param before If not null, include only events created before a particular date. + * @param after If not null, include only events created before a + * particular date. + * @param sortOrder If null, uses the server's default, which is "desc" + */ + public List getEvents(GitlabProject project, + GitlabEvent.ActionType action, + GitlabEvent.TargetType targetType, + GitlabDate before, + GitlabDate after, + SortOrder sortOrder) + throws IOException { + return getEvents(project, action, targetType, before, + after, sortOrder, new Pagination()); + } + + /** + * Get events for a project. + * + * @param action If not null, include only events of a particular action type + * @param targetType If not null, include only events of a particular target type + * @param before If not null, include only events created before a particular date. + * @param after If not null, include only events created before a + * particular date. + * @param sortOrder If null, uses the server's default, which is "desc" + */ + public List getEvents(GitlabProject project, + GitlabEvent.ActionType action, + GitlabEvent.TargetType targetType, + GitlabDate before, + GitlabDate after, + SortOrder sortOrder, + Pagination pagination) + throws IOException { + return getProjectEvents(project.getId(), action, targetType, before, + after, sortOrder, pagination); + } + + /** + * Get events for a project. + * + * @param action If not null, include only events of a particular action type + * @param targetType If not null, include only events of a particular target type + * @param before If not null, include only events created before a particular date. + * @param after If not null, include only events created before a + * particular date. + * @param sort If null, uses the server's default, which is "desc" + */ + public List getProjectEvents(Serializable projectId, + GitlabEvent.ActionType action, + GitlabEvent.TargetType targetType, + GitlabDate before, + GitlabDate after, + SortOrder sort) + throws IOException { + return getProjectEvents(projectId, action, targetType, before, + after, sort, new Pagination()); + } + + /** + * Get events for a project. + * + * @param action If not null, include only events of a particular action type + * @param targetType If not null, include only events of a particular target type + * @param before If not null, include only events created before a particular date. + * @param after If not null, include only events created before a + * particular date. + * @param sort If null, uses the server's default, which is "desc" + */ + public List getProjectEvents(Serializable projectId, + GitlabEvent.ActionType action, + GitlabEvent.TargetType targetType, + GitlabDate before, + GitlabDate after, + SortOrder sort, + Pagination pagination) + throws IOException { + + final Query query = new Query(); + query.appendIf("action", action); + query.appendIf("target_type", targetType); + query.appendIf("before", before); + query.appendIf("after", after); + query.appendIf("sort", sort); + + if (pagination != null) { + query.mergeWith(pagination.asQuery()); + } + + StringBuilder tailUrl = new StringBuilder(GitlabProject.URL) + .append("/") + .append(sanitizeProjectId(projectId)) + .append(GitlabEvent.URL) + .append(query.toString()); + + return Arrays.asList(retrieve().method(GET).to(tailUrl.toString(), GitlabEvent[].class)); + } } diff --git a/src/main/java/org/gitlab/api/Pagination.java b/src/main/java/org/gitlab/api/Pagination.java index b7084529..20be0d27 100644 --- a/src/main/java/org/gitlab/api/Pagination.java +++ b/src/main/java/org/gitlab/api/Pagination.java @@ -1,48 +1,44 @@ package org.gitlab.api; import org.gitlab.api.http.Query; +import org.gitlab.api.query.PaginationQuery; -import java.io.UnsupportedEncodingException; +/** + * @deprecated Use {@link PaginationQuery#PARAM_PAGE} instead. + */ +@Deprecated +public class Pagination extends PaginationQuery { -public class Pagination { - public static final String PARAM_PAGE = "page"; - public static final String PARAM_PER_PAGE = "per_page"; - public static final int MAX_ITEMS_PER_PAGE = 100; - private final Query paginationQuery = new Query(); + /** + * @deprecated Use {@link PaginationQuery#PARAM_PAGE} instead. + */ + @Deprecated + public static final String PARAM_PAGE = PaginationQuery.PARAM_PAGE; - public void setPage(int page) { - try { - paginationQuery.append(PARAM_PAGE, String.valueOf(page)); - } catch (UnsupportedEncodingException ignored) { - } - } + /** + @deprecated Use {@link PaginationQuery#PARAM_PER_PAGE} instead. + */ + @Deprecated + public static final String PARAM_PER_PAGE = PaginationQuery.PARAM_PER_PAGE; + + /** + @deprecated Use {@link PaginationQuery#MAX_ITEMS_PER_PAGE} instead. + */ + @Deprecated + public static final int MAX_ITEMS_PER_PAGE = PaginationQuery.MAX_ITEMS_PER_PAGE; - public void setPerPage(int perPage) { - if (perPage > MAX_ITEMS_PER_PAGE) { - throw new IllegalArgumentException("Max value for perPage is " + MAX_ITEMS_PER_PAGE); - } - try { - paginationQuery.append(PARAM_PER_PAGE, String.valueOf(perPage)); - } catch (UnsupportedEncodingException ignored) { - } - } - public Pagination withPage(int page) { setPage(page); return this; } - + public Pagination withPerPage(int perPage) { setPerPage(perPage); return this; } public Query asQuery() { - return paginationQuery; + return this; } - @Override - public String toString() { - return paginationQuery.toString(); - } } diff --git a/src/main/java/org/gitlab/api/http/GitlabHTTPRequestor.java b/src/main/java/org/gitlab/api/http/GitlabHTTPRequestor.java index e12dc6c6..ebe80573 100644 --- a/src/main/java/org/gitlab/api/http/GitlabHTTPRequestor.java +++ b/src/main/java/org/gitlab/api/http/GitlabHTTPRequestor.java @@ -10,6 +10,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; +import java.io.UncheckedIOException; import java.lang.reflect.Field; import java.net.*; import java.util.*; @@ -25,6 +26,10 @@ import org.gitlab.api.GitlabAPIException; import org.gitlab.api.TokenType; +import static org.gitlab.api.http.Method.GET; +import static org.gitlab.api.http.Method.POST; +import static org.gitlab.api.http.Method.PUT; + /** * Gitlab HTTP Requestor * Responsible for handling HTTP requests to the Gitlab API @@ -37,32 +42,14 @@ public class GitlabHTTPRequestor { private final GitlabAPI root; - private String method = "GET"; // Default to GET requests - private Map data = new HashMap(); - private Map attachments = new HashMap(); + private Method method = GET; // Default to GET requests + private Map data = new HashMap<>(); + private Map attachments = new HashMap<>(); private String apiToken; private TokenType tokenType; private AuthMethod authMethod; - private enum METHOD { - GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, TRACE; - - public static String prettyValues() { - METHOD[] methods = METHOD.values(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < methods.length; i++) { - METHOD method = methods[i]; - builder.append(method.toString()); - - if (i != methods.length - 1) { - builder.append(", "); - } - } - return builder.toString(); - } - } - public GitlabHTTPRequestor(GitlabAPI root) { this.root = root; } @@ -82,7 +69,7 @@ public GitlabHTTPRequestor authenticate(String token, TokenType type, AuthMethod this.authMethod = method; return this; } - + /** * Sets the HTTP Request method for the request. * Has a fluent api for method chaining. @@ -90,13 +77,8 @@ public GitlabHTTPRequestor authenticate(String token, TokenType type, AuthMethod * @param method The HTTP method * @return this */ - public GitlabHTTPRequestor method(String method) { - try { - this.method = METHOD.valueOf(method).toString(); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid HTTP Method: " + method + ". Must be one of " + METHOD.prettyValues()); - } - + public GitlabHTTPRequestor method(Method method) { + this.method = method; return this; } @@ -157,7 +139,7 @@ public T to(String tailAPIUrl, Class type, T instance) throws IOException submitAttachments(connection); } else if (hasOutput()) { submitData(connection); - } else if ("PUT".equals(method)) { + } else if (PUT.equals(method)) { // PUT requires Content-Length: 0 even when there is no body (eg: API for protecting a branch) connection.setDoOutput(true); connection.setFixedLengthStreamingMode(0); @@ -178,7 +160,7 @@ public T to(String tailAPIUrl, Class type, T instance) throws IOException } public List getAll(final String tailUrl, final Class type) { - List results = new ArrayList(); + List results = new ArrayList<>(); Iterator iterator = asIterator(tailUrl, type); while (iterator.hasNext()) { @@ -192,7 +174,7 @@ public List getAll(final String tailUrl, final Class type) { } public Iterator asIterator(final String tailApiUrl, final Class type) { - method("GET"); // Ensure we only use iterators for GET requests + method(GET); // Ensure we only use iterators for GET requests // Ensure that we don't submit any data and alert the user if (!data.isEmpty()) { @@ -207,7 +189,7 @@ public Iterator asIterator(final String tailApiUrl, final Class type) try { url = root.getAPIUrl(tailApiUrl); } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -259,7 +241,7 @@ private void fetch() { handleAPIError(e, connection); } } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -280,7 +262,7 @@ private void findNextUrl() throws MalformedURLException { } else { // Since the page query was not present, its safe to assume that we just // currently used the first page, so we can default to page 2 - this.url = new URL(url + (url.indexOf('?') > 0 ? '&' : '?') + "&page=2"); + this.url = new URL(url + (url.indexOf('?') > 0 ? '&' : '?') + "page=2"); } } }; @@ -293,35 +275,30 @@ private void submitAttachments(HttpURLConnection connection) throws IOException String charset = "UTF-8"; String CRLF = "\r\n"; // Line separator required by multipart/form-data. OutputStream output = connection.getOutputStream(); - PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true); - try { + try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true)) { for (Map.Entry paramEntry : data.entrySet()) { String paramName = paramEntry.getKey(); String param = GitlabAPI.MAPPER.writeValueAsString(paramEntry.getValue()); - writer.append("--" + boundary).append(CRLF); - writer.append("Content-Disposition: form-data; name=\"" + paramName + "\"").append(CRLF); - writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); + writer.append("--").append(boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"").append(paramName).append("\"").append(CRLF); + writer.append("Content-Type: text/plain; charset=").append(charset).append(CRLF); writer.append(CRLF).append(param).append(CRLF).flush(); } for (Map.Entry attachMentEntry : attachments.entrySet()) { File binaryFile = attachMentEntry.getValue(); - writer.append("--" + boundary).append(CRLF); - writer.append("Content-Disposition: form-data; name=\""+ attachMentEntry.getKey() +"\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF); - writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF); + writer.append("--").append(boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"").append(attachMentEntry.getKey()) + .append("\"; filename=\"").append(binaryFile.getName()).append("\"").append(CRLF); + writer.append("Content-Type: ").append(URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF); writer.append("Content-Transfer-Encoding: binary").append(CRLF); writer.append(CRLF).flush(); - Reader fileReader = new FileReader(binaryFile); - try { + try (Reader fileReader = new FileReader(binaryFile)) { IOUtils.copy(fileReader, output); - } finally { - fileReader.close(); } output.flush(); // Important before continuing with writer! writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary. } - writer.append("--" + boundary + "--").append(CRLF).flush(); - } finally { - writer.close(); + writer.append("--").append(boundary).append("--").append(CRLF).flush(); } } @@ -336,7 +313,7 @@ private boolean hasAttachments() { } private boolean hasOutput() { - return method.equals("POST") || method.equals("PUT") && !data.isEmpty(); + return method.equals(POST) || method.equals(PUT) && !data.isEmpty(); } private HttpURLConnection setupConnection(URL url) throws IOException { @@ -346,30 +323,31 @@ private HttpURLConnection setupConnection(URL url) throws IOException { if (apiToken != null && authMethod == AuthMethod.URL_PARAMETER) { String urlWithAuth = url.toString(); - urlWithAuth = urlWithAuth + (urlWithAuth.indexOf('?') > 0 ? '&' : '?') + tokenType.getTokenParamName() + "=" + apiToken; + urlWithAuth = urlWithAuth + (urlWithAuth.indexOf('?') > 0 ? '&' : '?') + + tokenType.getTokenParamName() + "=" + apiToken; url = new URL(urlWithAuth); } - HttpURLConnection connection = root.getProxy() != null ? (HttpURLConnection) url.openConnection(root.getProxy()) : (HttpURLConnection) url.openConnection(); + HttpURLConnection connection = root.getProxy() != null ? + (HttpURLConnection) url.openConnection(root.getProxy()) : (HttpURLConnection) url.openConnection(); if (apiToken != null && authMethod == AuthMethod.HEADER) { - connection.setRequestProperty(tokenType.getTokenHeaderName(), String.format(tokenType.getTokenHeaderFormat(), apiToken)); + connection.setRequestProperty(tokenType.getTokenHeaderName(), + String.format(tokenType.getTokenHeaderFormat(), apiToken)); } - final int requestTimeout = root.getRequestTimeout(); - if (requestTimeout > 0) { - connection.setReadTimeout(requestTimeout); - } + connection.setReadTimeout(root.getResponseReadTimeout()); + connection.setConnectTimeout(root.getConnectionTimeout()); try { - connection.setRequestMethod(method); + connection.setRequestMethod(method.name()); } catch (ProtocolException e) { // Hack in case the API uses a non-standard HTTP verb try { Field methodField = connection.getClass().getDeclaredField("method"); methodField.setAccessible(true); - methodField.set(connection, method); + methodField.set(connection, method.name()); } catch (Exception x) { - throw (IOException) new IOException("Failed to set the custom verb").initCause(x); + throw new IOException("Failed to set the custom verb", x); } } connection.setRequestProperty("User-Agent", root.getUserAgent()); @@ -396,7 +374,8 @@ private T parse(HttpURLConnection connection, Class type, T instance) thr return null; } } catch (SSLHandshakeException e) { - throw new SSLHandshakeException("You can disable certificate checking by setting ignoreCertificateErrors on GitlabHTTPRequestor. SSL Error: " + e.getMessage()); + throw new SSLException("You can disable certificate checking by setting ignoreCertificateErrors " + + "on GitlabHTTPRequestor.", e); } finally { IOUtils.closeQuietly(reader); } @@ -415,10 +394,9 @@ private InputStream wrapStream(HttpURLConnection connection, InputStream inputSt } private void handleAPIError(IOException e, HttpURLConnection connection) throws IOException { - if (e instanceof FileNotFoundException) { - throw e; // pass through 404 Not Found to allow the caller to handle it intelligently - } - if (e instanceof SocketTimeoutException && root.getRequestTimeout() > 0) { + if (e instanceof FileNotFoundException || // pass through 404 Not Found to allow the caller to handle it intelligently + e instanceof SocketTimeoutException || + e instanceof ConnectException) { throw e; } @@ -454,12 +432,7 @@ public void checkServerTrusted( } }; // Added per https://github.com/timols/java-gitlab-api/issues/44 - HostnameVerifier nullVerifier = new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; + HostnameVerifier nullVerifier = (hostname, session) -> true; try { SSLContext sc = SSLContext.getInstance("SSL"); @@ -467,8 +440,6 @@ public boolean verify(String hostname, SSLSession session) { HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Added per https://github.com/timols/java-gitlab-api/issues/44 HttpsURLConnection.setDefaultHostnameVerifier(nullVerifier); - } catch (Exception e) { - // Ignore it - } + } catch (Exception ignore) {} } } diff --git a/src/main/java/org/gitlab/api/http/Method.java b/src/main/java/org/gitlab/api/http/Method.java new file mode 100644 index 00000000..02655e7e --- /dev/null +++ b/src/main/java/org/gitlab/api/http/Method.java @@ -0,0 +1,11 @@ +package org.gitlab.api.http; + +/** + * Created by Oleg Shaburov on 03.05.2018 + * shaburov.o.a@gmail.com + */ +public enum Method { + + GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, TRACE; + +} diff --git a/src/main/java/org/gitlab/api/http/Query.java b/src/main/java/org/gitlab/api/http/Query.java index 521c493c..ad8db9a0 100644 --- a/src/main/java/org/gitlab/api/http/Query.java +++ b/src/main/java/org/gitlab/api/http/Query.java @@ -51,39 +51,7 @@ public Query append(final String name, final String value) throws UnsupportedEnc * @return this * @throws java.io.UnsupportedEncodingException If the provided value cannot be URL Encoded */ - public Query appendIf(final String name, final String value) throws UnsupportedEncodingException { - if (value != null) { - append(name, value); - } - return this; - } - - /** - * Conditionally append a parameter to the query - * if the value of the parameter is not null - * - * @param name Parameter name - * @param value Parameter value - * @return this - * @throws java.io.UnsupportedEncodingException If the provided value cannot be URL Encoded - */ - public Query appendIf(final String name, final Integer value) throws UnsupportedEncodingException { - if (value != null) { - append(name, value.toString()); - } - return this; - } - - /** - * Conditionally append a parameter to the query - * if the value of the parameter is not null - * - * @param name Parameter name - * @param value Parameter value - * @return this - * @throws java.io.UnsupportedEncodingException If the provided value cannot be URL Encoded - */ - public Query appendIf(final String name, final Boolean value) throws UnsupportedEncodingException { + public Query appendIf(final String name, final T value) throws UnsupportedEncodingException { if (value != null) { append(name, value.toString()); } diff --git a/src/main/java/org/gitlab/api/jackson/InstantDeserializer.java b/src/main/java/org/gitlab/api/jackson/InstantDeserializer.java new file mode 100644 index 00000000..5663826a --- /dev/null +++ b/src/main/java/org/gitlab/api/jackson/InstantDeserializer.java @@ -0,0 +1,80 @@ +package org.gitlab.api.jackson; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * A spezialized {@link Instant} deserializer that can parse different formats. + */ +public class InstantDeserializer extends StdDeserializer { + + private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER_WITH_SPACE_SEPARATOR = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .toFormatter(Locale.getDefault(Locale.Category.FORMAT)); + + public InstantDeserializer() { + super(Instant.class); + } + + @Override + public Instant deserialize(JsonParser parser, DeserializationContext context) throws IOException { + if (JsonToken.VALUE_NULL.equals(parser.getCurrentToken())) { + return null; + } + + final String text = parser.getText(); + + if (null == text || text.trim().isEmpty()) { + return null; + } + + return getFormatters() + .map(formatter -> { + try { + return formatter.parse(text, Instant::from); + } catch (DateTimeParseException e) { + return null; + } + }) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new JsonParseException("Unable to parse instant \"" + text + "\"", parser.getCurrentLocation())); + } + + private static Stream getFormatters() { + return Stream.of( + // English (Standard) Formats + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.LONG).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.LONG).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.LONG).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.MEDIUM).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.MEDIUM).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(Locale.ENGLISH), + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).withLocale(Locale.ENGLISH), + + // ISO Formats + LOCAL_DATE_TIME_FORMATTER_WITH_SPACE_SEPARATOR, + DateTimeFormatter.ISO_LOCAL_DATE_TIME, + DateTimeFormatter.ISO_DATE_TIME + ); + } + +} diff --git a/src/main/java/org/gitlab/api/models/GitlabBadge.java b/src/main/java/org/gitlab/api/models/GitlabBadge.java new file mode 100644 index 00000000..7de3f1a6 --- /dev/null +++ b/src/main/java/org/gitlab/api/models/GitlabBadge.java @@ -0,0 +1,70 @@ +package org.gitlab.api.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Olga Maciaszek-Sharma + */ +public class GitlabBadge { + + public static final String URL = "/badges"; + + private Integer id; + @JsonProperty("link_url") + private String linkUrl; + @JsonProperty("image_url") + private String imageUrl; + @JsonProperty("rendered_link_url") + private String renderedLinkUrl; + @JsonProperty("rendered_image_url") + private String renderedImageUrl; + private String kind; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getLinkUrl() { + return linkUrl; + } + + public void setLinkUrl(String linkUrl) { + this.linkUrl = linkUrl; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getRenderedLinkUrl() { + return renderedLinkUrl; + } + + public void setRenderedLinkUrl(String renderedLinkUrl) { + this.renderedLinkUrl = renderedLinkUrl; + } + + public String getRenderedImageUrl() { + return renderedImageUrl; + } + + public void setRenderedImageUrl(String renderedImageUrl) { + this.renderedImageUrl = renderedImageUrl; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } +} diff --git a/src/main/java/org/gitlab/api/models/GitlabBuildVariable.java b/src/main/java/org/gitlab/api/models/GitlabBuildVariable.java index e7e202f6..e26b9601 100644 --- a/src/main/java/org/gitlab/api/models/GitlabBuildVariable.java +++ b/src/main/java/org/gitlab/api/models/GitlabBuildVariable.java @@ -14,6 +14,13 @@ public GitlabBuildVariable() { public GitlabBuildVariable(String key, String value) { this.key = key; this.value = value; + this.variableType = VariableType.env_var; + } + + public GitlabBuildVariable(String key, String value, VariableType variableType) { + this.key = key; + this.value = value; + this.variableType = variableType; } @JsonProperty("key") @@ -22,6 +29,9 @@ public GitlabBuildVariable(String key, String value) { @JsonProperty("value") private String value; + @JsonProperty("variable_type") + private VariableType variableType; + public String getKey() { return key; } @@ -37,4 +47,18 @@ public String getValue() { public void setValue(String value) { this.value = value; } + + public VariableType getVariableType() { + return variableType; + } + + public void setVariableType(VariableType variableType) { + this.variableType = variableType; + } + + public enum VariableType { + env_var, + file + } + } diff --git a/src/main/java/org/gitlab/api/models/GitlabDate.java b/src/main/java/org/gitlab/api/models/GitlabDate.java new file mode 100644 index 00000000..43b2e845 --- /dev/null +++ b/src/main/java/org/gitlab/api/models/GitlabDate.java @@ -0,0 +1,55 @@ +package org.gitlab.api.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; +import java.util.List; + +/** + * A date, with no time or timezone. This is, given the lack of + * timezone, an object whose meaning is somewhat ill-defined, but we + * must use the API that the hexarchs^W^W Gitlab gives us. We're + * going to make this immutable, because that's less error-prone. And + * we won't provide a constructor from Date, because Date is an + * instant in time rather than a calendar period. + */ +public final class GitlabDate { + private int year; + private int month; + private int day; + + /** + * @param month and day are 1-based. + */ + public GitlabDate(int year, int month, int day) { + this.year = year; + this.month = month; + this.day = day; + } + + public int getYear() { + return year; + } + + public int getMonth() { + return month; + } + public int getDay() { + return day; + } + + public String toString() { + // Gitlab requires this specific format + return String.format("%04d-%02d-%02d", year, month, day); + } + + public int hashCode() { + return this.year * 31 * 12 + this.month * 31 + this.day; + } + + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + GitlabDate that = (GitlabDate) o; + return this.year == that.year && this.month == that.month && this.day == that.day; + } +} diff --git a/src/main/java/org/gitlab/api/models/GitlabDiscussion.java b/src/main/java/org/gitlab/api/models/GitlabDiscussion.java new file mode 100644 index 00000000..5b4b6887 --- /dev/null +++ b/src/main/java/org/gitlab/api/models/GitlabDiscussion.java @@ -0,0 +1,71 @@ +package org.gitlab.api.models; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A class representing a GitLab discussion. A discussion is a collection of + * notes. + * + * @author Patrizio Bonzani + */ +public class GitlabDiscussion { + + public static final String URL = "/discussions"; + + /** + * The ID of a discussion. + */ + private String id; + + /** + * The notes contained in this discussion. + */ + private List notes = new ArrayList(); + + @JsonProperty("individual_note") + private boolean individualNote; + + @SuppressWarnings("unused") + private GitlabDiscussion() {} + + public GitlabDiscussion(String id) { + this.id = id; + } + + /** + * Get the id of this discussion. + * + * @return The id of the discussion. + */ + public String getId() { + return id; + } + + /** + * Get the notes of this discussion. + * + * @return The notes contained in this discussion. + */ + public List getNotes() { + return Collections.unmodifiableList(notes); + } + + /** + * Add a note to the discussion. + * + * @param note The note to add to the discussion. + * @return true (as specified by {@link Collection#add}) + */ + public boolean addNote(GitlabNote note) { + return notes.add(note); + } + + public boolean isIndividualNote() { + return individualNote; + } +} diff --git a/src/main/java/org/gitlab/api/models/GitlabEvent.java b/src/main/java/org/gitlab/api/models/GitlabEvent.java new file mode 100644 index 00000000..640a8f66 --- /dev/null +++ b/src/main/java/org/gitlab/api/models/GitlabEvent.java @@ -0,0 +1,185 @@ +package org.gitlab.api.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; +import java.util.List; + +public class GitlabEvent { + + public final static String URL = "/events"; + + private String title; + + @JsonProperty("project_id") + private String projectId; + + @JsonProperty("action_name") + private String actionName; + + // nullable + @JsonProperty("target_id") + private Integer targetId; + + // nullable + @JsonProperty("target_iid") + private Integer targetIid; + + @JsonProperty("target_type") + private TargetType targetType; + + // It's not clear if this is nullable + @JsonProperty("author_id") + private Integer authorId; + + // nullable + @JsonProperty("target_title") + private String targetTitle; + + @JsonProperty("created_at") + private Date createdAt; + + // see above re "author" + private GitlabUser author; + + // see above re "author" + @JsonProperty("author_username") + private String authorUsername; + + @JsonProperty("push_data") + private GitlabPushData pushData; + + public String getTitle() { + return title; + } + + public String getProjectId() { + return projectId; + } + + /** + * It would be reasonable to expect that this matches up with + * ActionType, below, but it doesn't. The action type "pushed" is + * spelled "pushed to" in action_name (but it's spelled "pushed" + * in GitlabPushData). + */ + public String getActionName() { + return actionName; + } + + public Integer getTargetId() { + return targetId; + } + + public Integer getTargetIid() { + return targetIid; + } + + public TargetType getTargetType() { + return targetType; + } + + /** See() {@link #getAuthor()} for note */ + public Integer getAuthorId() { + return authorId; + } + + public String getTargetTitle() { + return targetTitle; + } + + public Date getCreatedAt() { + return createdAt; + } + + /** + * For many events, this seem to have nothing to do with any + * "author", but is in fact the user who performed the action. + * e.g. a push's "author" is not necessarily the author or + * committer of any commit, but the user doing the pushing. + */ + public GitlabUser getAuthor() { + return author; + } + + /** See {@link #getAuthor()} for note */ + public String getAuthorUsername() { + return authorUsername; + } + + public GitlabPushData getPushData() { + return pushData; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public void setTargetId(Integer targetId) { + this.targetId = targetId; + } + + public void setTargetIid(Integer targetIid) { + this.targetIid = targetIid; + } + + public void setTargetType(TargetType targetType) { + this.targetType = targetType; + } + + public void setAuthorId(Integer authorId) { + this.authorId = authorId; + } + + public void setTargetTitle(String targetTitle) { + this.targetTitle = targetTitle; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public void setAuthor(GitlabUser author) { + this.author = author; + } + + public void setAuthorUsername(String authorUsername) { + this.authorUsername = authorUsername; + } + + public void setPushData(GitlabPushData pushData) { + this.pushData = pushData; + } + + public enum ActionType { + created, + updated, + closed, + reopened, + pushed, + commented, + merged, + joined, + left, + destroyed, + expired + } + + public enum TargetType { + issue, + milestone, + merge_request, + note, + project, + snippet, + user + } +} diff --git a/src/main/java/org/gitlab/api/models/GitlabGroup.java b/src/main/java/org/gitlab/api/models/GitlabGroup.java index 3a456802..1003cc52 100644 --- a/src/main/java/org/gitlab/api/models/GitlabGroup.java +++ b/src/main/java/org/gitlab/api/models/GitlabGroup.java @@ -12,9 +12,24 @@ public class GitlabGroup { private String name; private String path; private String description; - + + @JsonProperty("membership_lock") + private Boolean membershipLock; + + @JsonProperty("share_with_group_lock") + private Boolean shareWithGroupLock; + + @JsonProperty("visibility") + private GitlabVisibility visibility; + @JsonProperty("lfs_enabled") private Boolean lfsEnabled; + + @JsonProperty("request_access_enabled") + private Boolean requestAccessEnabled; + + @JsonProperty("shared_runners_minutes_limit") + private Integer sharedRunnersMinutesLimit; @JsonProperty("avatar_url") private String avatarUrl; @@ -40,9 +55,6 @@ public class GitlabGroup { @JsonProperty("full_path") private String fullPath; - @JsonProperty("request_access_enabled") - private Boolean requestAccessEnabled; - public String getDescription() { return description; } @@ -131,6 +143,38 @@ public void setLdapCn(String ldapCn) { this.ldapCn = ldapCn; } + public Boolean getMembershipLock() { + return membershipLock; + } + + public void setMembershipLock(Boolean membershipLock) { + this.membershipLock = membershipLock; + } + + public Boolean getShareWithGroupLock() { + return shareWithGroupLock; + } + + public void setShareWithGroupLock(Boolean shareWithGroupLock) { + this.shareWithGroupLock = shareWithGroupLock; + } + + public GitlabVisibility getVisibility() { + return visibility; + } + + public void setVisibility(GitlabVisibility visibility) { + this.visibility = visibility; + } + + public Integer getSharedRunnersMinutesLimit() { + return sharedRunnersMinutesLimit; + } + + public void setSharedRunnersMinutesLimit(Integer sharedRunnersMinutesLimit) { + this.sharedRunnersMinutesLimit = sharedRunnersMinutesLimit; + } + public GitlabAccessLevel getLdapAccess() { if (ldapAccess == null) { return null; diff --git a/src/main/java/org/gitlab/api/models/GitlabIssue.java b/src/main/java/org/gitlab/api/models/GitlabIssue.java index 3d7f8cef..046fe609 100644 --- a/src/main/java/org/gitlab/api/models/GitlabIssue.java +++ b/src/main/java/org/gitlab/api/models/GitlabIssue.java @@ -1,9 +1,15 @@ package org.gitlab.api.models; +import java.time.LocalDate; import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +@JsonIgnoreProperties(ignoreUnknown = true) public class GitlabIssue { public enum Action { @@ -26,9 +32,31 @@ public enum Action { private String[] labels; private GitlabMilestone milestone; + private List assignees; private GitlabUser assignee; private GitlabUser author; + @JsonProperty("user_notes_count") + private Integer userNotesCount; + + @JsonProperty("upvotes") + private Integer upVotes; + + @JsonProperty("downvotes") + private Integer downVotes; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty("due_date") + private LocalDate dueDate; + + private Boolean confidential; + + @JsonProperty("discussion_locked") + private Boolean discussionLocked; + + @JsonProperty("time_stats") + private GitlabIssueTimeStats timeStats; + private String state; @JsonProperty("updated_at") @@ -37,6 +65,12 @@ public enum Action { @JsonProperty("created_at") private Date createdAt; + @JsonProperty("closed_at") + private Date closedAt; + + @JsonProperty("web_url") + private String webUrl; + public int getId() { return id; } @@ -93,6 +127,13 @@ public void setMilestone(GitlabMilestone milestone) { this.milestone = milestone; } + public List getAssignees() { + return assignees; + } + + public void setAssignees(List assignees) { + this.assignees = assignees; + } public GitlabUser getAssignee() { return assignee; } @@ -109,6 +150,62 @@ public void setAuthor(GitlabUser author) { this.author = author; } + public Integer getUserNotesCount() { + return userNotesCount; + } + + public void setUserNotesCount(Integer userNotesCount) { + this.userNotesCount = userNotesCount; + } + + public Integer getUpVotes() { + return upVotes; + } + + public void setUpVotes(Integer upVotes) { + this.upVotes = upVotes; + } + + public Integer getDownVotes() { + return downVotes; + } + + public void setDownVotes(Integer downVotes) { + this.downVotes = downVotes; + } + + public LocalDate getDueDate() { + return dueDate; + } + + public void setDueDate(LocalDate dueDate) { + this.dueDate = dueDate; + } + + public Boolean getConfidential() { + return confidential; + } + + public void setConfidential(Boolean confidential) { + this.confidential = confidential; + } + + public Boolean getDiscussionLocked() { + return discussionLocked; + } + + public void setDiscussionLocked(Boolean discussionLocked) { + this.discussionLocked = discussionLocked; + } + + public GitlabIssueTimeStats getTimeStats() { + return timeStats; + } + + public void setTimeStats(GitlabIssueTimeStats timeStats) { + this.timeStats = timeStats; + } + public String getState() { return state; } diff --git a/src/main/java/org/gitlab/api/models/GitlabMergeRequest.java b/src/main/java/org/gitlab/api/models/GitlabMergeRequest.java index 7663af17..11d037e5 100644 --- a/src/main/java/org/gitlab/api/models/GitlabMergeRequest.java +++ b/src/main/java/org/gitlab/api/models/GitlabMergeRequest.java @@ -10,7 +10,7 @@ public class GitlabMergeRequest { public static final String STATUS_OPENED = "opened"; public static final String STATUS_MERGED = "merged"; public static final String STATUS_CLOSED = "closed"; - + private Integer id; private Integer iid; private String title; @@ -23,10 +23,18 @@ public class GitlabMergeRequest { private GitlabMilestone milestone; private String[] labels; + private List changes; - private int upvotes; - private int downvotes; + private Integer upvotes; + + private Integer downvotes; + + @JsonProperty("updated_at") + private Date updatedAt; + + @JsonProperty("created_at") + private Date createdAt; @JsonProperty("target_branch") private String targetBranch; @@ -46,23 +54,64 @@ public class GitlabMergeRequest { @JsonProperty("milestone_id") private Integer milestoneId; - @JsonProperty("updated_at") - private Date updatedAt; + @JsonProperty("work_in_progress") + private Boolean workInProgress; - @JsonProperty("created_at") - private Date createdAt; + @JsonProperty("merge_when_pipeline_succeeds") + private Boolean mergeWhenPipelineSucceeds; - @JsonProperty("merge_commit_sha") - private String mergeCommitSHA; - @JsonProperty("merge_status") private String mergeStatus; + @JsonProperty("sha") + private String sha; + + @JsonProperty("merge_commit_sha") + private String mergeCommitSHA; + + @JsonProperty("user_notes_count") + private Integer userNotesCount; + + @JsonProperty("discussion_locked") + private Boolean discussionLocked; + + @JsonProperty("should_remove_source_branch") + private Boolean shouldRemoveSourceBranch; + + @JsonProperty("force_remove_source_branch") + private Boolean forceRemoveSourceBranch; + @JsonProperty("web_url") private String webUrl; - @JsonProperty("sha") - private String sha; + private Boolean squash; + + @JsonProperty("changes_count") + private String changesCount; + + @JsonProperty("merged_by") + private GitlabUser mergedBy; + + @JsonProperty("merged_at") + private Date mergedAt; + + @JsonProperty("closed_by") + private GitlabUser closedBy; + + @JsonProperty("closed_at") + private Date closedAt; + + @JsonProperty("latest_build_started_at") + private Date latestBuildStartedAt; + + @JsonProperty("latest_build_finished_at") + private Date latestBuildFinishedAt; + + @JsonProperty("first_deployed_to_production_at") + private Date firstDeployedToProductionAt; + + @JsonProperty("diff_refs") + private DiffRefs diffRefs; public Integer getId() { return id; @@ -202,7 +251,7 @@ public void setLabels(String[] labels) { this.labels = labels; } - public int getUpvotes() { + public Integer getUpvotes() { return upvotes; } @@ -210,7 +259,7 @@ public void setUpvotes(int upvotes) { this.upvotes = upvotes; } - public int getDownvotes() { + public Integer getDownvotes() { return downvotes; } @@ -281,4 +330,96 @@ public String getSha() { public void setSha(String sha) { this.sha = sha; } + + public Boolean isWorkInProgress() { + return workInProgress; + } + + + public Boolean isMergeWhenPipelineSucceeds() { + return mergeWhenPipelineSucceeds; + } + + public Integer getUserNotesCount() { + return userNotesCount; + } + + public Boolean isDiscussionLocked() { + return discussionLocked; + } + + public Boolean isShouldRemoveSourceBranch() { + return shouldRemoveSourceBranch; + } + + public Boolean isForceRemoveSourceBranch() { + return forceRemoveSourceBranch; + } + + public Boolean isSquash() { + return squash; + } + + public String getChangesCount() { + return changesCount; + } + + public GitlabUser getMergedBy() { + return mergedBy; + } + + public Date getMergedAt() { + return mergedAt; + } + + public GitlabUser getClosedBy() { + return closedBy; + } + + public Date getClosedAt() { + return closedAt; + } + + public Date getLatestBuildStartedAt() { + return latestBuildStartedAt; + } + + public Date getLatestBuildFinishedAt() { + return latestBuildFinishedAt; + } + + public Date getFirstDeployedToProductionAt() { + return firstDeployedToProductionAt; + } + + public String getBaseSha() { + return diffRefs == null ? null : diffRefs.baseSha; + } + + public String getHeadSha() { + return diffRefs == null ? null : diffRefs.headSha; + } + + public String getStartSha() { + return diffRefs == null ? null : diffRefs.startSha; + } + + /** + * Class representing the diff_refs json object, which is just a collection + * of sha references of the merge request. It is currently not provided by + * GitLab when fetching multiple merge requests. + * + * @author Patrizio Bonzani + */ + private static class DiffRefs { + + @JsonProperty("base_sha") + private String baseSha; + + @JsonProperty("head_sha") + private String headSha; + + @JsonProperty("start_sha") + private String startSha; + } } diff --git a/src/main/java/org/gitlab/api/models/GitlabMergeRequestApprovals.java b/src/main/java/org/gitlab/api/models/GitlabMergeRequestApprovals.java index 84069fed..81c6c991 100644 --- a/src/main/java/org/gitlab/api/models/GitlabMergeRequestApprovals.java +++ b/src/main/java/org/gitlab/api/models/GitlabMergeRequestApprovals.java @@ -9,6 +9,7 @@ public class GitlabMergeRequestApprovals { public static final String URL = "/approvals"; + public static final String APPROVERS_URL = "/approvers"; private Integer id; private Integer iid; @@ -39,6 +40,10 @@ public class GitlabMergeRequestApprovals { @JsonProperty("suggested_approvers") private List suggestedApprovers; + private List approvers; + + @JsonProperty("approver_groups") + private List approverGroups; public Integer getId() { return id; @@ -135,7 +140,7 @@ public List getApprovedBy() { public void setApprovedBy(List approvedBy) { this.approvedBy = approvedBy; } - + public List getSuggestedApprovers() { return suggestedApprovers; } @@ -143,4 +148,21 @@ public List getSuggestedApprovers() { public void setSuggestedApprovers(List suggestedApprovers) { this.suggestedApprovers = suggestedApprovers; } + + public List getApprovers() { + return approvers; + } + + public void setApprovers(List approvers) { + this.approvers = approvers; + } + + public List getApproverGroups() { + return approverGroups; + } + + public void setApproverGroups(List approverGroups) { + this.approverGroups = approverGroups; + } + } diff --git a/src/main/java/org/gitlab/api/models/GitlabPipeline.java b/src/main/java/org/gitlab/api/models/GitlabPipeline.java index 31927db5..c2f55b95 100644 --- a/src/main/java/org/gitlab/api/models/GitlabPipeline.java +++ b/src/main/java/org/gitlab/api/models/GitlabPipeline.java @@ -1,23 +1,73 @@ package org.gitlab.api.models; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.gitlab.api.jackson.InstantDeserializer; + +import java.time.Instant; public class GitlabPipeline { public static final String URL = "/pipelines"; + public static final String CREATE_URL = "/pipeline"; + @JsonProperty("id") private Integer id; - @JsonProperty("ref") - private String ref; - @JsonProperty("sha") private String sha; + @JsonProperty("ref") + private String ref; + @JsonProperty("status") private String status; + @JsonProperty("web_url") + private String webUrl; + + @JsonProperty("before_sha") + private String beforeSha; + + @JsonProperty("tag") + private boolean tag; + + @JsonProperty("yaml_errors") + private String yamlErrors; + + @JsonProperty("user") + private GitlabUser user; + + @JsonProperty("created_at") + @JsonDeserialize(using = InstantDeserializer.class) + private Instant createdAt; + + @JsonProperty("updated_at") + @JsonDeserialize(using = InstantDeserializer.class) + private Instant updatedAt; + + @JsonProperty("started_at") + @JsonDeserialize(using = InstantDeserializer.class) + private Instant startedAt; + + @JsonProperty("finished_at") + @JsonDeserialize(using = InstantDeserializer.class) + private Instant finishedAt; + + @JsonProperty("committed_at") + @JsonDeserialize(using = InstantDeserializer.class) + private Instant committedAt; + + @JsonProperty("duration") + private int duration; + + @JsonProperty("coverage") + private String coverage; + + @JsonProperty("detailed_status") + private DetailedStatus detailedStatus; + public Integer getId() { return id; } @@ -26,14 +76,6 @@ public void setId(Integer id) { this.id = id; } - public String getRef() { - return ref; - } - - public void setRef(String ref) { - this.ref = ref; - } - public String getSha() { return sha; } @@ -42,6 +84,14 @@ public void setSha(String sha) { this.sha = sha; } + public String getRef() { + return ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + public String getStatus() { return status; } @@ -49,4 +99,205 @@ public String getStatus() { public void setStatus(String status) { this.status = status; } + + public String getWebUrl() { + return webUrl; + } + + public void setWebUrl(String webUrl) { + this.webUrl = webUrl; + } + + public String getBeforeSha() { + return beforeSha; + } + + public void setBeforeSha(String beforeSha) { + this.beforeSha = beforeSha; + } + + public boolean isTag() { + return tag; + } + + public void setTag(boolean tag) { + this.tag = tag; + } + + public String getYamlErrors() { + return yamlErrors; + } + + public void setYamlErrors(String yamlErrors) { + this.yamlErrors = yamlErrors; + } + + public GitlabUser getUser() { + return user; + } + + public void setUser(GitlabUser user) { + this.user = user; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } + + public Instant getStartedAt() { + return startedAt; + } + + public void setStartedAt(Instant startedAt) { + this.startedAt = startedAt; + } + + public Instant getFinishedAt() { + return finishedAt; + } + + public void setFinishedAt(Instant finishedAt) { + this.finishedAt = finishedAt; + } + + public Instant getCommittedAt() { + return committedAt; + } + + public void setCommittedAt(Instant committedAt) { + this.committedAt = committedAt; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public String getCoverage() { + return coverage; + } + + public void setCoverage(String coverage) { + this.coverage = coverage; + } + + public DetailedStatus getDetailedStatus() { + return detailedStatus; + } + + public void setDetailedStatus(DetailedStatus detailedStatus) { + this.detailedStatus = detailedStatus; + } + + public static class DetailedStatus { + + private String icon; + + private String text; + + private String label; + + private String group; + + private String tooltip; + + @JsonProperty("has_details") + private String hasDetails; + + @JsonProperty("details_path") + private String detailsPath; + + private String illustration; + + private String favicon; + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTooltip() { + return tooltip; + } + + public void setTooltip(String tooltip) { + this.tooltip = tooltip; + } + + public String getHasDetails() { + return hasDetails; + } + + public void setHasDetails(String hasDetails) { + this.hasDetails = hasDetails; + } + + public String getDetailsPath() { + return detailsPath; + } + + public void setDetailsPath(String detailsPath) { + this.detailsPath = detailsPath; + } + + public String getIllustration() { + return illustration; + } + + public void setIllustration(String illustration) { + this.illustration = illustration; + } + + public String getFavicon() { + return favicon; + } + + public void setFavicon(String favicon) { + this.favicon = favicon; + } + + } + } diff --git a/src/main/java/org/gitlab/api/models/GitlabProject.java b/src/main/java/org/gitlab/api/models/GitlabProject.java index 33fb923c..a245d23b 100644 --- a/src/main/java/org/gitlab/api/models/GitlabProject.java +++ b/src/main/java/org/gitlab/api/models/GitlabProject.java @@ -125,9 +125,15 @@ public class GitlabProject { @JsonProperty("forked_from_project") private GitlabProject forkedFrom; - @JsonProperty("is_printing_merge_request_link_enabled") + @JsonProperty("printing_merge_request_link_enabled") private Boolean printingMergeRequestLinkEnabled; + @JsonProperty("import_status") + private String importStatus; + + @JsonProperty("initialize_with_readme") + private Boolean initializeWithReadme; + public Integer getId() { return id; } @@ -472,6 +478,14 @@ public void setPrintingMergeRequestLinkEnabled(Boolean printingMergeRequestLinkE this.printingMergeRequestLinkEnabled = printingMergeRequestLinkEnabled; } + public Boolean isInitializeWithReadme() { + return initializeWithReadme; + } + + public void setInitializeWithReadme(Boolean initializeWithReadme) { + this.initializeWithReadme = initializeWithReadme; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/gitlab/api/models/GitlabProjectHook.java b/src/main/java/org/gitlab/api/models/GitlabProjectHook.java index 32f63ed4..34326f4c 100644 --- a/src/main/java/org/gitlab/api/models/GitlabProjectHook.java +++ b/src/main/java/org/gitlab/api/models/GitlabProjectHook.java @@ -25,13 +25,25 @@ public class GitlabProjectHook { @JsonProperty("tag_push_events") private boolean tagPushEvents; - + @JsonProperty("created_at") private Date createdAt; @JsonProperty("enable_ssl_verification") private boolean sslVerificationEnabled; + @JsonProperty("note_events") + private boolean noteEvents; + + @JsonProperty("job_events") + private boolean jobEvents; + + @JsonProperty("pipeline_events") + private boolean pipelineEvents; + + @JsonProperty("wiki_page_events") + private boolean wikiPageEvents; + public String getId() { return id; } @@ -80,15 +92,14 @@ public void setMergeRequestsEvents(boolean mergeRequestsEvents) { this.mergeRequestsEvents = mergeRequestsEvents; } - public boolean isTagPushEvents() { return tagPushEvents; } - + public void setTagPushEvents(boolean tagPushEvents) { this.tagPushEvents = tagPushEvents; } - + public Date getCreatedAt() { return createdAt; } @@ -105,4 +116,19 @@ public void setSslVerificationEnabled(boolean sslVerificationEnabled) { this.sslVerificationEnabled = sslVerificationEnabled; } + public boolean isNoteEvents() { return noteEvents; } + + public void setNoteEvents(boolean noteEvents) { this.noteEvents = noteEvents; } + + public boolean isJobEvents() { return jobEvents; } + + public void setJobEvents(boolean jobEvents) { this.jobEvents = jobEvents; } + + public boolean isPipelineEvents() { return pipelineEvents; } + + public void setPipelineEvents(boolean pipelineEvents) { this.pipelineEvents = pipelineEvents; } + + public boolean isWikiPageEvents() { return wikiPageEvents; } + + public void setWikiPageEvents(boolean wikiPageEvents) { this.wikiPageEvents = wikiPageEvents; } } diff --git a/src/main/java/org/gitlab/api/models/GitlabPushData.java b/src/main/java/org/gitlab/api/models/GitlabPushData.java new file mode 100644 index 00000000..95f8f4d7 --- /dev/null +++ b/src/main/java/org/gitlab/api/models/GitlabPushData.java @@ -0,0 +1,85 @@ +package org.gitlab.api.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; +import java.util.List; + +public class GitlabPushData { + + @JsonProperty("commit_count") + private int commitCount; + + @JsonProperty("action") + private GitlabEvent.ActionType action; + + @JsonProperty("ref_type") + private String refType; + + @JsonProperty("commit_from") + private String commitFrom; + + @JsonProperty("commit_to") + private String commitTo; + + private String ref; + + @JsonProperty("commit_title") + private String commitTitle; + + public int getCommitCount() { + return commitCount; + } + + public GitlabEvent.ActionType getAction() { + return action; + } + + public String getRefType() { + return refType; + } + + public String getCommitFrom() { + return commitFrom; + } + + public String getCommitTo() { + return commitTo; + } + + public String getRef() { + return ref; + } + + public String getCommitTitle() { + return commitTitle; + } + + public void setCommitCount(int commitCount) { + this.commitCount = commitCount; + } + + public void setAction(GitlabEvent.ActionType action) { + this.action = action; + } + + public void setRefType(String refType) { + this.refType = refType; + } + + public void setCommitFrom(String commitFrom) { + this.commitFrom = commitFrom; + } + + public void setCommitTo(String commitTo) { + this.commitTo = commitTo; + } + + public void setRef(String ref) { + this.ref = ref; + } + + public void setCommitTitle(String commitTitle) { + this.commitTitle = commitTitle; + } +} diff --git a/src/main/java/org/gitlab/api/models/GitlabRunner.java b/src/main/java/org/gitlab/api/models/GitlabRunner.java index 27082868..d03d0561 100644 --- a/src/main/java/org/gitlab/api/models/GitlabRunner.java +++ b/src/main/java/org/gitlab/api/models/GitlabRunner.java @@ -7,6 +7,8 @@ import java.util.List; public class GitlabRunner { + public static final String URL = "/runners"; + public enum RunnerScope { SPECIFIC("specific"), SHARED("shared"), @@ -54,7 +56,10 @@ public String getScope() { private String architecture; @JsonProperty("projects") private List projects; - + @JsonProperty("online") + private Boolean online; + @JsonProperty("status") + private String status; public Integer getId() { return this.id; @@ -161,4 +166,19 @@ public void setArchitecture(String architecture) { this.architecture = architecture; } + public Boolean getOnline() { + return this.online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + public String getStatus() { + return this.status; + } + + public void setStatus(String status) { + this.status = status; + } } diff --git a/src/main/java/org/gitlab/api/models/GitlabTag.java b/src/main/java/org/gitlab/api/models/GitlabTag.java index 4ed51596..c0ec8cbb 100644 --- a/src/main/java/org/gitlab/api/models/GitlabTag.java +++ b/src/main/java/org/gitlab/api/models/GitlabTag.java @@ -7,7 +7,7 @@ public class GitlabTag { public final static String URL = "/repository/tags"; @JsonProperty("commit") - private GitlabBranchCommit commit; + private GitlabCommit commit; @JsonProperty("release") private GitlabRelease release; @@ -18,11 +18,11 @@ public class GitlabTag { @JsonProperty("message") private String message; - public GitlabBranchCommit getCommit() { + public GitlabCommit getCommit() { return commit; } - public void setCommit(GitlabBranchCommit commit) { + public void setCommit(GitlabCommit commit) { this.commit = commit; } diff --git a/src/main/java/org/gitlab/api/models/GitlabUser.java b/src/main/java/org/gitlab/api/models/GitlabUser.java index 9e82dbb1..9f6cff5f 100644 --- a/src/main/java/org/gitlab/api/models/GitlabUser.java +++ b/src/main/java/org/gitlab/api/models/GitlabUser.java @@ -1,10 +1,12 @@ package org.gitlab.api.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Date; import java.util.List; -import com.fasterxml.jackson.annotation.JsonProperty; - +@JsonIgnoreProperties(ignoreUnknown = true) public class GitlabUser { public static String URL = "/users"; @@ -64,6 +66,9 @@ public class GitlabUser { @JsonProperty("can_create_team") private Boolean _canCreateTeam; + @JsonProperty("external") + private boolean _external; + @JsonProperty("avatar_url") private String _avatarUrl; @@ -247,6 +252,14 @@ public void setCanCreateTeam(boolean canCreateTeam) { this._canCreateTeam = canCreateTeam; } + public boolean isExternal() { + return _external; + } + + public void setExternal(boolean external) { + _external = external; + } + public String getAvatarUrl() { return _avatarUrl; } diff --git a/src/main/java/org/gitlab/api/models/SortOrder.java b/src/main/java/org/gitlab/api/models/SortOrder.java new file mode 100644 index 00000000..a181791f --- /dev/null +++ b/src/main/java/org/gitlab/api/models/SortOrder.java @@ -0,0 +1,12 @@ +package org.gitlab.api.models; + +public enum SortOrder { + ASC, DESC; + + public static final SortOrder DEFAULT = DESC; + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/org/gitlab/api/query/PaginationQuery.java b/src/main/java/org/gitlab/api/query/PaginationQuery.java new file mode 100644 index 00000000..7996ec3f --- /dev/null +++ b/src/main/java/org/gitlab/api/query/PaginationQuery.java @@ -0,0 +1,40 @@ +package org.gitlab.api.query; + +import org.gitlab.api.http.Query; + +import java.io.UnsupportedEncodingException; + +public class PaginationQuery extends Query { + + public static final String PARAM_PAGE = "page"; + public static final String PARAM_PER_PAGE = "per_page"; + public static final int MAX_ITEMS_PER_PAGE = 100; + + public void setPage(int page) { + try { + append(PARAM_PAGE, String.valueOf(page)); + } catch (UnsupportedEncodingException ignored) { + } + } + + public void setPerPage(int perPage) { + if (perPage > MAX_ITEMS_PER_PAGE) { + throw new IllegalArgumentException("Max value for perPage is " + MAX_ITEMS_PER_PAGE); + } + try { + append(PARAM_PER_PAGE, String.valueOf(perPage)); + } catch (UnsupportedEncodingException ignored) { + } + } + + public PaginationQuery withPage(int page) { + setPage(page); + return this; + } + + public PaginationQuery withPerPage(int perPage) { + setPerPage(perPage); + return this; + } + +} diff --git a/src/main/java/org/gitlab/api/query/PipelinesQuery.java b/src/main/java/org/gitlab/api/query/PipelinesQuery.java new file mode 100644 index 00000000..5c919e2c --- /dev/null +++ b/src/main/java/org/gitlab/api/query/PipelinesQuery.java @@ -0,0 +1,100 @@ +package org.gitlab.api.query; + +import java.io.UnsupportedEncodingException; + +public class PipelinesQuery extends PaginationQuery { + + public void setScope(String scope) throws UnsupportedEncodingException { + appendIf("scope", scope); + } + + public PipelinesQuery withScope(String scope) throws UnsupportedEncodingException { + this.setScope(scope); + return this; + } + + public void setStatus(String status) throws UnsupportedEncodingException { + appendIf("status", status); + } + + public PipelinesQuery withStatus(String status) throws UnsupportedEncodingException { + this.setStatus(status); + return this; + } + + public void setRef(String ref) throws UnsupportedEncodingException { + appendIf("ref", ref); + } + + public PipelinesQuery withRef(String ref) throws UnsupportedEncodingException { + this.setRef(ref); + return this; + } + + public void setSha(String sha) throws UnsupportedEncodingException { + appendIf("sha", sha); + } + + public PipelinesQuery withSha(String sha) throws UnsupportedEncodingException { + this.setSha(sha); + return this; + } + + public void setYamlErrors(Boolean yamlErrors) throws UnsupportedEncodingException { + appendIf("yaml_errors", yamlErrors); + } + + public PipelinesQuery withYamlErrors(Boolean yamlErrors) throws UnsupportedEncodingException { + this.setYamlErrors(yamlErrors); + return this; + } + + public void setName(String name) throws UnsupportedEncodingException { + appendIf("name", name); + } + + public PipelinesQuery withName(String name) throws UnsupportedEncodingException { + this.setName(name); + return this; + } + + public void setUsername(String username) throws UnsupportedEncodingException { + appendIf("username", username); + } + + public PipelinesQuery withUsername(String username) throws UnsupportedEncodingException { + this.setUsername(username); + return this; + } + + public void setOrderBy(String orderBy) throws UnsupportedEncodingException { + appendIf("order_by", orderBy); + } + + public PipelinesQuery withOrderBy(String orderBy) throws UnsupportedEncodingException { + this.setOrderBy(orderBy); + return this; + } + + public void setSort(String sort) throws UnsupportedEncodingException { + appendIf("sort", sort); + } + + public PipelinesQuery withSort(String sort) throws UnsupportedEncodingException { + this.setSort(sort); + return this; + } + + @Override + public PipelinesQuery withPage(int page) { + super.withPage(page); + return this; + } + + @Override + public PipelinesQuery withPerPage(int perPage) { + super.withPerPage(perPage); + return this; + } + +} diff --git a/src/main/java/org/gitlab/api/query/ProjectsQuery.java b/src/main/java/org/gitlab/api/query/ProjectsQuery.java new file mode 100644 index 00000000..02fd4142 --- /dev/null +++ b/src/main/java/org/gitlab/api/query/ProjectsQuery.java @@ -0,0 +1,192 @@ +package org.gitlab.api.query; + +import org.gitlab.api.models.GitlabAccessLevel; + +import java.io.UnsupportedEncodingException; + +public class ProjectsQuery extends PaginationQuery { + + public static final String PARAM_ARCHIVED = "archived"; + public static final String PARAM_VISIBILITY = "visibility"; + public static final String PARAM_ORDER_BY = "order_by"; + public static final String PARAM_SORT = "sort"; + public static final String PARAM_SEARCH = "search"; + public static final String PARAM_SIMPLE = "simple"; + public static final String PARAM_OWNED = "owned"; + public static final String PARAM_MEMBERSHIP = "membership"; + public static final String PARAM_STARRED = "starred"; + public static final String PARAM_STATISTICS = "statistics"; + public static final String PARAM_WITH_CUSTOM_ATTRIBUTES = "with_custom_attributes"; + public static final String PARAM_WITH_ISSUES_ENABLED = "with_issues_enabled"; + public static final String PARAM_WITH_MERGE_REQUESTS_ENABLED = "with_merge_requests_enabled"; + public static final String PARAM_WITH_PROGRAMMING_LANGUAGE = "with_programming_language"; + public static final String PARAM_WIKI_CHECKSUM_FAILED = "wiki_checksum_failed"; + public static final String PARAM_REPOSITORY_CHECKSUM_FAILED = "repository_checksum_failed"; + public static final String PARAM_MIN_ACCESS_LEVEL = "min_access_level"; + + public void setArchived(Boolean archived) throws UnsupportedEncodingException { + appendIf(PARAM_ARCHIVED, archived); + } + + public ProjectsQuery withArchived(Boolean archived) throws UnsupportedEncodingException { + setArchived(archived); + return this; + } + + public void setVisibility(String visibility) throws UnsupportedEncodingException { + appendIf(PARAM_VISIBILITY, visibility); + } + + public ProjectsQuery withVisibility(String visibility) throws UnsupportedEncodingException { + setVisibility(visibility); + return this; + } + + public void setOrderBy(String orderBy) throws UnsupportedEncodingException { + appendIf(PARAM_ORDER_BY, orderBy); + } + + public ProjectsQuery withOrderBy(String orderBy) throws UnsupportedEncodingException { + setOrderBy(orderBy); + return this; + } + + public void setSort(String sort) throws UnsupportedEncodingException { + appendIf(PARAM_SORT, sort); + } + + public ProjectsQuery withSort(String sort) throws UnsupportedEncodingException { + setSort(sort); + return this; + } + + public void setSearch(String search) throws UnsupportedEncodingException { + appendIf(PARAM_SEARCH, search); + } + + public ProjectsQuery withSearch(String search) throws UnsupportedEncodingException { + setSearch(search); + return this; + } + + public void setSimple(Boolean simple) throws UnsupportedEncodingException { + appendIf(PARAM_SIMPLE, simple); + } + + public ProjectsQuery withSimple(Boolean simple) throws UnsupportedEncodingException { + setSimple(simple); + return this; + } + + public void setOwned(Boolean owned) throws UnsupportedEncodingException { + appendIf(PARAM_OWNED, owned); + } + + public ProjectsQuery withOwned(Boolean owned) throws UnsupportedEncodingException { + setOwned(owned); + return this; + } + + public void setMembership(Boolean membership) throws UnsupportedEncodingException { + appendIf(PARAM_MEMBERSHIP, membership); + } + + public ProjectsQuery withMembership(Boolean membership) throws UnsupportedEncodingException { + setMembership(membership); + return this; + } + + public void setStarred(Boolean starred) throws UnsupportedEncodingException { + appendIf(PARAM_STARRED, starred); + } + + public ProjectsQuery withStarred(Boolean starred) throws UnsupportedEncodingException { + setStarred(starred); + return this; + } + + public void setStatistics(Boolean statistics) throws UnsupportedEncodingException { + appendIf(PARAM_STATISTICS, statistics); + } + + public ProjectsQuery withStatistics(Boolean statistics) throws UnsupportedEncodingException { + setStatistics(statistics); + return this; + } + + public void setWithCustomAttributes(Boolean withCustomAttributes) throws UnsupportedEncodingException { + appendIf(PARAM_WITH_CUSTOM_ATTRIBUTES, withCustomAttributes); + } + + public ProjectsQuery withWithCustomAttributes(Boolean withCustomAttributes) throws UnsupportedEncodingException { + setWithCustomAttributes(withCustomAttributes); + return this; + } + + public void setWithIssuesEnabled(Boolean withIssuesEnabled) throws UnsupportedEncodingException { + appendIf(PARAM_WITH_ISSUES_ENABLED, withIssuesEnabled); + } + + public ProjectsQuery withWithIssuesEnabled(Boolean withIssuesEnabled) throws UnsupportedEncodingException { + setWithIssuesEnabled(withIssuesEnabled); + return this; + } + + public void setWithMergeRequestsEnabled(Boolean withMergeRequestsEnabled) throws UnsupportedEncodingException { + appendIf(PARAM_WITH_MERGE_REQUESTS_ENABLED, withMergeRequestsEnabled); + } + + public ProjectsQuery withWithMergeRequestsEnabled(Boolean withMergeRequestsEnabled) throws UnsupportedEncodingException { + setWithMergeRequestsEnabled(withMergeRequestsEnabled); + return this; + } + + public void setWithProgrammingLanguage(String withProgrammingLanguage) throws UnsupportedEncodingException { + appendIf(PARAM_WITH_PROGRAMMING_LANGUAGE, withProgrammingLanguage); + } + + public ProjectsQuery withWithProgrammingLanguage(String withProgrammingLanguage) throws UnsupportedEncodingException { + setWithProgrammingLanguage(withProgrammingLanguage); + return this; + } + + public void setWikiChecksumFailed(Boolean wikiChecksumFailed) throws UnsupportedEncodingException { + appendIf(PARAM_WIKI_CHECKSUM_FAILED, wikiChecksumFailed); + } + + public ProjectsQuery withWikiChecksumFailed(Boolean wikiChecksumFailed) throws UnsupportedEncodingException { + setWikiChecksumFailed(wikiChecksumFailed); + return this; + } + + public void setRepositoryChecksumFailed(Boolean repositoryChecksumFailed) throws UnsupportedEncodingException { + appendIf(PARAM_REPOSITORY_CHECKSUM_FAILED, repositoryChecksumFailed); + } + + public ProjectsQuery withRepositoryChecksumFailed(Boolean repositoryChecksumFailed) throws UnsupportedEncodingException { + setRepositoryChecksumFailed(repositoryChecksumFailed); + return this; + } + + public void setMinAccessLevel(GitlabAccessLevel minAccessLevel) throws UnsupportedEncodingException { + appendIf(PARAM_MIN_ACCESS_LEVEL, minAccessLevel); + } + + public ProjectsQuery withMinAccessLevel(GitlabAccessLevel minAccessLevel) throws UnsupportedEncodingException { + setMinAccessLevel(minAccessLevel); + return this; + } + + @Override + public ProjectsQuery withPage(int page) { + super.withPage(page); + return this; + } + + @Override + public ProjectsQuery withPerPage(int perPage) { + super.withPerPage(perPage); + return this; + } + +} diff --git a/src/test/java/org/gitlab/api/GitlabAPIIT.java b/src/test/java/org/gitlab/api/GitlabAPIIT.java index 3d087a2d..f8d2fc62 100644 --- a/src/test/java/org/gitlab/api/GitlabAPIIT.java +++ b/src/test/java/org/gitlab/api/GitlabAPIIT.java @@ -1,27 +1,27 @@ package org.gitlab.api; -import org.gitlab.api.models.GitlabBuildVariable; -import org.gitlab.api.models.GitlabGroup; -import org.gitlab.api.models.GitlabNamespace; -import org.gitlab.api.models.GitlabProject; -import org.gitlab.api.models.GitlabUser; +import org.gitlab.api.models.*; import org.junit.BeforeClass; import org.junit.Test; import java.io.FileNotFoundException; -import java.io.IOException; import java.net.URL; import java.util.List; import java.util.UUID; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; public class GitlabAPIIT { - static GitlabAPI api; + private static GitlabAPI api; - private static final String TEST_URL = "http://" + System.getProperty("docker.host.address", "localhost") + ":" + System.getProperty("gitlab.port", "18080"); - String rand = createRandomString(); + private static final String TEST_URL = "http://" + System.getProperty("docker.host.address", "localhost") + + ":" + System.getProperty("gitlab.port", "18080"); @BeforeClass public static void getApi() { @@ -29,9 +29,10 @@ public static void getApi() { } @Test - public void Check_invalid_credentials() throws IOException { + public void checkInvalidCredentials() throws Exception { try { - api.dispatch().with("login", "INVALID").with("password", createRandomString()).to("session", GitlabUser.class); + api.dispatch().with("login", "INVALID").with("password", createRandomString()) + .to("session", GitlabUser.class); } catch (GitlabAPIException e) { final String message = e.getMessage(); if (!message.equals("{\"message\":\"401 Unauthorized\"}")) { @@ -41,30 +42,26 @@ public void Check_invalid_credentials() throws IOException { } } } - @Test - public void testAllProjects() throws IOException { - api.getAllProjects(); - } @Test - public void testConnect() throws IOException { - assertEquals(GitlabAPI.class, api.getClass()); + public void testAllProjects() { + assertThat(api.getAllProjects(), is(notNullValue())); } @Test - public void testGetAPIUrl() throws IOException { + public void testGetAPIUrl() throws Exception { URL expected = new URL(TEST_URL + "/api/v4/"); assertEquals(expected, api.getAPIUrl("")); } @Test - public void testGetUrl() throws IOException { + public void testGetUrl() throws Exception { URL expected = new URL(TEST_URL); assertEquals(expected + "/", api.getUrl("").toString()); } @Test - public void testCreateUpdateDeleteVariable() throws IOException { + public void testCreateUpdateDeleteVariable() throws Exception { String key = randVal("key"); String value = randVal("value"); String newValue = randVal("new_value"); @@ -85,16 +82,13 @@ public void testCreateUpdateDeleteVariable() throws IOException { api.updateBuildVariable(project.getId(), key, newValue); - GitlabBuildVariable postUpdate = api.getBuildVariable(project.getId(), key); - assertNotNull(postUpdate); assertEquals(postUpdate.getKey(), variable.getKey()); assertNotEquals(postUpdate.getValue(), variable.getValue()); assertEquals(postUpdate.getValue(), newValue); - api.deleteBuildVariable(project.getId(), key); // expect a 404, but we have no access to it @@ -104,16 +98,12 @@ public void testCreateUpdateDeleteVariable() throws IOException { } catch (FileNotFoundException thisIsSoOddForAnRESTApiClient) { assertTrue(true); // expected } - api.deleteProject(project.getId()); } @Test - public void testCreateUpdateDeleteUser() throws IOException, InterruptedException { - + public void testCreateUpdateDeleteUser() throws Exception { String password = randVal("$%password"); - - GitlabUser gitUser = api.createUser(randVal("testEmail@gitlabapitest.com"), password, randVal("userName"), @@ -128,6 +118,7 @@ public void testCreateUpdateDeleteUser() throws IOException, InterruptedExceptio randVal("bio"), false, false, + true, false); assertNotNull(gitUser); @@ -137,14 +128,14 @@ public void testCreateUpdateDeleteUser() throws IOException, InterruptedExceptio assertEquals(refetched.getUsername(), gitUser.getUsername()); api.updateUser(gitUser.getId(), gitUser.getEmail(), password, gitUser.getUsername(), - gitUser.getName(), "newSkypeId", gitUser.getLinkedin(), gitUser.getTwitter(), gitUser.getWebsiteUrl(), - 10 /* project limit does not come back on GET */, gitUser.getExternUid(), gitUser.getExternProviderName(), - gitUser.getBio(), gitUser.isAdmin(), gitUser.isCanCreateGroup()); - + gitUser.getName(), "newSkypeId", gitUser.getLinkedin(), + gitUser.getTwitter(), gitUser.getWebsiteUrl(), + 10 /* project limit does not come back on GET */, + gitUser.getExternUid(), gitUser.getExternProviderName(), + gitUser.getBio(), gitUser.isAdmin(), gitUser.isCanCreateGroup(), gitUser.isExternal()); GitlabUser postUpdate = api.getUserViaSudo(gitUser.getUsername()); - assertNotNull(postUpdate); assertEquals(postUpdate.getSkype(), "newSkypeId"); @@ -162,12 +153,10 @@ public void testCreateUpdateDeleteUser() throws IOException, InterruptedExceptio } catch (FileNotFoundException thisIsSoOddForAnRESTApiClient) { assertTrue(true); // expected } - - } @Test - public void testGetGroupByPath() throws IOException { + public void testGetGroupByPath() throws Exception { // Given String name = "groupName"; String path = "groupPath"; @@ -186,43 +175,70 @@ public void testGetGroupByPath() throws IOException { // Cleanup api.deleteGroup(group.getId()); } + + @Test + public void testCreateAndUpdateGroup() throws Exception { + // Given + GitlabGroup originalGroup = new GitlabGroup(); + originalGroup.setDescription("test description"); + originalGroup.setName("groupNameTest"); + originalGroup.setPath("groupPathTest"); + originalGroup.setVisibility(GitlabVisibility.INTERNAL); + + GitlabGroup newGroup = api.createGroup(originalGroup, null); + assertNotNull(newGroup); + assertEquals(originalGroup.getId(), newGroup.getId()); + assertEquals(originalGroup.getName(), newGroup.getName()); + assertEquals(originalGroup.getPath(), newGroup.getPath()); + assertEquals(originalGroup.getDescription(), newGroup.getDescription()); + assertEquals(originalGroup.getVisibility(), newGroup.getVisibility()); + + GitlabGroup groupToUpdate = new GitlabGroup(); + groupToUpdate.setId(newGroup.getId()); + groupToUpdate.setVisibility(GitlabVisibility.PRIVATE); + + // When + GitlabGroup updatedGroup = api.updateGroup(newGroup, null); + + // Then: + assertNotNull(updatedGroup); + assertEquals(groupToUpdate.getVisibility(), updatedGroup.getVisibility()); + + // Cleanup + api.deleteGroup(updatedGroup.getId()); + } @Test - public void testGetMembershipProjects() throws IOException { - final List membershipProjects = api.getMembershipProjects(); - assertTrue(membershipProjects.size() >= 0); + public void testGetMembershipProjects() throws Exception { + assertThat(api.getMembershipProjects(), not(nullValue())); } @Test - public void Check_get_owned_projects() throws IOException { - final List ownedProjects = api.getOwnedProjects(); - assertTrue(ownedProjects.size() >= 0); + public void checkGetOwnedProjects() throws Exception { + assertThat(api.getOwnedProjects(), not(nullValue())); } @Test - public void Check_search_projects() throws IOException { + public void checkSearchProjects() throws Exception { final List searchedProjects = api.searchProjects("foo"); - assertEquals(0, searchedProjects.size()); + assertThat(searchedProjects, not(nullValue())); + assertThat(searchedProjects.isEmpty(), is(true)); } /** * There is at least one namespace for the user - * - * @throws IOException */ @Test - public void testGetNamespace() throws IOException { + public void testGetNamespace() { final List gitlabNamespaces = api.getNamespaces(); - assertTrue(gitlabNamespaces.size() > 0); + assertThat(gitlabNamespaces, not(nullValue())); + assertThat(gitlabNamespaces.isEmpty(), is(false)); } @Test - public void testCreateDeleteFork() throws IOException { + public void testCreateDeleteFork() throws Exception { String projectName = randVal("Fork-me"); - String password = randVal("$%password"); - - GitlabUser gitUser = api.createUser(randVal("testEmail@gitlabapitest.com"), password, randVal("userName"), @@ -237,24 +253,25 @@ public void testCreateDeleteFork() throws IOException { randVal("bio"), false, false, + false, false); + String namespace = api.getNamespaces().get(0).getPath(); GitlabProject project = api.createUserProject(gitUser.getId(), projectName); - GitlabProject fork = api.createFork(api.getNamespaces().get(0).getPath(), project); + GitlabProject fork = api.createFork(namespace, project); assertNotNull(fork); - assertEquals(project.getId(), fork.getForkedFrom().getId()); + assertEquals(project.getNamespace(), namespace); api.deleteProject(project.getId()); api.deleteProject(fork.getId()); - api.deleteUser(gitUser.getId()); } private String randVal(String postfix) { - return rand + "_" + postfix; + return createRandomString() + "_" + postfix; } private static String createRandomString() { diff --git a/src/test/java/org/gitlab/api/GitlabAPIUT.java b/src/test/java/org/gitlab/api/GitlabAPIUT.java new file mode 100644 index 00000000..dd5cec6e --- /dev/null +++ b/src/test/java/org/gitlab/api/GitlabAPIUT.java @@ -0,0 +1,94 @@ +package org.gitlab.api; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.SocketTimeoutException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Created by Oleg Shaburov on 03.05.2018 + * shaburov.o.a@gmail.com + */ +@SuppressWarnings("WeakerAccess") +public class GitlabAPIUT { + + //@Test + @DisplayName(value = "Check non-routable connection with connection timeout error") + public void unitTest_20180503175711() { + GitlabAPI api = GitlabAPI.connect("http://172.16.0.0:80", "test"); + api.setConnectionTimeout(100); + Throwable exception = assertThrows(NoRouteToHostException.class, api::getVersion); + assertThat(exception.getMessage(), is("No route to host")); + } + + @Test + @DisplayName(value = "Check default value is 0 for connection timeout") + public void unitTest_20180503185536() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + assertThat(api.getConnectionTimeout(), is(0)); + } + + @Test + @DisplayName(value = "Check set/get methods for connection timeout parameter") + public void unitTest_20180503185632() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + api.setConnectionTimeout(123); + assertThat(api.getConnectionTimeout(), is(123)); + } + + @Test + @DisplayName(value = "Check ignore negative value for connection timeout parameter") + public void unitTest_20180503185750() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + api.setConnectionTimeout(-123); + assertThat(api.getConnectionTimeout(), is(0)); + } + + @Test + @DisplayName(value = "Check connection with read timeout error") + public void unitTest_20180503191159() throws IOException { + ServerSocket socket = null; + try { + socket = new ServerSocket(15896); + GitlabAPI api = GitlabAPI.connect("http://localhost:15896", "test"); + api.setResponseReadTimeout(100); + Throwable exception = assertThrows(SocketTimeoutException.class, api::getVersion); + assertThat(exception.getMessage(), is("Read timed out")); + } finally { + if (socket != null) { + socket.close(); + } + } + } + + @Test + @DisplayName(value = "Check default value is 0 for request timeout parameter") + public void unitTest_20180503191716() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + assertThat(api.getResponseReadTimeout(), is(0)); + } + + @Test + @DisplayName(value = "Check set/get methods for request timeout parameter") + public void unitTest_20180503191945() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + api.setResponseReadTimeout(123); + assertThat(api.getResponseReadTimeout(), is(123)); + } + + @Test + @DisplayName(value = "Check ignore negative value for request timeout parameter") + public void unitTest_20180503192141() { + GitlabAPI api = GitlabAPI.connect("http://test.api", "test"); + api.setResponseReadTimeout(-123); + assertThat(api.getResponseReadTimeout(), is(0)); + } + +} diff --git a/src/test/java/org/gitlab/api/InstantDeserializerTest.java b/src/test/java/org/gitlab/api/InstantDeserializerTest.java new file mode 100644 index 00000000..727554db --- /dev/null +++ b/src/test/java/org/gitlab/api/InstantDeserializerTest.java @@ -0,0 +1,34 @@ +package org.gitlab.api; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gitlab.api.jackson.InstantDeserializer; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.*; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +class InstantDeserializerTest { + + @Test + void deserialize() throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + + final InstantDeserializer deserializer = new InstantDeserializer(); + final JsonParser parser = objectMapper.treeAsTokens(objectMapper.readTree("\"2016-08-11T11:28:34.085Z\"")); + parser.nextToken(); + final Instant instant = deserializer.deserialize(parser, objectMapper.getDeserializationContext()); + + assertEquals(Instant.from( + ZonedDateTime.of( + LocalDate.of(2016, 8, 11), + LocalTime.of(11, 28, 34, (int) TimeUnit.MILLISECONDS.toNanos(85)), + ZoneOffset.UTC + ) + ), instant); + } + +} \ No newline at end of file diff --git a/src/test/java/org/gitlab/api/TestUtils.java b/src/test/java/org/gitlab/api/TestUtils.java new file mode 100644 index 00000000..71bbe027 --- /dev/null +++ b/src/test/java/org/gitlab/api/TestUtils.java @@ -0,0 +1,18 @@ +package org.gitlab.api; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +public class TestUtils { + public static String readDataFromResource(String path) throws IOException { + InputStream inputStream = TestUtils.class.getClassLoader().getResourceAsStream(path); + assert inputStream != null; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } + +} diff --git a/src/test/java/org/gitlab/api/http/GitlabHTTPRequestorTest.java b/src/test/java/org/gitlab/api/http/GitlabHTTPRequestorTest.java index f9effc4b..9db94bac 100644 --- a/src/test/java/org/gitlab/api/http/GitlabHTTPRequestorTest.java +++ b/src/test/java/org/gitlab/api/http/GitlabHTTPRequestorTest.java @@ -1,20 +1,114 @@ package org.gitlab.api.http; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.gitlab.api.GitlabAPI; -import org.junit.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; + +@SuppressWarnings({"WeakerAccess", "ConstantConditions"}) public class GitlabHTTPRequestorTest { @Test - public void testSettingInvalidHTTPMethod() { - GitlabHTTPRequestor http = new GitlabHTTPRequestor(GitlabAPI.connect("localhost", "api")); + @DisplayName(value = "Expected success, calling the \"setupConnection\" method if the connection timeout = 0") + public void unitTest_20180503194340() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getConnectionTimeout()).thenReturn(0); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + HttpURLConnection connection = (HttpURLConnection) method.invoke(http, url); + assertThat(connection.getConnectTimeout(), is(0)); + } + + @Test + @DisplayName(value = "Expected success, calling the \"setupConnection\" method if the connection timeout > 0") + public void unitTest_20180503194559() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getConnectionTimeout()).thenReturn(456); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + HttpURLConnection connection = (HttpURLConnection) method.invoke(http, url); + assertThat(connection.getConnectTimeout(), is(456)); + } + + @Test + @DisplayName(value = "An error is expected, calling the \"setupConnection\" method if the connection timeout < 0") + public void unitTest_20180503194643() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getConnectionTimeout()).thenReturn(-555); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + Throwable throwable = null; + try { + method.invoke(http, url); + } catch (Exception e) { + throwable = e.getCause(); + } + assertThat(throwable, not(nullValue())); + assertThat(throwable, is(instanceOf(IllegalArgumentException.class))); + assertThat(throwable.getMessage(), is("timeouts can't be negative")); + } + + @Test + @DisplayName(value = "Expected success, calling the \"setupConnection\" method if the read timeout = 0") + public void unitTest_20180503202458() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getResponseReadTimeout()).thenReturn(0); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + HttpURLConnection connection = (HttpURLConnection) method.invoke(http, url); + assertThat(connection.getReadTimeout(), is(0)); + } + + @Test + @DisplayName(value = "Expected success, calling the \"setupConnection\" method if the read timeout > 0") + public void unitTest_20180503203531() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getResponseReadTimeout()).thenReturn(555); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + HttpURLConnection connection = (HttpURLConnection) method.invoke(http, url); + assertThat(connection.getReadTimeout(), is(555)); + } + + @Test + @DisplayName(value = "An error is expected, calling the \"setupConnection\" method if the read timeout < 0") + public void unitTest_20180503203635() throws Exception { + GitlabAPI api = mock(GitlabAPI.class); + when(api.getResponseReadTimeout()).thenReturn(-555); + GitlabHTTPRequestor http = new GitlabHTTPRequestor(api); + URL url = new URL("http://test.url"); + Method method = GitlabHTTPRequestor.class.getDeclaredMethod("setupConnection", URL.class); + method.setAccessible(true); + Throwable throwable = null; try { - http.method("WRONG METHOD"); + method.invoke(http, url); } catch (Exception e) { - assertEquals("Invalid HTTP Method: WRONG METHOD. Must be one of GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, TRACE", e.getMessage()); + throwable = e.getCause(); } + assertThat(throwable, not(nullValue())); + assertThat(throwable, is(instanceOf(IllegalArgumentException.class))); + assertThat(throwable.getMessage(), is("timeouts can't be negative")); } } diff --git a/src/test/java/org/gitlab/api/models/GitlabIssueDeserializationTest.java b/src/test/java/org/gitlab/api/models/GitlabIssueDeserializationTest.java new file mode 100644 index 00000000..52802671 --- /dev/null +++ b/src/test/java/org/gitlab/api/models/GitlabIssueDeserializationTest.java @@ -0,0 +1,17 @@ +package org.gitlab.api.models; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gitlab.api.TestUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +class GitlabIssueDeserializationTest { + @Test + void deserializationTest() { + ObjectMapper objectMapper = new ObjectMapper(); + Assertions.assertDoesNotThrow(() -> objectMapper.readValue( + TestUtils.readDataFromResource("IssueExample.json"), GitlabIssue.class)); + } +} diff --git a/src/test/resources/IssueExample.json b/src/test/resources/IssueExample.json new file mode 100644 index 00000000..66936686 --- /dev/null +++ b/src/test/resources/IssueExample.json @@ -0,0 +1,44 @@ +{ + "id":231, + "iid":2456, + "project_id":871, + "title":"Data Feed Dghrythfh00", + "description":"# Bountyrdhfy.", + "state":"opened", + "created_at":"2019-06-06T21:54:50.241Z", + "updated_at":"2019-06-08T17:22:59.613Z", + "closed_at":null, + "closed_by":null, + "labels":[ + "bounty::available", + "status::untouched" + ], + "milestone":null, + "assignees":[ + + ], + "author":{ + "id":1325, + "name":"jlsfldgs", + "username":"jlsfldgsd", + "state":"active", + "avatar_url":"https://assets.gitlab-static.net/uploads/-/system/user/avatar/139453452225/avatar.png", + "web_url":"https://gitlab.com/jlsfldgsd" + }, + "assignee":null, + "user_notes_count":4, + "merge_requests_count":0, + "upvotes":0, + "downvotes":0, + "due_date":"2019-06-29", + "confidential":false, + "discussion_locked":null, + "web_url":"https://gitlab.com/3456457", + "time_stats":{ + "time_estimate":0, + "total_time_spent":0, + "human_time_estimate":null, + "human_total_time_spent":null + }, + "weight":null +} \ No newline at end of file diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 00000000..57f3d206 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + %d{HH:mm:ss.SSS} [%-5p] [%25.25C] - %m%n + + + + + + + + + + + +